diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 5ca7c241..841d3c6c 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -34,6 +34,7 @@ type ABI struct { Constructor Method Methods map[string]Method Events map[string]Event + Errors map[string]Error // Additional "special" functions introduced in solidity v0.6.0. // It's separated from the original default fallback. Each contract @@ -86,7 +87,7 @@ func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { var args Arguments if method, ok := abi.Methods[name]; ok { if len(data)%32 != 0 { - return nil, fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) + return nil, fmt.Errorf("abi: improperly formatted output: %q - Bytes: %+v", data, data) } args = method.Outputs } @@ -94,7 +95,7 @@ func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { args = event.Inputs } if args == nil { - return nil, errors.New("abi: could not locate named method or event") + return nil, fmt.Errorf("abi: could not locate named method or event: %s", name) } return args, nil } @@ -157,12 +158,13 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { } abi.Methods = make(map[string]Method) abi.Events = make(map[string]Event) + abi.Errors = make(map[string]Error) for _, field := range fields { switch field.Type { case "constructor": abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil) case "function": - name := abi.overloadedMethodName(field.Name) + name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok }) abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs) case "fallback": // New introduced function type in v0.6.0, check more detail @@ -182,8 +184,12 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { } abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil) case "event": - name := abi.overloadedEventName(field.Name) + name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok }) abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs) + case "error": + // Errors cannot be overloaded or overridden but are inherited, + // no need to resolve the name conflict here. + abi.Errors[field.Name] = NewError(field.Name, field.Inputs) default: return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name) } @@ -191,36 +197,6 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { return nil } -// overloadedMethodName returns the next available name for a given function. -// Needed since solidity allows for function overload. -// -// e.g. if the abi contains Methods send, send1 -// overloadedMethodName would return send2 for input send. -func (abi *ABI) overloadedMethodName(rawName string) string { - name := rawName - _, ok := abi.Methods[name] - for idx := 0; ok; idx++ { - name = fmt.Sprintf("%s%d", rawName, idx) - _, ok = abi.Methods[name] - } - return name -} - -// overloadedEventName returns the next available name for a given event. -// Needed since solidity allows for event overload. -// -// e.g. if the abi contains events received, received1 -// overloadedEventName would return received2 for input received. -func (abi *ABI) overloadedEventName(rawName string) string { - name := rawName - _, ok := abi.Events[name] - for idx := 0; ok; idx++ { - name = fmt.Sprintf("%s%d", rawName, idx) - _, ok = abi.Events[name] - } - return name -} - // MethodById looks up a method by the 4-byte id, // returns nil if none found. func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index a022ec5f..96c11e09 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -165,8 +165,9 @@ func TestInvalidABI(t *testing.T) { // TestConstructor tests a constructor function. // The test is based on the following contract: -// contract TestConstructor { -// constructor(uint256 a, uint256 b) public{} +// +// contract TestConstructor { +// constructor(uint256 a, uint256 b) public{} // } func TestConstructor(t *testing.T) { json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]` @@ -295,6 +296,20 @@ func TestOverloadedMethodSignature(t *testing.T) { check("bar0", "bar(uint256,uint256)", false) } +func TestCustomErrors(t *testing.T) { + json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]` + abi, err := JSON(strings.NewReader(json)) + if err != nil { + t.Fatal(err) + } + check := func(name string, expect string) { + if abi.Errors[name].Sig != expect { + t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig) + } + } + check("MyError", "MyError(uint256)") +} + func TestMultiPack(t *testing.T) { abi, err := JSON(strings.NewReader(jsondata)) if err != nil { @@ -710,16 +725,19 @@ func TestBareEvents(t *testing.T) { } // TestUnpackEvent is based on this contract: -// contract T { -// event received(address sender, uint amount, bytes memo); -// event receivedAddr(address sender); -// function receive(bytes memo) external payable { -// received(msg.sender, msg.value, memo); -// receivedAddr(msg.sender); -// } -// } +// +// contract T { +// event received(address sender, uint amount, bytes memo); +// event receivedAddr(address sender); +// function receive(bytes memo) external payable { +// received(msg.sender, msg.value, memo); +// receivedAddr(msg.sender); +// } +// } +// // When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt: -// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} +// +// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} func TestUnpackEvent(t *testing.T) { const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` abi, err := JSON(strings.NewReader(abiJSON)) @@ -1024,9 +1042,7 @@ func TestABI_EventById(t *testing.T) { } if event == nil { t.Errorf("We should find a event for topic %s, test #%d", topicID.Hex(), testnum) - } - - if event.ID != topicID { + } else if event.ID != topicID { t.Errorf("Event id %s does not match topic %s, test #%d", event.ID.Hex(), topicID.Hex(), testnum) } @@ -1066,8 +1082,9 @@ func TestDoubleDuplicateMethodNames(t *testing.T) { // TestDoubleDuplicateEventNames checks that if send0 already exists, there won't be a name // conflict and that the second send event will be renamed send1. // The test runs the abi of the following contract. -// contract DuplicateEvent { -// event send(uint256 a); +// +// contract DuplicateEvent { +// event send(uint256 a); // event send0(); // event send(); // } @@ -1094,7 +1111,8 @@ func TestDoubleDuplicateEventNames(t *testing.T) { // TestUnnamedEventParam checks that an event with unnamed parameters is // correctly handled. // The test runs the abi of the following contract. -// contract TestEvent { +// +// contract TestEvent { // event send(uint256, uint256); // } func TestUnnamedEventParam(t *testing.T) { diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index e6d52455..2e48d539 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -18,6 +18,7 @@ package abi import ( "encoding/json" + "errors" "fmt" "reflect" "strings" @@ -78,16 +79,10 @@ func (arguments Arguments) isTuple() bool { // Unpack performs the operation hexdata -> Go format. func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { if len(data) == 0 { - if len(arguments) != 0 { - return nil, fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") + if len(arguments.NonIndexed()) != 0 { + return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected") } - // 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 make([]interface{}, 0), nil } return arguments.UnpackValues(data) } @@ -96,11 +91,11 @@ func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { // Make sure map is not nil if v == nil { - return fmt.Errorf("abi: cannot unpack into a nil map") + return errors.New("abi: cannot unpack into a nil map") } if len(data) == 0 { - if len(arguments) != 0 { - return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") + if len(arguments.NonIndexed()) != 0 { + return errors.New("abi: attempting to unmarshall an empty string while arguments are expected") } return nil // Nothing to unmarshal, return } @@ -121,8 +116,8 @@ func (arguments Arguments) Copy(v interface{}, values []interface{}) error { 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)) + if len(arguments.NonIndexed()) != 0 { + return errors.New("abi: attempting to copy no values while arguments are expected") } return nil // Nothing to copy, return } @@ -137,7 +132,7 @@ func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{ dst := reflect.ValueOf(v).Elem() src := reflect.ValueOf(marshalledValues) - if dst.Kind() == reflect.Struct && src.Kind() != reflect.Struct { + if dst.Kind() == reflect.Struct { return set(dst.Field(0), src) } return set(dst, src) @@ -192,6 +187,9 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { virtualArgs := 0 for index, arg := range nonIndexedArgs { marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) + if err != nil { + return nil, err + } if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -209,9 +207,6 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { // coded as just like uint256,bool,uint256 virtualArgs += getTypeSize(arg.Type)/32 - 1 } - if err != nil { - return nil, err - } retval = append(retval, marshalledValue) } return retval, nil diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index daae4418..f25bab1b 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -63,11 +63,13 @@ type SimulatedBackend struct { database ethdb.Database // In memory database to store our testing data blockchain *core.BlockChain // Ethereum blockchain to handle the consensus - mu sync.Mutex - pendingBlock *types.Block // Currently pending block that will be imported on request - pendingState *state.StateDB // Currently pending state that will be the active on request + mu sync.Mutex + pendingBlock *types.Block // Currently pending block that will be imported on request + pendingState *state.StateDB // Currently pending state that will be the active on request + pendingReceipts types.Receipts // Currently receipts for the pending block - events *filters.EventSystem // Event system for filtering log events live + events *filters.EventSystem // for filtering log events live + filterSystem *filters.FilterSystem // for filtering database logs config *params.ChainConfig } @@ -76,17 +78,29 @@ type SimulatedBackend struct { // 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 := core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: gasLimit, + Alloc: alloc, + CommunityRate: big.NewInt(20), + } genesis.MustCommit(database) - blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, err := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + if err != nil { + panic(err) + } backend := &SimulatedBackend{ database: database, blockchain: blockchain, config: genesis.Config, - events: filters.NewEventSystem(&filterBackend{database, blockchain}, false), } - backend.rollback() + + filterBackend := &filterBackend{database, blockchain, backend} + backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{}) + backend.events = filters.NewEventSystem(backend.filterSystem, false) + + backend.rollback(blockchain.CurrentBlock()) return backend } @@ -105,14 +119,20 @@ func (b *SimulatedBackend) Close() error { // Commit imports all the pending transactions as a single block and starts a // fresh new state. -func (b *SimulatedBackend) Commit() { +func (b *SimulatedBackend) Commit() common.Hash { b.mu.Lock() defer b.mu.Unlock() if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { panic(err) // This cannot happen unless the simulator is wrong, fail in that case } - b.rollback() + blockHash := b.pendingBlock.Hash() + + // Using the last inserted block here makes it possible to build on a side + // chain after a fork. + b.rollback(b.pendingBlock) + + return blockHash } // Rollback aborts all pending transactions, reverting to the last committed state. @@ -120,22 +140,49 @@ func (b *SimulatedBackend) Rollback() { b.mu.Lock() defer b.mu.Unlock() - b.rollback() + b.rollback(b.blockchain.CurrentBlock()) } -func (b *SimulatedBackend) rollback() { - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) +func (b *SimulatedBackend) rollback(parent *types.Block) { + blocks, _ := core.GenerateChain(b.config, parent, ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil) } +// Fork creates a side-chain that can be used to simulate reorgs. +// +// This function should be called with the ancestor block where the new side +// chain should be started. Transactions (old and new) can then be applied on +// top and Commit-ed. +// +// Note, the side-chain will only become canonical (and trigger the events) when +// it becomes longer. Until then CallContract will still operate on the current +// canonical chain. +// +// There is a % chance that the side chain becomes canonical at the same length +// to simulate live network behavior. +func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error { + b.mu.Lock() + defer b.mu.Unlock() + + if len(b.pendingBlock.Transactions()) != 0 { + return errors.New("pending block dirty") + } + block, err := b.blockByHash(ctx, parent) + if err != nil { + return err + } + b.rollback(block) + return nil +} + // stateByBlockNumber retrieves a state by a given blocknumber. func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) { if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) == 0 { return b.blockchain.State() } - block, err := b.blockByNumberNoLock(ctx, blockNumber) + block, err := b.blockByNumber(ctx, blockNumber) if err != nil { return nil, err } @@ -201,6 +248,9 @@ func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common defer b.mu.Unlock() receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config) + if receipt == nil { + return nil, ethereum.NotFound + } return receipt, nil } @@ -228,6 +278,11 @@ func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (* b.mu.Lock() defer b.mu.Unlock() + return b.blockByHash(ctx, hash) +} + +// blockByHash retrieves a block based on the block hash without Locking. +func (b *SimulatedBackend) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { if hash == b.pendingBlock.Hash() { return b.pendingBlock, nil } @@ -246,12 +301,12 @@ func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) ( b.mu.Lock() defer b.mu.Unlock() - return b.blockByNumberNoLock(ctx, number) + return b.blockByNumber(ctx, number) } -// blockByNumberNoLock retrieves a block from the database by number, caching it +// blockByNumber 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) blockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 { return b.blockchain.CurrentBlock(), nil } @@ -428,6 +483,12 @@ 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) { + b.mu.Lock() + defer b.mu.Unlock() + + if b.pendingBlock.Header().BaseFee != nil { + return b.pendingBlock.Header().BaseFee, nil + } return big.NewInt(1), nil } @@ -454,24 +515,35 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs } else { hi = b.pendingBlock.GasLimit() } + // Normalize the max fee per gas the call is willing to spend. + var feeCap *big.Int + if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) { + return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } else if call.GasPrice != nil { + feeCap = call.GasPrice + } else if call.GasFeeCap != nil { + feeCap = call.GasFeeCap + } else { + feeCap = common.Big0 + } // Recap the highest gas allowance with account's balance. - if call.GasPrice != nil && call.GasPrice.BitLen() != 0 { + if feeCap.BitLen() != 0 { balance := b.pendingState.GetBalance(call.From) // from can't be nil available := new(big.Int).Set(balance) if call.Value != nil { if call.Value.Cmp(available) >= 0 { - return 0, errors.New("insufficient funds for transfer") + return 0, core.ErrInsufficientFundsForTransfer } available.Sub(available, call.Value) } - allowance := new(big.Int).Div(available, call.GasPrice) + allowance := new(big.Int).Div(available, feeCap) if allowance.IsUint64() && hi > allowance.Uint64() { transfer := call.Value if transfer == nil { transfer = new(big.Int) } log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, - "sent", transfer, "gasprice", call.GasPrice, "fundable", allowance) + "sent", transfer, "feecap", feeCap, "fundable", allowance) hi = allowance.Uint64() } } @@ -550,7 +622,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // User specified the legacy gas field, convert to 1559 gas typing call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice } else { - // User specified 1559 gas feilds (or none), use those + // User specified 1559 gas fields (or none), use those if call.GasFeeCap == nil { call.GasFeeCap = new(big.Int) } @@ -588,25 +660,27 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM } // 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 { b.mu.Lock() defer b.mu.Unlock() - // Check transaction validity. - block := b.blockchain.CurrentBlock() + // Get the last block + block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash()) + if err != nil { + return fmt.Errorf("could not fetch parent") + } + // Check transaction validity signer := types.MakeSigner(b.blockchain.Config(), block.Number()) sender, err := types.Sender(signer, tx) if err != nil { - panic(fmt.Errorf("invalid transaction: %v", err)) + return fmt.Errorf("invalid transaction: %v", err) } nonce := b.pendingState.GetNonce(sender) if tx.Nonce() != nonce { - panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) + return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce) } - - // Include tx in chain. - blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + // Include tx in chain + blocks, receipts := 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) } @@ -616,6 +690,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) + b.pendingReceipts = receipts[0] return nil } @@ -627,7 +702,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter var filter *filters.Filter if query.BlockHash != nil { // Block filter requested, construct a single-shot filter - filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics) + filter = b.filterSystem.NewBlockFilter(*query.BlockHash, query.Addresses, query.Topics) } else { // Initialize unset filter boundaries to run from genesis to chain head from := int64(0) @@ -639,7 +714,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter to = query.ToBlock.Int64() } // Construct the range filter - filter = filters.NewRangeFilter(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics) + filter = b.filterSystem.NewRangeFilter(from, to, query.Addresses, query.Topics) } // Run the filter and return all the logs logs, err := filter.Logs(ctx) @@ -723,8 +798,13 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { if len(b.pendingBlock.Transactions()) != 0 { return errors.New("Could not adjust time on non-empty block") } + // Get the last block + block := b.blockchain.GetBlockByHash(b.pendingBlock.ParentHash()) + if block == nil { + return fmt.Errorf("could not find parent") + } - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { block.OffsetTime(int64(adjustment.Seconds())) }) stateDB, _ := b.blockchain.State() @@ -740,7 +820,7 @@ func (b *SimulatedBackend) Blockchain() *core.BlockChain { return b.blockchain } -// callMsg implements core.message to allow passing it as a transaction simulator. +// callMsg implements core.Message to allow passing it as a transaction simulator. type callMsg struct { ethereum.CallMsg } @@ -748,6 +828,7 @@ type callMsg struct { 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) IsFake() bool { return true } func (m callMsg) To() *common.Address { return m.CallMsg.To } func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap } @@ -761,24 +842,37 @@ func (m callMsg) TxHash() common.Hash { return common.Hash{} } // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. type filterBackend struct { - db ethdb.Database - bc *core.BlockChain + db ethdb.Database + bc *core.BlockChain + backend *SimulatedBackend } -func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db } +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) { - if block == rpc.LatestBlockNumber { +func (fb *filterBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + switch number { + case rpc.PendingBlockNumber: + if block := fb.backend.pendingBlock; block != nil { + return block.Header(), nil + } + return nil, nil + case rpc.LatestBlockNumber: return fb.bc.CurrentHeader(), nil + default: + return fb.bc.GetHeaderByNumber(uint64(number.Int64())), nil } - return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil } func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { return fb.bc.GetHeaderByHash(hash), nil } +func (fb *filterBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + return fb.backend.pendingBlock, fb.backend.pendingReceipts +} + func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { number := rawdb.ReadHeaderNumber(fb.db, hash) if number == nil { @@ -787,19 +881,8 @@ 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) { - number := rawdb.ReadHeaderNumber(fb.db, hash) - if number == nil { - return nil, nil - } - receipts := rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()) - if receipts == nil { - return nil, nil - } - logs := make([][]*types.Log, len(receipts)) - for i, receipt := range receipts { - logs[i] = receipt.Logs - } +func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { + logs := rawdb.ReadLogs(fb.db, hash, number, fb.bc.Config()) return logs, nil } @@ -829,6 +912,14 @@ func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.Matche panic("not supported") } +func (fb *filterBackend) ChainConfig() *params.ChainConfig { + panic("not supported") +} + +func (fb *filterBackend) CurrentHeader() *types.Header { + panic("not supported") +} + func nullSubscription() event.Subscription { return event.NewSubscription(func(quit <-chan struct{}) error { <-quit diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 210a03d9..85bda49d 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -21,6 +21,7 @@ import ( "context" "errors" "math/big" + "math/rand" "reflect" "strings" "testing" @@ -37,6 +38,7 @@ import ( ) func TestSimulatedBackend(t *testing.T) { + core.CheckAllocWithTotalSupply = false var gasLimit uint64 = 8000029 key, _ := crypto.GenerateKey() // nolint: gosec auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) @@ -92,17 +94,18 @@ func TestSimulatedBackend(t *testing.T) { var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") -// the following is based on this contract: -// contract T { -// event received(address sender, uint amount, bytes memo); -// event receivedAddr(address sender); +// the following is based on this contract: // -// function receive(bytes calldata memo) external payable returns (string memory res) { -// emit received(msg.sender, msg.value, memo); -// emit receivedAddr(msg.sender); -// return "hello world"; -// } -// } +// contract T { +// event received(address sender, uint amount, bytes memo); +// event receivedAddr(address sender); +// +// function receive(bytes calldata memo) external payable returns (string memory res) { +// emit received(msg.sender, msg.value, memo); +// emit receivedAddr(msg.sender); +// return "hello world"; +// } +// } const abiJSON = `[ { "constant": false, "inputs": [ { "name": "memo", "type": "bytes" } ], "name": "receive", "outputs": [ { "name": "res", "type": "string" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" }, { "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "memo", "type": "bytes" } ], "name": "received", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" } ], "name": "receivedAddr", "type": "event" } ]` const abiBin = `0x608060405234801561001057600080fd5b506102a0806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` const deployedCode = `60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` @@ -111,6 +114,7 @@ const deployedCode = `60806040526004361061003b576000357c010000000000000000000000 var expectedReturn = []byte{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, 32, 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, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} func simTestBackend(testAddr common.Address) *SimulatedBackend { + core.CheckAllocWithTotalSupply = false return NewSimulatedBackend( core.GenesisAlloc{ testAddr: {Balance: big.NewInt(10000000000000000)}, @@ -139,7 +143,8 @@ func TestNewSimulatedBackend(t *testing.T) { } } -func TestSimulatedBackend_AdjustTime(t *testing.T) { +func TestAdjustTime(t *testing.T) { + core.CheckAllocWithTotalSupply = false sim := NewSimulatedBackend( core.GenesisAlloc{}, 10000000, ) @@ -156,7 +161,7 @@ func TestSimulatedBackend_AdjustTime(t *testing.T) { } } -func TestNewSimulatedBackend_AdjustTimeFail(t *testing.T) { +func TestNewAdjustTimeFail(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -198,7 +203,7 @@ func TestNewSimulatedBackend_AdjustTimeFail(t *testing.T) { } } -func TestSimulatedBackend_BalanceAt(t *testing.T) { +func TestBalanceAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) expectedBal := big.NewInt(10000000000000000) sim := simTestBackend(testAddr) @@ -215,7 +220,8 @@ func TestSimulatedBackend_BalanceAt(t *testing.T) { } } -func TestSimulatedBackend_BlockByHash(t *testing.T) { +func TestBlockByHash(t *testing.T) { + core.CheckAllocWithTotalSupply = false sim := NewSimulatedBackend( core.GenesisAlloc{}, 10000000, ) @@ -236,7 +242,8 @@ func TestSimulatedBackend_BlockByHash(t *testing.T) { } } -func TestSimulatedBackend_BlockByNumber(t *testing.T) { +func TestBlockByNumber(t *testing.T) { + core.CheckAllocWithTotalSupply = false sim := NewSimulatedBackend( core.GenesisAlloc{}, 10000000, ) @@ -271,7 +278,7 @@ func TestSimulatedBackend_BlockByNumber(t *testing.T) { } } -func TestSimulatedBackend_NonceAt(t *testing.T) { +func TestNonceAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -324,7 +331,7 @@ func TestSimulatedBackend_NonceAt(t *testing.T) { } } -func TestSimulatedBackend_SendTransaction(t *testing.T) { +func TestSendTransaction(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -358,7 +365,8 @@ func TestSimulatedBackend_SendTransaction(t *testing.T) { } } -func TestSimulatedBackend_TransactionByHash(t *testing.T) { +func TestTransactionByHash(t *testing.T) { + core.CheckAllocWithTotalSupply = false testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := NewSimulatedBackend( @@ -412,16 +420,18 @@ func TestSimulatedBackend_TransactionByHash(t *testing.T) { } } -func TestSimulatedBackend_EstimateGas(t *testing.T) { +func TestEstimateGas(t *testing.T) { + core.CheckAllocWithTotalSupply = false /* pragma solidity ^0.6.4; contract GasEstimation { - function PureRevert() public { revert(); } - function Revert() public { revert("revert reason");} - function OOG() public { for (uint i = 0; ; i++) {}} - function Assert() public { assert(false);} - function Valid() public {} - }*/ + function PureRevert() public { revert(); } + function Revert() public { revert("revert reason");} + function OOG() public { for (uint i = 0; ; i++) {}} + function Assert() public { assert(false);} + function Valid() public {} + } + */ const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033" @@ -530,7 +540,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) { } } -func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { +func TestEstimateGasWithPrice(t *testing.T) { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -579,6 +589,26 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { Value: big.NewInt(100000000000), Data: nil, }, 21000, errors.New("gas required exceeds allowance (10999)")}, // 10999=(2.2ether-1000wei)/(2e14) + + {"EstimateEIP1559WithHighFees", ethereum.CallMsg{ + From: addr, + To: &addr, + Gas: 0, + GasFeeCap: big.NewInt(1e14), // maxgascost = 2.1ether + GasTipCap: big.NewInt(1), + Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether + Data: nil, + }, params.TxGas, nil}, + + {"EstimateEIP1559WithSuperHighFees", ethereum.CallMsg{ + From: addr, + To: &addr, + Gas: 0, + GasFeeCap: big.NewInt(1e14), // maxgascost = 2.1ether + GasTipCap: big.NewInt(1), + Value: big.NewInt(1e17 + 1), // the remaining balance for fee is 2.1ether + Data: nil, + }, params.TxGas, errors.New("gas required exceeds allowance (20999)")}, // 20999=(2.2ether-0.1ether-1wei)/(1e14) } for i, c := range cases { got, err := sim.EstimateGas(context.Background(), c.message) @@ -591,13 +621,16 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { } continue } + if c.expectError == nil && err != nil { + t.Fatalf("test %d: didn't expect error, got %v", i, err) + } if got != c.expect { t.Fatalf("test %d: gas estimation mismatch, want %d, got %d", i, c.expect, got) } } } -func TestSimulatedBackend_HeaderByHash(t *testing.T) { +func TestHeaderByHash(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -618,7 +651,7 @@ func TestSimulatedBackend_HeaderByHash(t *testing.T) { } } -func TestSimulatedBackend_HeaderByNumber(t *testing.T) { +func TestHeaderByNumber(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -631,8 +664,7 @@ func TestSimulatedBackend_HeaderByNumber(t *testing.T) { } if latestBlockHeader == nil { t.Errorf("received a nil block header") - } - if latestBlockHeader.Number.Uint64() != uint64(0) { + } else if latestBlockHeader.Number.Uint64() != uint64(0) { t.Errorf("expected block header number 0, instead got %v", latestBlockHeader.Number.Uint64()) } @@ -665,7 +697,7 @@ func TestSimulatedBackend_HeaderByNumber(t *testing.T) { } } -func TestSimulatedBackend_TransactionCount(t *testing.T) { +func TestTransactionCount(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -684,7 +716,6 @@ func TestSimulatedBackend_TransactionCount(t *testing.T) { if count != 0 { t.Errorf("expected transaction count of %v does not match actual count of %v", 0, count) } - // create a signed transaction to send head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) @@ -718,7 +749,7 @@ func TestSimulatedBackend_TransactionCount(t *testing.T) { } } -func TestSimulatedBackend_TransactionInBlock(t *testing.T) { +func TestTransactionInBlock(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -742,7 +773,6 @@ func TestSimulatedBackend_TransactionInBlock(t *testing.T) { if pendingNonce != uint64(0) { t.Errorf("expected pending nonce of 0 got %v", pendingNonce) } - // create a signed transaction to send head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) @@ -784,7 +814,7 @@ func TestSimulatedBackend_TransactionInBlock(t *testing.T) { } } -func TestSimulatedBackend_PendingNonceAt(t *testing.T) { +func TestPendingNonceAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -849,7 +879,7 @@ func TestSimulatedBackend_PendingNonceAt(t *testing.T) { } } -func TestSimulatedBackend_TransactionReceipt(t *testing.T) { +func TestTransactionReceipt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) @@ -883,7 +913,8 @@ func TestSimulatedBackend_TransactionReceipt(t *testing.T) { } } -func TestSimulatedBackend_SuggestGasPrice(t *testing.T) { +func TestSuggestGasPrice(t *testing.T) { + core.CheckAllocWithTotalSupply = false sim := NewSimulatedBackend( core.GenesisAlloc{}, 10000000, @@ -894,12 +925,12 @@ func TestSimulatedBackend_SuggestGasPrice(t *testing.T) { if err != nil { t.Errorf("could not get gas price: %v", err) } - if gasPrice.Uint64() != uint64(1) { - t.Errorf("gas price was not expected value of 1. actual: %v", gasPrice.Uint64()) + if gasPrice.Uint64() != sim.pendingBlock.Header().BaseFee.Uint64() { + t.Errorf("gas price was not expected value of %v. actual: %v", sim.pendingBlock.Header().BaseFee.Uint64(), gasPrice.Uint64()) } } -func TestSimulatedBackend_PendingCodeAt(t *testing.T) { +func TestPendingCodeAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) defer sim.Close() @@ -935,7 +966,7 @@ func TestSimulatedBackend_PendingCodeAt(t *testing.T) { } } -func TestSimulatedBackend_CodeAt(t *testing.T) { +func TestCodeAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) defer sim.Close() @@ -973,8 +1004,9 @@ func TestSimulatedBackend_CodeAt(t *testing.T) { } // When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt: -// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} -func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { +// +// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} +func TestPendingAndCallContract(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) defer sim.Close() @@ -1036,29 +1068,29 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { // This test is based on the following contract: /* contract Reverter { - function revertString() public pure{ - require(false, "some error"); - } - function revertNoString() public pure { - require(false, ""); - } - function revertASM() public pure { - assembly { - revert(0x0, 0x0) - } - } - function noRevert() public pure { - assembly { - // Assembles something that looks like require(false, "some error") but is not reverted - mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x4, 0x0000000000000000000000000000000000000000000000000000000000000020) - mstore(0x24, 0x000000000000000000000000000000000000000000000000000000000000000a) - mstore(0x44, 0x736f6d65206572726f7200000000000000000000000000000000000000000000) - return(0x0, 0x64) - } - } + function revertString() public pure{ + require(false, "some error"); + } + function revertNoString() public pure { + require(false, ""); + } + function revertASM() public pure { + assembly { + revert(0x0, 0x0) + } + } + function noRevert() public pure { + assembly { + // Assembles something that looks like require(false, "some error") but is not reverted + mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x4, 0x0000000000000000000000000000000000000000000000000000000000000020) + mstore(0x24, 0x000000000000000000000000000000000000000000000000000000000000000a) + mstore(0x44, 0x736f6d65206572726f7200000000000000000000000000000000000000000000) + return(0x0, 0x64) + } + } }*/ -func TestSimulatedBackend_CallContractRevert(t *testing.T) { +func TestCallContractRevert(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) defer sim.Close() @@ -1142,3 +1174,234 @@ func TestSimulatedBackend_CallContractRevert(t *testing.T) { sim.Commit() } } + +// TestFork check that the chain length after a reorg is correct. +// Steps: +// 1. Save the current block which will serve as parent for the fork. +// 2. Mine n blocks with n ∈ [0, 20]. +// 3. Assert that the chain length is n. +// 4. Fork by using the parent block as ancestor. +// 5. Mine n+1 blocks which should trigger a reorg. +// 6. Assert that the chain length is n+1. +// Since Commit() was called 2n+1 times in total, +// having a chain length of just n+1 means that a reorg occurred. +func TestFork(t *testing.T) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + // 1. + parent := sim.blockchain.CurrentBlock() + // 2. + n := int(rand.Int31n(21)) + for i := 0; i < n; i++ { + sim.Commit() + } + // 3. + if sim.blockchain.CurrentBlock().NumberU64() != uint64(n) { + t.Error("wrong chain length") + } + // 4. + sim.Fork(context.Background(), parent.Hash()) + // 5. + for i := 0; i < n+1; i++ { + sim.Commit() + } + // 6. + if sim.blockchain.CurrentBlock().NumberU64() != uint64(n+1) { + t.Error("wrong chain length") + } +} + +/* +Example contract to test event emission: + + pragma solidity >=0.7.0 <0.9.0; + contract Callable { + event Called(); + function Call() public { emit Called(); } + } +*/ +const callableAbi = "[{\"anonymous\":false,\"inputs\":[],\"name\":\"Called\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"Call\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + +const callableBin = "6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806334e2292114602d575b600080fd5b60336035565b005b7f81fab7a4a0aa961db47eefc81f143a5220e8c8495260dd65b1356f1d19d3c7b860405160405180910390a156fea2646970667358221220029436d24f3ac598ceca41d4d712e13ced6d70727f4cdc580667de66d2f51d8b64736f6c63430008010033" + +// TestForkLogsReborn check that the simulated reorgs +// correctly remove and reborn logs. +// Steps: +// 1. Deploy the Callable contract. +// 2. Set up an event subscription. +// 3. Save the current block which will serve as parent for the fork. +// 4. Send a transaction. +// 5. Check that the event was included. +// 6. Fork by using the parent block as ancestor. +// 7. Mine two blocks to trigger a reorg. +// 8. Check that the event was removed. +// 9. Re-send the transaction and mine a block. +// 10. Check that the event was reborn. +func TestForkLogsReborn(t *testing.T) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + // 1. + parsed, _ := abi.JSON(strings.NewReader(callableAbi)) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) + _, _, contract, err := bind.DeployContract(auth, parsed, common.FromHex(callableBin), sim) + if err != nil { + t.Errorf("deploying contract: %v", err) + } + sim.Commit() + // 2. + logs, sub, err := contract.WatchLogs(nil, "Called") + if err != nil { + t.Errorf("watching logs: %v", err) + } + defer sub.Unsubscribe() + // 3. + parent := sim.blockchain.CurrentBlock() + // 4. + tx, err := contract.Transact(auth, "Call") + if err != nil { + t.Errorf("transacting: %v", err) + } + sim.Commit() + // 5. + log := <-logs + if log.TxHash != tx.Hash() { + t.Error("wrong event tx hash") + } + if log.Removed { + t.Error("Event should be included") + } + // 6. + if err := sim.Fork(context.Background(), parent.Hash()); err != nil { + t.Errorf("forking: %v", err) + } + // 7. + sim.Commit() + sim.Commit() + // 8. + log = <-logs + if log.TxHash != tx.Hash() { + t.Error("wrong event tx hash") + } + if !log.Removed { + t.Error("Event should be removed") + } + // 9. + if err := sim.SendTransaction(context.Background(), tx); err != nil { + t.Errorf("sending transaction: %v", err) + } + sim.Commit() + // 10. + log = <-logs + if log.TxHash != tx.Hash() { + t.Error("wrong event tx hash") + } + if log.Removed { + t.Error("Event should be included") + } +} + +// TestForkResendTx checks that re-sending a TX after a fork +// is possible and does not cause a "nonce mismatch" panic. +// Steps: +// 1. Save the current block which will serve as parent for the fork. +// 2. Send a transaction. +// 3. Check that the TX is included in block 1. +// 4. Fork by using the parent block as ancestor. +// 5. Mine a block, Re-send the transaction and mine another one. +// 6. Check that the TX is now included in block 2. +func TestForkResendTx(t *testing.T) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + // 1. + parent := sim.blockchain.CurrentBlock() + // 2. + head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + + _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey) + sim.SendTransaction(context.Background(), tx) + sim.Commit() + // 3. + receipt, _ := sim.TransactionReceipt(context.Background(), tx.Hash()) + if h := receipt.BlockNumber.Uint64(); h != 1 { + t.Errorf("TX included in wrong block: %d", h) + } + // 4. + if err := sim.Fork(context.Background(), parent.Hash()); err != nil { + t.Errorf("forking: %v", err) + } + // 5. + sim.Commit() + if err := sim.SendTransaction(context.Background(), tx); err != nil { + t.Errorf("sending transaction: %v", err) + } + sim.Commit() + // 6. + receipt, _ = sim.TransactionReceipt(context.Background(), tx.Hash()) + if h := receipt.BlockNumber.Uint64(); h != 2 { + t.Errorf("TX included in wrong block: %d", h) + } +} + +func TestCommitReturnValue(t *testing.T) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + startBlockHeight := sim.blockchain.CurrentBlock().NumberU64() + + // Test if Commit returns the correct block hash + h1 := sim.Commit() + if h1 != sim.blockchain.CurrentBlock().Hash() { + t.Error("Commit did not return the hash of the last block.") + } + + // Create a block in the original chain (containing a transaction to force different block hashes) + head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey) + sim.SendTransaction(context.Background(), tx) + h2 := sim.Commit() + + // Create another block in the original chain + sim.Commit() + + // Fork at the first bock + if err := sim.Fork(context.Background(), h1); err != nil { + t.Errorf("forking: %v", err) + } + + // Test if Commit returns the correct block hash after the reorg + h2fork := sim.Commit() + if h2 == h2fork { + t.Error("The block in the fork and the original block are the same block!") + } + if sim.blockchain.GetHeader(h2fork, startBlockHeight+2) == nil { + t.Error("Could not retrieve the just created block (side-chain)") + } +} + +// TestAdjustTimeAfterFork ensures that after a fork, AdjustTime uses the pending fork +// block's parent rather than the canonical head's parent. +func TestAdjustTimeAfterFork(t *testing.T) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + sim.Commit() // h1 + h1 := sim.blockchain.CurrentHeader().Hash() + sim.Commit() // h2 + sim.Fork(context.Background(), h1) + sim.AdjustTime(1 * time.Second) + sim.Commit() + + head := sim.blockchain.CurrentHeader() + if head.Number == common.Big2 && head.ParentHash != h1 { + t.Errorf("failed to build block on fork") + } +} diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 274f6e4d..354632b2 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -21,6 +21,8 @@ import ( "errors" "fmt" "math/big" + "strings" + "sync" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -76,6 +78,29 @@ type WatchOpts struct { Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) } +// MetaData collects all metadata for a bound contract. +type MetaData struct { + mu sync.Mutex + Sigs map[string]string + Bin string + ABI string + ab *abi.ABI +} + +func (m *MetaData) GetAbi() (*abi.ABI, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.ab != nil { + return m.ab, nil + } + if parsed, err := abi.JSON(strings.NewReader(m.ABI)); err != nil { + return nil, err + } else { + m.ab = &parsed + } + return m.ab, nil +} + // BoundContract is the base wrapper object that reflects a contract on the // Ethereum network. It contains a collection of methods that are used by the // higher level contract bindings to operate. diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index f07cfac6..c3743a37 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -22,7 +22,6 @@ package bind import ( "bytes" - "errors" "fmt" "go/format" "regexp" @@ -37,13 +36,48 @@ import ( // Lang is a target programming language selector to generate bindings for. type Lang int +var Zion bool + const ( LangGo Lang = iota - LangJava - LangObjC ) -var Zion bool +func isKeyWord(arg string) bool { + switch arg { + case "break": + case "case": + case "chan": + case "const": + case "continue": + case "default": + case "defer": + case "else": + case "fallthrough": + case "for": + case "func": + case "go": + case "goto": + case "if": + case "import": + case "interface": + case "iota": + case "map": + case "make": + case "new": + case "package": + case "range": + case "return": + case "select": + case "struct": + case "switch": + case "type": + case "var": + default: + return false + } + + return true +} // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant // to be used as is in client code, but rather as an intermediate struct which @@ -90,10 +124,18 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] transactIdentifiers = make(map[string]bool) eventIdentifiers = make(map[string]bool) ) + + for _, input := range evmABI.Constructor.Inputs { + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + for _, original := range evmABI.Methods { // Normalize the method for capital cases and non-anonymous inputs/outputs normalized := original normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + // Ensure there is no duplicated identifier var identifiers = callIdentifiers if !original.IsConstant() { @@ -103,11 +145,12 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) } identifiers[normalizedName] = true + normalized.Name = normalizedName normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { - if input.Name == "" { + if input.Name == "" || isKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } if hasStruct(input.Type) { @@ -140,24 +183,33 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalized := original // Ensure there is no duplicated identifier - var normalizedName string + originalName := original.Name if Zion { - normalizedName = methodNormalizer[lang](alias(aliases, strings.TrimPrefix(original.Name, "evt"))) - } else { - normalizedName = methodNormalizer[lang](alias(aliases, original.Name)) - } + originalName = strings.TrimPrefix(original.Name, "evt") + } + normalizedName := methodNormalizer[lang](alias(aliases, originalName)) if eventIdentifiers[normalizedName] { return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) } eventIdentifiers[normalizedName] = true normalized.Name = normalizedName + used := make(map[string]bool) normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { - if input.Name == "" { + if input.Name == "" || isKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } + // Event is a bit special, we need to define event struct in binding, + // ensure there is no camel-case-style name conflict. + for index := 0; ; index++ { + if !used[capitalise(normalized.Inputs[j].Name)] { + used[capitalise(normalized.Inputs[j].Name)] = true + break + } + normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index) + } if hasStruct(input.Type) { bindStructType[lang](input.Type, structs) } @@ -172,14 +224,9 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] if evmABI.HasReceive() { receive = &tmplMethod{Original: evmABI.Receive} } - // There is no easy way to pass arbitrary java objects to the Go side. - if len(structs) > 0 && lang == LangJava { - return "", errors.New("java binding for tuple arguments is not supported yet") - } - contracts[types[i]] = &tmplContract{ Type: capitalise(types[i]), - InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), + InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), Constructor: evmABI.Constructor, Calls: calls, @@ -220,7 +267,6 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] Contracts: contracts, Libraries: libs, Structs: structs, - Zion: Zion, } buffer := new(bytes.Buffer) @@ -250,8 +296,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] // bindType is a set of type binders that convert Solidity types to some supported // programming language types. var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ - LangGo: bindTypeGo, - LangJava: bindTypeJava, + LangGo: bindTypeGo, } // bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. @@ -294,86 +339,10 @@ func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { } } -// 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: - return "Address" - case abi.IntTy, abi.UintTy: - // Note that uint and int (without digits) are also matched, - // these are size 256, and will translate to BigInt (the default). - parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) - if len(parts) != 3 { - return kind.String() - } - // All unsigned integers should be translated to BigInt since gomobile doesn't - // support them. - if parts[1] == "u" { - return "BigInt" - } - - namedSize := map[string]string{ - "8": "byte", - "16": "short", - "32": "int", - "64": "long", - }[parts[2]] - - // default to BigInt - if namedSize == "" { - namedSize = "BigInt" - } - return namedSize - case abi.FixedBytesTy, abi.BytesTy: - return "byte[]" - case abi.BoolTy: - return "boolean" - case abi.StringTy: - return "String" - case abi.FunctionTy: - return "byte[24]" - default: - return kind.String() - } -} - -// pluralizeJavaType explicitly converts multidimensional types to predefined -// types in go side. -func pluralizeJavaType(typ string) string { - switch typ { - case "boolean": - return "Bools" - case "String": - return "Strings" - case "Address": - return "Addresses" - case "byte[]": - return "Binaries" - case "BigInt": - return "BigInts" - } - return typ + "[]" -} - -// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping -// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly -// mapped will use an upscaled type (e.g. BigDecimal). -func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { - switch kind.T { - case abi.TupleTy: - return structs[kind.TupleRawName+kind.String()].Name - case abi.ArrayTy, abi.SliceTy: - return pluralizeJavaType(bindTypeJava(*kind.Elem, structs)) - default: - return bindBasicTypeJava(kind) - } -} - // bindTopicType is a set of type binders that convert Solidity types to some // supported programming language topic types. var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ - LangGo: bindTopicTypeGo, - LangJava: bindTopicTypeJava, + LangGo: bindTopicTypeGo, } // bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same @@ -393,28 +362,10 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { return bound } -// bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same -// 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) - - // todo(rjl493456442) according solidity documentation, indexed event - // 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 - // array(both fixed-size and dynamic-size) and struct. - if bound == "String" || bound == "byte[]" { - bound = "Hash" - } - return bound -} - // bindStructType is a set of type binders that convert Solidity tuple types to some supported // programming language struct definition. var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ - LangGo: bindStructTypeGo, - LangJava: bindStructTypeJava, + LangGo: bindStructTypeGo, } // bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping @@ -433,15 +384,22 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { if s, exist := structs[id]; exist { return s.Name } - var fields []*tmplField + var ( + names = make(map[string]bool) + fields []*tmplField + ) for i, elem := range kind.TupleElems { - field := bindStructTypeGo(*elem, structs) - fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem}) + name := capitalise(kind.TupleRawNames[i]) + name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] }) + names[name] = true + fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem}) } name := kind.TupleRawName if name == "" { name = fmt.Sprintf("Struct%d", len(structs)) } + name = capitalise(name) + structs[id] = &tmplStruct{ Name: name, Fields: fields, @@ -456,74 +414,10 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { } } -// bindStructTypeJava converts a Solidity tuple type to a Java one and records the mapping -// in the given map. -// Notably, this function will resolve and record nested struct recursively. -func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { - switch kind.T { - case abi.TupleTy: - // 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 - // compatibility, we concat these two together so that if kind.TupleRawName - // is not empty, it can have unique id. - id := kind.TupleRawName + kind.String() - if s, exist := structs[id]; exist { - return s.Name - } - var fields []*tmplField - for i, elem := range kind.TupleElems { - field := bindStructTypeJava(*elem, structs) - fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem}) - } - name := kind.TupleRawName - if name == "" { - name = fmt.Sprintf("Class%d", len(structs)) - } - structs[id] = &tmplStruct{ - Name: name, - Fields: fields, - } - return name - case abi.ArrayTy, abi.SliceTy: - return pluralizeJavaType(bindStructTypeJava(*kind.Elem, structs)) - default: - return bindBasicTypeJava(kind) - } -} - // namedType is a set of functions that transform language specific types to // 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, -} - -// namedTypeJava converts some primitive data types to named variants that can -// be used as parts of method names. -func namedTypeJava(javaKind string, solKind abi.Type) string { - switch javaKind { - case "byte[]": - return "Binary" - case "boolean": - return "Bool" - default: - parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) - if len(parts) != 4 { - return javaKind - } - switch parts[2] { - case "8", "16", "32", "64": - if parts[3] == "" { - return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) - } - return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) - - default: - return javaKind - } - } + LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, } // alias returns an alias of the given string based on the aliasing rules @@ -538,8 +432,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 conventions. var methodNormalizer = map[Lang]func(string) string{ - LangGo: abi.ToCamelCase, - LangJava: decapitalise, + LangGo: abi.ToCamelCase, } // capitalise makes a camel-case string which starts with an upper case character. diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index d0958cb6..5f25fdb4 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -18,7 +18,6 @@ package bind import ( "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -47,7 +46,9 @@ var bindTests = []struct { `contract NilContract {}`, []string{`606060405260068060106000396000f3606060405200`}, []string{`[]`}, - `"github.com/ethereum/go-ethereum/common"`, + `"github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + `, ` if b, err := NewEmpty(common.Address{}, nil); b == nil || err != nil { t.Fatalf("combined binding (%v) nil or error (%v) not nil", b, nil) @@ -70,7 +71,9 @@ var bindTests = []struct { `https://ethereum.org/token`, []string{`60606040526040516107fd3803806107fd83398101604052805160805160a05160c051929391820192909101600160a060020a0333166000908152600360209081526040822086905581548551838052601f6002600019610100600186161502019093169290920482018390047f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56390810193919290918801908390106100e857805160ff19168380011785555b506101189291505b8082111561017157600081556001016100b4565b50506002805460ff19168317905550505050610658806101a56000396000f35b828001600101855582156100ac579182015b828111156100ac5782518260005055916020019190600101906100fa565b50508060016000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017557805160ff19168380011785555b506100c89291506100b4565b5090565b82800160010185558215610165579182015b8281111561016557825182600050559160200191906001019061018756606060405236156100775760e060020a600035046306fdde03811461007f57806323b872dd146100dc578063313ce5671461010e57806370a082311461011a57806395d89b4114610132578063a9059cbb1461018e578063cae9ca51146101bd578063dc3080f21461031c578063dd62ed3e14610341575b610365610002565b61036760008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156104eb5780601f106104c0576101008083540402835291602001916104eb565b6103d5600435602435604435600160a060020a038316600090815260036020526040812054829010156104f357610002565b6103e760025460ff1681565b6103d560043560036020526000908152604090205481565b610367600180546020600282841615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156104eb5780601f106104c0576101008083540402835291602001916104eb565b610365600435602435600160a060020a033316600090815260036020526040902054819010156103f157610002565b60806020604435600481810135601f8101849004909302840160405260608381526103d5948235946024803595606494939101919081908382808284375094965050505050505060006000836004600050600033600160a060020a03168152602001908152602001600020600050600087600160a060020a031681526020019081526020016000206000508190555084905080600160a060020a0316638f4ffcb1338630876040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156102f25780820380516001836020036101000a031916815260200191505b50955050505050506000604051808303816000876161da5a03f11561000257505050509392505050565b6005602090815260043560009081526040808220909252602435815220546103d59081565b60046020818152903560009081526040808220909252602435815220546103d59081565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156103c75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f35b6060908152602090f35b600160a060020a03821660009081526040902054808201101561041357610002565b806003600050600033600160a060020a03168152602001908152602001600020600082828250540392505081905550806003600050600084600160a060020a0316815260200190815260200160002060008282825054019250508190555081600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b820191906000526020600020905b8154815290600101906020018083116104ce57829003601f168201915b505050505081565b600160a060020a03831681526040812054808301101561051257610002565b600160a060020a0380851680835260046020908152604080852033949094168086529382528085205492855260058252808520938552929052908220548301111561055c57610002565b816003600050600086600160a060020a03168152602001908152602001600020600082828250540392505081905550816003600050600085600160a060020a03168152602001908152602001600020600082828250540192505081905550816005600050600086600160a060020a03168152602001908152602001600020600050600033600160a060020a0316815260200190815260200160002060008282825054019250508190555082600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3939250505056`}, []string{`[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"spentAllowance","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"},{"name":"tokenName","type":"string"},{"name":"decimalUnits","type":"uint8"},{"name":"tokenSymbol","type":"string"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]`}, - `"github.com/ethereum/go-ethereum/common"`, + `"github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + `, ` if b, err := NewToken(common.Address{}, nil); b == nil || err != nil { t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) @@ -86,7 +89,9 @@ var bindTests = []struct { `https://ethereum.org/crowdsale`, []string{`606060408190526007805460ff1916905560a0806105a883396101006040529051608051915160c05160e05160008054600160a060020a03199081169095178155670de0b6b3a7640000958602600155603c9093024201600355930260045560058054909216909217905561052f90819061007990396000f36060604052361561006c5760e060020a600035046301cb3b20811461008257806329dcb0cf1461014457806338af3eed1461014d5780636e66f6e91461015f5780637a3a0e84146101715780637b3e5e7b1461017a578063a035b1fe14610183578063dc0d3dff1461018c575b61020060075460009060ff161561032357610002565b61020060035460009042106103205760025460015490106103cb576002548154600160a060020a0316908290606082818181858883f150915460025460408051600160a060020a039390931683526020830191909152818101869052517fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf6945090819003909201919050a15b60405160008054600160a060020a039081169230909116319082818181858883f150506007805460ff1916600117905550505050565b6103a160035481565b6103ab600054600160a060020a031681565b6103ab600554600160a060020a031681565b6103a160015481565b6103a160025481565b6103a160045481565b6103be60043560068054829081101561000257506000526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f8101547ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d409190910154600160a060020a03919091169082565b005b505050815481101561000257906000526020600020906002020160005060008201518160000160006101000a815481600160a060020a030219169083021790555060208201518160010160005055905050806002600082828250540192505081905550600560009054906101000a9004600160a060020a0316600160a060020a031663a9059cbb3360046000505484046040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506000604051808303816000876161da5a03f11561000257505060408051600160a060020a03331681526020810184905260018183015290517fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf692509081900360600190a15b50565b5060a0604052336060908152346080819052600680546001810180835592939282908280158290116102025760020281600202836000526020600020918201910161020291905b8082111561039d57805473ffffffffffffffffffffffffffffffffffffffff19168155600060019190910190815561036a565b5090565b6060908152602090f35b600160a060020a03166060908152602090f35b6060918252608052604090f35b5b60065481101561010e576006805482908110156100025760009182526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f0190600680549254600160a060020a0316928490811015610002576002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d40015460405190915082818181858883f19350505050507fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf660066000508281548110156100025760008290526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01548154600160a060020a039190911691908490811015610002576002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d40015460408051600160a060020a0394909416845260208401919091526000838201525191829003606001919050a16001016103cc56`}, []string{`[{"constant":false,"inputs":[],"name":"checkGoalReached","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"deadline","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"beneficiary","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"tokenReward","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"fundingGoal","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"amountRaised","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"price","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"funders","outputs":[{"name":"addr","type":"address"},{"name":"amount","type":"uint256"}],"type":"function"},{"inputs":[{"name":"ifSuccessfulSendTo","type":"address"},{"name":"fundingGoalInEthers","type":"uint256"},{"name":"durationInMinutes","type":"uint256"},{"name":"etherCostOfEachToken","type":"uint256"},{"name":"addressOfTokenUsedAsReward","type":"address"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"backer","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"isContribution","type":"bool"}],"name":"FundTransfer","type":"event"}]`}, - `"github.com/ethereum/go-ethereum/common"`, + `"github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + `, ` if b, err := NewCrowdsale(common.Address{}, nil); b == nil || err != nil { t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) @@ -102,7 +107,9 @@ var bindTests = []struct { `https://ethereum.org/dao`, []string{`606060405260405160808061145f833960e06040529051905160a05160c05160008054600160a060020a03191633179055600184815560028490556003839055600780549182018082558280158290116100b8576003028160030283600052602060002091820191016100b891906101c8565b50506060919091015160029190910155600160a060020a0381166000146100a65760008054600160a060020a031916821790555b505050506111f18061026e6000396000f35b505060408051608081018252600080825260208281018290528351908101845281815292820192909252426060820152600780549194509250811015610002579081527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6889050815181546020848101517401000000000000000000000000000000000000000002600160a060020a03199290921690921760a060020a60ff021916178255604083015180516001848101805460008281528690209195600293821615610100026000190190911692909204601f9081018390048201949192919091019083901061023e57805160ff19168380011785555b50610072929150610226565b5050600060028201556001015b8082111561023a578054600160a860020a031916815560018181018054600080835592600290821615610100026000190190911604601f81901061020c57506101bb565b601f0160209004906000526020600020908101906101bb91905b8082111561023a5760008155600101610226565b5090565b828001600101855582156101af579182015b828111156101af57825182600050559160200191906001019061025056606060405236156100b95760e060020a6000350463013cf08b81146100bb578063237e9492146101285780633910682114610281578063400e3949146102995780635daf08ca146102a257806369bd34361461032f5780638160f0b5146103385780638da5cb5b146103415780639644fcbd14610353578063aa02a90f146103be578063b1050da5146103c7578063bcca1fd3146104b5578063d3c0715b146104dc578063eceb29451461058d578063f2fde38b1461067b575b005b61069c6004356004805482908110156100025790600052602060002090600a02016000506005810154815460018301546003840154600485015460068601546007870154600160a060020a03959095169750929560020194919360ff828116946101009093041692919089565b60408051602060248035600481810135601f81018590048502860185019096528585526107759581359591946044949293909201918190840183828082843750949650505050505050600060006004600050848154811015610002575090527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19e600a8402908101547f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b909101904210806101e65750600481015460ff165b8061026757508060000160009054906101000a9004600160a060020a03168160010160005054846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020816007016000505414155b8061027757506001546005820154105b1561109257610002565b61077560043560066020526000908152604090205481565b61077560055481565b61078760043560078054829081101561000257506000526003026000805160206111d18339815191528101547fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68a820154600160a060020a0382169260a060020a90920460ff16917fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c689019084565b61077560025481565b61077560015481565b610830600054600160a060020a031681565b604080516020604435600481810135601f81018490048402850184019095528484526100b9948135946024803595939460649492939101918190840183828082843750949650505050505050600080548190600160a060020a03908116339091161461084d57610002565b61077560035481565b604080516020604435600481810135601f8101849004840285018401909552848452610775948135946024803595939460649492939101918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024909101945090925082915084018382808284375094965050505050505033600160a060020a031660009081526006602052604081205481908114806104ab5750604081205460078054909190811015610002579082526003026000805160206111d1833981519152015460a060020a900460ff16155b15610ce557610002565b6100b960043560243560443560005433600160a060020a03908116911614610b1857610002565b604080516020604435600481810135601f810184900484028501840190955284845261077594813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a031660009081526006602052604081205481908114806105835750604081205460078054909190811015610002579082526003026000805160206111d18339815191520181505460a060020a900460ff16155b15610f1d57610002565b604080516020606435600481810135601f81018490048402850184019095528484526107759481359460248035956044359560849492019190819084018382808284375094965050505050505060006000600460005086815481101561000257908252600a027f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b01815090508484846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160070160005054149150610cdc565b6100b960043560005433600160a060020a03908116911614610f0857610002565b604051808a600160a060020a031681526020018981526020018060200188815260200187815260200186815260200185815260200184815260200183815260200182810382528981815460018160011615610100020316600290048152602001915080546001816001161561010002031660029004801561075e5780601f106107335761010080835404028352916020019161075e565b820191906000526020600020905b81548152906001019060200180831161074157829003601f168201915b50509a505050505050505050505060405180910390f35b60408051918252519081900360200190f35b60408051600160a060020a038616815260208101859052606081018390526080918101828152845460026001821615610100026000190190911604928201839052909160a08301908590801561081e5780601f106107f35761010080835404028352916020019161081e565b820191906000526020600020905b81548152906001019060200180831161080157829003601f168201915b50509550505050505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b600160a060020a03851660009081526006602052604081205414156108a957604060002060078054918290556001820180825582801582901161095c5760030281600302836000526020600020918201910161095c9190610a4f565b600160a060020a03851660009081526006602052604090205460078054919350908390811015610002575060005250600381026000805160206111d183398151915201805474ff0000000000000000000000000000000000000000191660a060020a85021781555b60408051600160a060020a03871681526020810186905281517f27b022af4a8347100c7a041ce5ccf8e14d644ff05de696315196faae8cd50c9b929181900390910190a15050505050565b505050915081506080604051908101604052808681526020018581526020018481526020014281526020015060076000508381548110156100025790600052602060002090600302016000508151815460208481015160a060020a02600160a060020a03199290921690921774ff00000000000000000000000000000000000000001916178255604083015180516001848101805460008281528690209195600293821615610100026000190190911692909204601f90810183900482019491929190910190839010610ad357805160ff19168380011785555b50610b03929150610abb565b5050600060028201556001015b80821115610acf57805474ffffffffffffffffffffffffffffffffffffffffff1916815560018181018054600080835592600290821615610100026000190190911604601f819010610aa15750610a42565b601f016020900490600052602060002090810190610a4291905b80821115610acf5760008155600101610abb565b5090565b82800160010185558215610a36579182015b82811115610a36578251826000505591602001919060010190610ae5565b50506060919091015160029190910155610911565b600183905560028290556003819055604080518481526020810184905280820183905290517fa439d3fa452be5e0e1e24a8145e715f4fd8b9c08c96a42fd82a855a85e5d57de9181900360600190a1505050565b50508585846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160070160005081905550600260005054603c024201816003016000508190555060008160040160006101000a81548160ff0219169083021790555060008160040160016101000a81548160ff02191690830217905550600081600501600050819055507f646fec02522b41e7125cfc859a64fd4f4cefd5dc3b6237ca0abe251ded1fa881828787876040518085815260200184600160a060020a03168152602001838152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015610cc45780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a1600182016005555b50949350505050565b6004805460018101808355909190828015829011610d1c57600a0281600a028360005260206000209182019101610d1c9190610db8565b505060048054929450918491508110156100025790600052602060002090600a02016000508054600160a060020a031916871781556001818101879055855160028381018054600082815260209081902096975091959481161561010002600019011691909104601f90810182900484019391890190839010610ed857805160ff19168380011785555b50610b6c929150610abb565b50506001015b80821115610acf578054600160a060020a03191681556000600182810182905560028381018054848255909281161561010002600019011604601f819010610e9c57505b5060006003830181905560048301805461ffff191690556005830181905560068301819055600783018190556008830180548282559082526020909120610db2916002028101905b80821115610acf57805474ffffffffffffffffffffffffffffffffffffffffff1916815560018181018054600080835592600290821615610100026000190190911604601f819010610eba57505b5050600101610e44565b601f016020900490600052602060002090810190610dfc9190610abb565b601f016020900490600052602060002090810190610e929190610abb565b82800160010185558215610da6579182015b82811115610da6578251826000505591602001919060010190610eea565b60008054600160a060020a0319168217905550565b600480548690811015610002576000918252600a027f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b01905033600160a060020a0316600090815260098201602052604090205490915060ff1660011415610f8457610002565b33600160a060020a031660009081526009820160205260409020805460ff1916600190811790915560058201805490910190558315610fcd576006810180546001019055610fda565b6006810180546000190190555b7fc34f869b7ff431b034b7b9aea9822dac189a685e0b015c7d1be3add3f89128e8858533866040518085815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561107a5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a1509392505050565b6006810154600354901315611158578060000160009054906101000a9004600160a060020a0316600160a060020a03168160010160005054670de0b6b3a76400000284604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156111225780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f15050505060048101805460ff191660011761ff00191661010017905561116d565b60048101805460ff191660011761ff00191690555b60068101546005820154600483015460408051888152602081019490945283810192909252610100900460ff166060830152517fd220b7272a8b6d0d7d6bcdace67b936a8f175e6d5c1b3ee438b72256b32ab3af9181900360800190a1509291505056a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688`}, []string{`[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"proposals","outputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"},{"name":"description","type":"string"},{"name":"votingDeadline","type":"uint256"},{"name":"executed","type":"bool"},{"name":"proposalPassed","type":"bool"},{"name":"numberOfVotes","type":"uint256"},{"name":"currentResult","type":"int256"},{"name":"proposalHash","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"transactionBytecode","type":"bytes"}],"name":"executeProposal","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"memberId","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"numProposals","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"members","outputs":[{"name":"member","type":"address"},{"name":"canVote","type":"bool"},{"name":"name","type":"string"},{"name":"memberSince","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"debatingPeriodInMinutes","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"minimumQuorum","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"targetMember","type":"address"},{"name":"canVote","type":"bool"},{"name":"memberName","type":"string"}],"name":"changeMembership","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"majorityMargin","outputs":[{"name":"","type":"int256"}],"type":"function"},{"constant":false,"inputs":[{"name":"beneficiary","type":"address"},{"name":"etherAmount","type":"uint256"},{"name":"JobDescription","type":"string"},{"name":"transactionBytecode","type":"bytes"}],"name":"newProposal","outputs":[{"name":"proposalID","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"minimumQuorumForProposals","type":"uint256"},{"name":"minutesForDebate","type":"uint256"},{"name":"marginOfVotesForMajority","type":"int256"}],"name":"changeVotingRules","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"supportsProposal","type":"bool"},{"name":"justificationText","type":"string"}],"name":"vote","outputs":[{"name":"voteID","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"beneficiary","type":"address"},{"name":"etherAmount","type":"uint256"},{"name":"transactionBytecode","type":"bytes"}],"name":"checkProposalCode","outputs":[{"name":"codeChecksOut","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"type":"function"},{"inputs":[{"name":"minimumQuorumForProposals","type":"uint256"},{"name":"minutesForDebate","type":"uint256"},{"name":"marginOfVotesForMajority","type":"int256"},{"name":"congressLeader","type":"address"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"recipient","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"description","type":"string"}],"name":"ProposalAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"position","type":"bool"},{"indexed":false,"name":"voter","type":"address"},{"indexed":false,"name":"justification","type":"string"}],"name":"Voted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"result","type":"int256"},{"indexed":false,"name":"quorum","type":"uint256"},{"indexed":false,"name":"active","type":"bool"}],"name":"ProposalTallied","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"member","type":"address"},{"indexed":false,"name":"isMember","type":"bool"}],"name":"MembershipChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"minimumQuorum","type":"uint256"},{"indexed":false,"name":"debatingPeriodInMinutes","type":"uint256"},{"indexed":false,"name":"majorityMargin","type":"int256"}],"name":"ChangeOfRules","type":"event"}]`}, - `"github.com/ethereum/go-ethereum/common"`, + `"github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + `, ` if b, err := NewDAO(common.Address{}, nil); b == nil || err != nil { t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) @@ -130,6 +137,7 @@ var bindTests = []struct { "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" `, `if b, err := NewInputChecker(common.Address{}, nil); b == nil || err != nil { t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) @@ -168,6 +176,7 @@ var bindTests = []struct { "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" `, `if b, err := NewOutputChecker(common.Address{}, nil); b == nil || err != nil { t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) @@ -209,6 +218,7 @@ var bindTests = []struct { "reflect" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" `, `if e, err := NewEventChecker(common.Address{}, nil); e == nil || err != nil { t.Fatalf("binding (%v) nil or error (%v) not nil", e, nil) @@ -298,7 +308,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy an interaction tester contract and call a transaction on it @@ -353,7 +363,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a tuple tester contract and execute a structured call on it @@ -399,7 +409,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a tuple tester contract and execute a structured call on it @@ -457,7 +467,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a slice tester contract and execute a n array call on it @@ -505,7 +515,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a default method invoker contract and execute its default method @@ -571,7 +581,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a structs method invoker contract and execute its default method @@ -703,7 +713,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a funky gas pattern contract @@ -753,7 +763,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a sender tester contract and execute a structured call on it @@ -828,7 +838,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a underscorer tester contract and execute a structured call on it @@ -922,7 +932,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy an eventer contract @@ -1112,7 +1122,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() //deploy the test contract @@ -1192,6 +1202,8 @@ var bindTests = []struct { } ]`}, ` "strings" + + "github.com/ethereum/go-ethereum/core" `, ` if strings.Compare("test(function)", CallbackParamFuncSigs["d7a5aba2"]) != 0 { @@ -1247,7 +1259,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() _, _, contract, err := DeployTuple(auth, sim) @@ -1389,7 +1401,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() //deploy the test contract @@ -1454,7 +1466,7 @@ var bindTests = []struct { // Initialize test accounts key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // deploy the test contract @@ -1544,7 +1556,7 @@ var bindTests = []struct { addr := crypto.PubkeyToAddress(key.PublicKey) // Deploy registrar contract - sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) @@ -1606,7 +1618,7 @@ var bindTests = []struct { addr := crypto.PubkeyToAddress(key.PublicKey) // Deploy registrar contract - sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) @@ -1668,7 +1680,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) defer sim.Close() // Deploy a tester contract and execute a structured call on it @@ -1728,7 +1740,7 @@ var bindTests = []struct { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) - sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 1000000) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000) defer sim.Close() opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) @@ -1785,6 +1797,261 @@ var bindTests = []struct { nil, nil, }, + // Test resolving single struct argument + { + `NewSingleStructArgument`, + ` + pragma solidity ^0.8.0; + + contract NewSingleStructArgument { + struct MyStruct{ + uint256 a; + uint256 b; + } + event StructEvent(MyStruct s); + function TestEvent() public { + emit StructEvent(MyStruct({a: 1, b: 2})); + } + } + `, + []string{"608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806324ec1d3f14602d575b600080fd5b60336035565b005b7fb4b2ff75e30cb4317eaae16dd8a187dd89978df17565104caa6c2797caae27d460405180604001604052806001815260200160028152506040516078919060ba565b60405180910390a1565b6040820160008201516096600085018260ad565b50602082015160a7602085018260ad565b50505050565b60b48160d3565b82525050565b600060408201905060cd60008301846082565b92915050565b600081905091905056fea26469706673582212208823628796125bf9941ce4eda18da1be3cf2931b231708ab848e1bd7151c0c9a64736f6c63430008070033"}, + []string{`[{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"indexed":false,"internalType":"struct Test.MyStruct","name":"s","type":"tuple"}],"name":"StructEvent","type":"event"},{"inputs":[],"name":"TestEvent","outputs":[],"stateMutability":"nonpayable","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" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + defer sim.Close() + + _, _, d, err := DeployNewSingleStructArgument(user, sim) + if err != nil { + t.Fatalf("Failed to deploy contract %v", err) + } + sim.Commit() + + _, err = d.TestEvent(user) + if err != nil { + t.Fatalf("Failed to call contract %v", err) + } + sim.Commit() + + it, err := d.FilterStructEvent(nil) + if err != nil { + t.Fatalf("Failed to filter contract event %v", err) + } + var count int + for it.Next() { + if it.Event.S.A.Cmp(big.NewInt(1)) != 0 { + t.Fatal("Unexpected contract event") + } + if it.Event.S.B.Cmp(big.NewInt(2)) != 0 { + t.Fatal("Unexpected contract event") + } + count += 1 + } + if count != 1 { + t.Fatal("Unexpected contract event number") + } + `, + nil, + nil, + nil, + nil, + }, + // Test errors introduced in v0.8.4 + { + `NewErrors`, + ` + pragma solidity >0.8.4; + + contract NewErrors { + error MyError(uint256); + error MyError1(uint256); + error MyError2(uint256, uint256); + error MyError3(uint256 a, uint256 b, uint256 c); + function Error() public pure { + revert MyError3(1,2,3); + } + } + `, + []string{"0x6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063726c638214602d575b600080fd5b60336035565b005b60405163024876cd60e61b815260016004820152600260248201526003604482015260640160405180910390fdfea264697066735822122093f786a1bc60216540cd999fbb4a6109e0fef20abcff6e9107fb2817ca968f3c64736f6c63430008070033"}, + []string{`[{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError1","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError2","type":"error"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"name":"MyError3","type":"error"},{"inputs":[],"name":"Error","outputs":[],"stateMutability":"pure","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" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + defer sim.Close() + + _, tx, contract, err := DeployNewErrors(user, sim) + if err != nil { + t.Fatal(err) + } + sim.Commit() + _, err = bind.WaitDeployed(nil, sim, tx) + if err != nil { + t.Error(err) + } + if err := contract.Error(new(bind.CallOpts)); err == nil { + t.Fatalf("expected contract to throw error") + } + // TODO (MariusVanDerWijden unpack error using abigen + // once that is implemented + `, + nil, + nil, + nil, + nil, + }, + { + name: `ConstructorWithStructParam`, + contract: ` + pragma solidity >=0.8.0 <0.9.0; + + contract ConstructorWithStructParam { + struct StructType { + uint256 field; + } + + constructor(StructType memory st) {} + } + `, + bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`}, + abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`}, + imports: ` + "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" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + tester: ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + defer sim.Close() + + _, tx, _, err := DeployConstructorWithStructParam(user, sim, ConstructorWithStructParamStructType{Field: big.NewInt(42)}) + if err != nil { + t.Fatalf("DeployConstructorWithStructParam() got err %v; want nil err", err) + } + sim.Commit() + + if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { + t.Logf("Deployment tx: %+v", tx) + t.Errorf("bind.WaitDeployed(nil, %T, ) got err %v; want nil err", sim, err) + } + `, + }, + { + name: `NameConflict`, + contract: ` + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.4.22 <0.9.0; + contract oracle { + struct request { + bytes data; + bytes _data; + } + event log (int msg, int _msg); + function addRequest(request memory req) public pure {} + function getRequest() pure public returns (request memory) { + return request("", ""); + } + } + `, + bytecode: []string{"0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033"}, + abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`}, + imports: ` + "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" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + tester: ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + defer sim.Close() + + _, tx, _, err := DeployNameConflict(user, sim) + if err != nil { + t.Fatalf("DeployNameConflict() got err %v; want nil err", err) + } + sim.Commit() + + if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { + t.Logf("Deployment tx: %+v", tx) + t.Errorf("bind.WaitDeployed(nil, %T, ) got err %v; want nil err", sim, err) + } + `, + }, + { + name: "RangeKeyword", + contract: ` + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.4.22 <0.9.0; + contract keywordcontract { + function functionWithKeywordParameter(range uint256) public pure {} + } + `, + bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"}, + abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`}, + imports: ` + "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" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + tester: ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + _, tx, _, err := DeployRangeKeyword(user, sim) + if err != nil { + t.Fatalf("error deploying contract: %v", err) + } + sim.Commit() + + if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { + t.Errorf("error deploying the contract: %v", err) + } + `, + }, } // Tests that packages generated by the binder can be successfully compiled and @@ -1796,34 +2063,31 @@ 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("", "binding-test") - if err != nil { - t.Fatalf("failed to create temporary workspace: %v", err) - } - //defer os.RemoveAll(ws) + ws := t.TempDir() pkg := filepath.Join(ws, "bindtest") - if err = os.MkdirAll(pkg, 0700); err != nil { + if err := os.MkdirAll(pkg, 0700); err != nil { t.Fatalf("failed to create package: %v", err) } // Generate the test suite for all the contracts for i, tt := range bindTests { - var types []string - if tt.types != nil { - types = tt.types - } else { - types = []string{tt.name} - } - // Generate the binding and create a Go source file in the workspace - bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases) - if err != nil { - t.Fatalf("test %d: failed to generate binding: %v", i, err) - } - if err = ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0600); err != nil { - t.Fatalf("test %d: failed to write binding: %v", i, err) - } - // Generate the test file with the injected test code - code := fmt.Sprintf(` + t.Run(tt.name, func(t *testing.T) { + var types []string + if tt.types != nil { + types = tt.types + } else { + types = []string{tt.name} + } + // Generate the binding and create a Go source file in the workspace + bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases) + if err != nil { + t.Fatalf("test %d: failed to generate binding: %v", i, err) + } + if err = os.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + // Generate the test file with the injected test code + code := fmt.Sprintf(` package bindtest import ( @@ -1831,13 +2095,19 @@ func TestGolangBindings(t *testing.T) { %s ) + func init() { + core.RegGenesis = nil + core.CheckAllocWithTotalSupply = false + } + func Test%s(t *testing.T) { %s } `, tt.imports, tt.name, tt.tester) - if err := ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0600); err != nil { - t.Fatalf("test %d: failed to write tests: %v", i, err) - } + if err := os.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0600); err != nil { + t.Fatalf("test %d: failed to write tests: %v", i, err) + } + }) } // Convert the package to go modules and use the current source for go-ethereum moder := exec.Command(gocmd, "mod", "init", "bindtest") @@ -1863,408 +2133,3 @@ func TestGolangBindings(t *testing.T) { t.Fatalf("failed to run binding test: %v\n%s", err, out) } } - -// Tests that java binding generated by the binder is exactly matched. -func TestJavaBindings(t *testing.T) { - var cases = []struct { - name string - contract string - abi string - bytecode string - expected string - }{ - { - "test", - ` - pragma experimental ABIEncoderV2; - pragma solidity ^0.5.2; - - contract test { - function setAddress(address a) public returns(address){} - function setAddressList(address[] memory a_l) public returns(address[] memory){} - function setAddressArray(address[2] memory a_a) public returns(address[2] memory){} - - function setUint8(uint8 u8) public returns(uint8){} - function setUint16(uint16 u16) public returns(uint16){} - function setUint32(uint32 u32) public returns(uint32){} - function setUint64(uint64 u64) public returns(uint64){} - function setUint256(uint256 u256) public returns(uint256){} - function setUint256List(uint256[] memory u256_l) public returns(uint256[] memory){} - function setUint256Array(uint256[2] memory u256_a) public returns(uint256[2] memory){} - - function setInt8(int8 i8) public returns(int8){} - function setInt16(int16 i16) public returns(int16){} - function setInt32(int32 i32) public returns(int32){} - function setInt64(int64 i64) public returns(int64){} - function setInt256(int256 i256) public returns(int256){} - function setInt256List(int256[] memory i256_l) public returns(int256[] memory){} - function setInt256Array(int256[2] memory i256_a) public returns(int256[2] memory){} - - function setBytes1(bytes1 b1) public returns(bytes1) {} - function setBytes32(bytes32 b32) public returns(bytes32) {} - function setBytes(bytes memory bs) public returns(bytes memory) {} - function setBytesList(bytes[] memory bs_l) public returns(bytes[] memory) {} - function setBytesArray(bytes[2] memory bs_a) public returns(bytes[2] memory) {} - - function setString(string memory s) public returns(string memory) {} - function setStringList(string[] memory s_l) public returns(string[] memory) {} - function setStringArray(string[2] memory s_a) public returns(string[2] memory) {} - - function setBool(bool b) public returns(bool) {} - function setBoolList(bool[] memory b_l) public returns(bool[] memory) {} - function setBoolArray(bool[2] memory b_a) public returns(bool[2] memory) {} - }`, - `[{"constant":false,"inputs":[{"name":"u16","type":"uint16"}],"name":"setUint16","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"b_a","type":"bool[2]"}],"name":"setBoolArray","outputs":[{"name":"","type":"bool[2]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"a_a","type":"address[2]"}],"name":"setAddressArray","outputs":[{"name":"","type":"address[2]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"bs_l","type":"bytes[]"}],"name":"setBytesList","outputs":[{"name":"","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"u8","type":"uint8"}],"name":"setUint8","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"u32","type":"uint32"}],"name":"setUint32","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"b","type":"bool"}],"name":"setBool","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"i256_l","type":"int256[]"}],"name":"setInt256List","outputs":[{"name":"","type":"int256[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"u256_a","type":"uint256[2]"}],"name":"setUint256Array","outputs":[{"name":"","type":"uint256[2]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"b_l","type":"bool[]"}],"name":"setBoolList","outputs":[{"name":"","type":"bool[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"bs_a","type":"bytes[2]"}],"name":"setBytesArray","outputs":[{"name":"","type":"bytes[2]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"a_l","type":"address[]"}],"name":"setAddressList","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"i256_a","type":"int256[2]"}],"name":"setInt256Array","outputs":[{"name":"","type":"int256[2]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"s_a","type":"string[2]"}],"name":"setStringArray","outputs":[{"name":"","type":"string[2]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"s","type":"string"}],"name":"setString","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"u64","type":"uint64"}],"name":"setUint64","outputs":[{"name":"","type":"uint64"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"i16","type":"int16"}],"name":"setInt16","outputs":[{"name":"","type":"int16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"i8","type":"int8"}],"name":"setInt8","outputs":[{"name":"","type":"int8"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"u256_l","type":"uint256[]"}],"name":"setUint256List","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"i256","type":"int256"}],"name":"setInt256","outputs":[{"name":"","type":"int256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"i32","type":"int32"}],"name":"setInt32","outputs":[{"name":"","type":"int32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"b32","type":"bytes32"}],"name":"setBytes32","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"s_l","type":"string[]"}],"name":"setStringList","outputs":[{"name":"","type":"string[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"u256","type":"uint256"}],"name":"setUint256","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"bs","type":"bytes"}],"name":"setBytes","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"a","type":"address"}],"name":"setAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"i64","type":"int64"}],"name":"setInt64","outputs":[{"name":"","type":"int64"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"b1","type":"bytes1"}],"name":"setBytes1","outputs":[{"name":"","type":"bytes1"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`, - `608060405234801561001057600080fd5b5061265a806100206000396000f3fe608060405234801561001057600080fd5b50600436106101e1576000357c0100000000000000000000000000000000000000000000000000000000900480637fcaf66611610116578063c2b12a73116100b4578063da359dc81161008e578063da359dc814610666578063e30081a014610696578063e673eb32146106c6578063fba1a1c3146106f6576101e1565b8063c2b12a73146105d6578063c577796114610606578063d2282dc514610636576101e1565b80639a19a953116100f05780639a19a95314610516578063a0709e1914610546578063a53b1c1e14610576578063b7d5df31146105a6576101e1565b80637fcaf66614610486578063822cba69146104b657806386114cea146104e6576101e1565b806322722302116101835780635119655d1161015d5780635119655d146103c65780635be6b37e146103f65780636aa482fc146104265780637173b69514610456576101e1565b806322722302146103365780632766a755146103665780634d5ee6da14610396576101e1565b806316c105e2116101bf57806316c105e2146102765780631774e646146102a65780631c9352e2146102d65780631e26fd3314610306576101e1565b80630477988a146101e6578063118a971814610216578063151f547114610246575b600080fd5b61020060048036036101fb9190810190611599565b610726565b60405161020d9190611f01565b60405180910390f35b610230600480360361022b919081019061118d565b61072d565b60405161023d9190611ca6565b60405180910390f35b610260600480360361025b9190810190611123565b61073a565b60405161026d9190611c69565b60405180910390f35b610290600480360361028b9190810190611238565b610747565b60405161029d9190611d05565b60405180910390f35b6102c060048036036102bb919081019061163d565b61074e565b6040516102cd9190611f6d565b60405180910390f35b6102f060048036036102eb91908101906115eb565b610755565b6040516102fd9190611f37565b60405180910390f35b610320600480360361031b91908101906113cf565b61075c565b60405161032d9190611de5565b60405180910390f35b610350600480360361034b91908101906112a2565b610763565b60405161035d9190611d42565b60405180910390f35b610380600480360361037b9190810190611365565b61076a565b60405161038d9190611da8565b60405180910390f35b6103b060048036036103ab91908101906111b6565b610777565b6040516103bd9190611cc1565b60405180910390f35b6103e060048036036103db91908101906111f7565b61077e565b6040516103ed9190611ce3565b60405180910390f35b610410600480360361040b919081019061114c565b61078b565b60405161041d9190611c84565b60405180910390f35b610440600480360361043b9190810190611279565b610792565b60405161044d9190611d27565b60405180910390f35b610470600480360361046b91908101906112e3565b61079f565b60405161047d9190611d64565b60405180910390f35b6104a0600480360361049b9190810190611558565b6107ac565b6040516104ad9190611edf565b60405180910390f35b6104d060048036036104cb9190810190611614565b6107b3565b6040516104dd9190611f52565b60405180910390f35b61050060048036036104fb919081019061148b565b6107ba565b60405161050d9190611e58565b60405180910390f35b610530600480360361052b919081019061152f565b6107c1565b60405161053d9190611ec4565b60405180910390f35b610560600480360361055b919081019061138e565b6107c8565b60405161056d9190611dc3565b60405180910390f35b610590600480360361058b91908101906114b4565b6107cf565b60405161059d9190611e73565b60405180910390f35b6105c060048036036105bb91908101906114dd565b6107d6565b6040516105cd9190611e8e565b60405180910390f35b6105f060048036036105eb9190810190611421565b6107dd565b6040516105fd9190611e1b565b60405180910390f35b610620600480360361061b9190810190611324565b6107e4565b60405161062d9190611d86565b60405180910390f35b610650600480360361064b91908101906115c2565b6107eb565b60405161065d9190611f1c565b60405180910390f35b610680600480360361067b919081019061144a565b6107f2565b60405161068d9190611e36565b60405180910390f35b6106b060048036036106ab91908101906110fa565b6107f9565b6040516106bd9190611c4e565b60405180910390f35b6106e060048036036106db9190810190611506565b610800565b6040516106ed9190611ea9565b60405180910390f35b610710600480360361070b91908101906113f8565b610807565b60405161071d9190611e00565b60405180910390f35b6000919050565b61073561080e565b919050565b610742610830565b919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b610772610852565b919050565b6060919050565b610786610874565b919050565b6060919050565b61079a61089b565b919050565b6107a76108bd565b919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b6000919050565b6060919050565b6000919050565b6000919050565b6000919050565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816108835790505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816108cc5790505090565b60006108f082356124f2565b905092915050565b600082601f830112151561090b57600080fd5b600261091e61091982611fb5565b611f88565b9150818385602084028201111561093457600080fd5b60005b83811015610964578161094a88826108e4565b845260208401935060208301925050600181019050610937565b5050505092915050565b600082601f830112151561098157600080fd5b813561099461098f82611fd7565b611f88565b915081818352602084019350602081019050838560208402820111156109b957600080fd5b60005b838110156109e957816109cf88826108e4565b8452602084019350602083019250506001810190506109bc565b5050505092915050565b600082601f8301121515610a0657600080fd5b6002610a19610a1482611fff565b611f88565b91508183856020840282011115610a2f57600080fd5b60005b83811015610a5f5781610a458882610e9e565b845260208401935060208301925050600181019050610a32565b5050505092915050565b600082601f8301121515610a7c57600080fd5b8135610a8f610a8a82612021565b611f88565b91508181835260208401935060208101905083856020840282011115610ab457600080fd5b60005b83811015610ae45781610aca8882610e9e565b845260208401935060208301925050600181019050610ab7565b5050505092915050565b600082601f8301121515610b0157600080fd5b6002610b14610b0f82612049565b611f88565b9150818360005b83811015610b4b5781358601610b318882610eda565b845260208401935060208301925050600181019050610b1b565b5050505092915050565b600082601f8301121515610b6857600080fd5b8135610b7b610b768261206b565b611f88565b9150818183526020840193506020810190508360005b83811015610bc15781358601610ba78882610eda565b845260208401935060208301925050600181019050610b91565b5050505092915050565b600082601f8301121515610bde57600080fd5b6002610bf1610bec82612093565b611f88565b91508183856020840282011115610c0757600080fd5b60005b83811015610c375781610c1d8882610f9a565b845260208401935060208301925050600181019050610c0a565b5050505092915050565b600082601f8301121515610c5457600080fd5b8135610c67610c62826120b5565b611f88565b91508181835260208401935060208101905083856020840282011115610c8c57600080fd5b60005b83811015610cbc5781610ca28882610f9a565b845260208401935060208301925050600181019050610c8f565b5050505092915050565b600082601f8301121515610cd957600080fd5b6002610cec610ce7826120dd565b611f88565b9150818360005b83811015610d235781358601610d098882610fea565b845260208401935060208301925050600181019050610cf3565b5050505092915050565b600082601f8301121515610d4057600080fd5b8135610d53610d4e826120ff565b611f88565b9150818183526020840193506020810190508360005b83811015610d995781358601610d7f8882610fea565b845260208401935060208301925050600181019050610d69565b5050505092915050565b600082601f8301121515610db657600080fd5b6002610dc9610dc482612127565b611f88565b91508183856020840282011115610ddf57600080fd5b60005b83811015610e0f5781610df588826110aa565b845260208401935060208301925050600181019050610de2565b5050505092915050565b600082601f8301121515610e2c57600080fd5b8135610e3f610e3a82612149565b611f88565b91508181835260208401935060208101905083856020840282011115610e6457600080fd5b60005b83811015610e945781610e7a88826110aa565b845260208401935060208301925050600181019050610e67565b5050505092915050565b6000610eaa8235612504565b905092915050565b6000610ebe8235612510565b905092915050565b6000610ed2823561253c565b905092915050565b600082601f8301121515610eed57600080fd5b8135610f00610efb82612171565b611f88565b91508082526020830160208301858383011115610f1c57600080fd5b610f278382846125cd565b50505092915050565b600082601f8301121515610f4357600080fd5b8135610f56610f518261219d565b611f88565b91508082526020830160208301858383011115610f7257600080fd5b610f7d8382846125cd565b50505092915050565b6000610f928235612546565b905092915050565b6000610fa68235612553565b905092915050565b6000610fba823561255d565b905092915050565b6000610fce823561256a565b905092915050565b6000610fe28235612577565b905092915050565b600082601f8301121515610ffd57600080fd5b813561101061100b826121c9565b611f88565b9150808252602083016020830185838301111561102c57600080fd5b6110378382846125cd565b50505092915050565b600082601f830112151561105357600080fd5b8135611066611061826121f5565b611f88565b9150808252602083016020830185838301111561108257600080fd5b61108d8382846125cd565b50505092915050565b60006110a28235612584565b905092915050565b60006110b68235612592565b905092915050565b60006110ca823561259c565b905092915050565b60006110de82356125ac565b905092915050565b60006110f282356125c0565b905092915050565b60006020828403121561110c57600080fd5b600061111a848285016108e4565b91505092915050565b60006040828403121561113557600080fd5b6000611143848285016108f8565b91505092915050565b60006020828403121561115e57600080fd5b600082013567ffffffffffffffff81111561117857600080fd5b6111848482850161096e565b91505092915050565b60006040828403121561119f57600080fd5b60006111ad848285016109f3565b91505092915050565b6000602082840312156111c857600080fd5b600082013567ffffffffffffffff8111156111e257600080fd5b6111ee84828501610a69565b91505092915050565b60006020828403121561120957600080fd5b600082013567ffffffffffffffff81111561122357600080fd5b61122f84828501610aee565b91505092915050565b60006020828403121561124a57600080fd5b600082013567ffffffffffffffff81111561126457600080fd5b61127084828501610b55565b91505092915050565b60006040828403121561128b57600080fd5b600061129984828501610bcb565b91505092915050565b6000602082840312156112b457600080fd5b600082013567ffffffffffffffff8111156112ce57600080fd5b6112da84828501610c41565b91505092915050565b6000602082840312156112f557600080fd5b600082013567ffffffffffffffff81111561130f57600080fd5b61131b84828501610cc6565b91505092915050565b60006020828403121561133657600080fd5b600082013567ffffffffffffffff81111561135057600080fd5b61135c84828501610d2d565b91505092915050565b60006040828403121561137757600080fd5b600061138584828501610da3565b91505092915050565b6000602082840312156113a057600080fd5b600082013567ffffffffffffffff8111156113ba57600080fd5b6113c684828501610e19565b91505092915050565b6000602082840312156113e157600080fd5b60006113ef84828501610e9e565b91505092915050565b60006020828403121561140a57600080fd5b600061141884828501610eb2565b91505092915050565b60006020828403121561143357600080fd5b600061144184828501610ec6565b91505092915050565b60006020828403121561145c57600080fd5b600082013567ffffffffffffffff81111561147657600080fd5b61148284828501610f30565b91505092915050565b60006020828403121561149d57600080fd5b60006114ab84828501610f86565b91505092915050565b6000602082840312156114c657600080fd5b60006114d484828501610f9a565b91505092915050565b6000602082840312156114ef57600080fd5b60006114fd84828501610fae565b91505092915050565b60006020828403121561151857600080fd5b600061152684828501610fc2565b91505092915050565b60006020828403121561154157600080fd5b600061154f84828501610fd6565b91505092915050565b60006020828403121561156a57600080fd5b600082013567ffffffffffffffff81111561158457600080fd5b61159084828501611040565b91505092915050565b6000602082840312156115ab57600080fd5b60006115b984828501611096565b91505092915050565b6000602082840312156115d457600080fd5b60006115e2848285016110aa565b91505092915050565b6000602082840312156115fd57600080fd5b600061160b848285016110be565b91505092915050565b60006020828403121561162657600080fd5b6000611634848285016110d2565b91505092915050565b60006020828403121561164f57600080fd5b600061165d848285016110e6565b91505092915050565b61166f816123f7565b82525050565b61167e816122ab565b61168782612221565b60005b828110156116b95761169d858351611666565b6116a68261235b565b915060208501945060018101905061168a565b5050505050565b60006116cb826122b6565b8084526020840193506116dd8361222b565b60005b8281101561170f576116f3868351611666565b6116fc82612368565b91506020860195506001810190506116e0565b50849250505092915050565b611724816122c1565b61172d82612238565b60005b8281101561175f57611743858351611ab3565b61174c82612375565b9150602085019450600181019050611730565b5050505050565b6000611771826122cc565b80845260208401935061178383612242565b60005b828110156117b557611799868351611ab3565b6117a282612382565b9150602086019550600181019050611786565b50849250505092915050565b60006117cc826122d7565b836020820285016117dc8561224f565b60005b848110156118155783830388526117f7838351611b16565b92506118028261238f565b91506020880197506001810190506117df565b508196508694505050505092915050565b6000611831826122e2565b8084526020840193508360208202850161184a85612259565b60005b84811015611883578383038852611865838351611b16565b92506118708261239c565b915060208801975060018101905061184d565b508196508694505050505092915050565b61189d816122ed565b6118a682612266565b60005b828110156118d8576118bc858351611b5b565b6118c5826123a9565b91506020850194506001810190506118a9565b5050505050565b60006118ea826122f8565b8084526020840193506118fc83612270565b60005b8281101561192e57611912868351611b5b565b61191b826123b6565b91506020860195506001810190506118ff565b50849250505092915050565b600061194582612303565b836020820285016119558561227d565b60005b8481101561198e578383038852611970838351611bcd565b925061197b826123c3565b9150602088019750600181019050611958565b508196508694505050505092915050565b60006119aa8261230e565b808452602084019350836020820285016119c385612287565b60005b848110156119fc5783830388526119de838351611bcd565b92506119e9826123d0565b91506020880197506001810190506119c6565b508196508694505050505092915050565b611a1681612319565b611a1f82612294565b60005b82811015611a5157611a35858351611c12565b611a3e826123dd565b9150602085019450600181019050611a22565b5050505050565b6000611a6382612324565b808452602084019350611a758361229e565b60005b82811015611aa757611a8b868351611c12565b611a94826123ea565b9150602086019550600181019050611a78565b50849250505092915050565b611abc81612409565b82525050565b611acb81612415565b82525050565b611ada81612441565b82525050565b6000611aeb8261233a565b808452611aff8160208601602086016125dc565b611b088161260f565b602085010191505092915050565b6000611b218261232f565b808452611b358160208601602086016125dc565b611b3e8161260f565b602085010191505092915050565b611b558161244b565b82525050565b611b6481612458565b82525050565b611b7381612462565b82525050565b611b828161246f565b82525050565b611b918161247c565b82525050565b6000611ba282612350565b808452611bb68160208601602086016125dc565b611bbf8161260f565b602085010191505092915050565b6000611bd882612345565b808452611bec8160208601602086016125dc565b611bf58161260f565b602085010191505092915050565b611c0c81612489565b82525050565b611c1b816124b7565b82525050565b611c2a816124c1565b82525050565b611c39816124d1565b82525050565b611c48816124e5565b82525050565b6000602082019050611c636000830184611666565b92915050565b6000604082019050611c7e6000830184611675565b92915050565b60006020820190508181036000830152611c9e81846116c0565b905092915050565b6000604082019050611cbb600083018461171b565b92915050565b60006020820190508181036000830152611cdb8184611766565b905092915050565b60006020820190508181036000830152611cfd81846117c1565b905092915050565b60006020820190508181036000830152611d1f8184611826565b905092915050565b6000604082019050611d3c6000830184611894565b92915050565b60006020820190508181036000830152611d5c81846118df565b905092915050565b60006020820190508181036000830152611d7e818461193a565b905092915050565b60006020820190508181036000830152611da0818461199f565b905092915050565b6000604082019050611dbd6000830184611a0d565b92915050565b60006020820190508181036000830152611ddd8184611a58565b905092915050565b6000602082019050611dfa6000830184611ab3565b92915050565b6000602082019050611e156000830184611ac2565b92915050565b6000602082019050611e306000830184611ad1565b92915050565b60006020820190508181036000830152611e508184611ae0565b905092915050565b6000602082019050611e6d6000830184611b4c565b92915050565b6000602082019050611e886000830184611b5b565b92915050565b6000602082019050611ea36000830184611b6a565b92915050565b6000602082019050611ebe6000830184611b79565b92915050565b6000602082019050611ed96000830184611b88565b92915050565b60006020820190508181036000830152611ef98184611b97565b905092915050565b6000602082019050611f166000830184611c03565b92915050565b6000602082019050611f316000830184611c12565b92915050565b6000602082019050611f4c6000830184611c21565b92915050565b6000602082019050611f676000830184611c30565b92915050565b6000602082019050611f826000830184611c3f565b92915050565b6000604051905081810181811067ffffffffffffffff82111715611fab57600080fd5b8060405250919050565b600067ffffffffffffffff821115611fcc57600080fd5b602082029050919050565b600067ffffffffffffffff821115611fee57600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561201657600080fd5b602082029050919050565b600067ffffffffffffffff82111561203857600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561206057600080fd5b602082029050919050565b600067ffffffffffffffff82111561208257600080fd5b602082029050602081019050919050565b600067ffffffffffffffff8211156120aa57600080fd5b602082029050919050565b600067ffffffffffffffff8211156120cc57600080fd5b602082029050602081019050919050565b600067ffffffffffffffff8211156120f457600080fd5b602082029050919050565b600067ffffffffffffffff82111561211657600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561213e57600080fd5b602082029050919050565b600067ffffffffffffffff82111561216057600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561218857600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff8211156121b457600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff8211156121e057600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff82111561220c57600080fd5b601f19601f8301169050602081019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b600081519050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b600061240282612497565b9050919050565b60008115159050919050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60008160010b9050919050565b6000819050919050565b60008160030b9050919050565b60008160070b9050919050565b60008160000b9050919050565b600061ffff82169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600063ffffffff82169050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b60006124fd82612497565b9050919050565b60008115159050919050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60008160010b9050919050565b6000819050919050565b60008160030b9050919050565b60008160070b9050919050565b60008160000b9050919050565b600061ffff82169050919050565b6000819050919050565b600063ffffffff82169050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b838110156125fa5780820151818401526020810190506125df565b83811115612609576000848401525b50505050565b6000601f19601f830116905091905056fea265627a7a723058206fe37171cf1b10ebd291cfdca61d67e7fc3c208795e999c833c42a14d86cf00d6c6578706572696d656e74616cf50037`, - ` -// This file is an automatically generated Java binding. Do not modify as any -// change will likely be lost upon the next re-generation! - -package bindtest; - -import org.ethereum.geth.*; -import java.util.*; - -public class Test { - // ABI is the input ABI used to generate the binding from. - public final static String ABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"u16\",\"type\":\"uint16\"}],\"name\":\"setUint16\",\"outputs\":[{\"name\":\"\",\"type\":\"uint16\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b_a\",\"type\":\"bool[2]\"}],\"name\":\"setBoolArray\",\"outputs\":[{\"name\":\"\",\"type\":\"bool[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a_a\",\"type\":\"address[2]\"}],\"name\":\"setAddressArray\",\"outputs\":[{\"name\":\"\",\"type\":\"address[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"bs_l\",\"type\":\"bytes[]\"}],\"name\":\"setBytesList\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u8\",\"type\":\"uint8\"}],\"name\":\"setUint8\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u32\",\"type\":\"uint32\"}],\"name\":\"setUint32\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b\",\"type\":\"bool\"}],\"name\":\"setBool\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i256_l\",\"type\":\"int256[]\"}],\"name\":\"setInt256List\",\"outputs\":[{\"name\":\"\",\"type\":\"int256[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u256_a\",\"type\":\"uint256[2]\"}],\"name\":\"setUint256Array\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b_l\",\"type\":\"bool[]\"}],\"name\":\"setBoolList\",\"outputs\":[{\"name\":\"\",\"type\":\"bool[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"bs_a\",\"type\":\"bytes[2]\"}],\"name\":\"setBytesArray\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a_l\",\"type\":\"address[]\"}],\"name\":\"setAddressList\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i256_a\",\"type\":\"int256[2]\"}],\"name\":\"setInt256Array\",\"outputs\":[{\"name\":\"\",\"type\":\"int256[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"s_a\",\"type\":\"string[2]\"}],\"name\":\"setStringArray\",\"outputs\":[{\"name\":\"\",\"type\":\"string[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"s\",\"type\":\"string\"}],\"name\":\"setString\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u64\",\"type\":\"uint64\"}],\"name\":\"setUint64\",\"outputs\":[{\"name\":\"\",\"type\":\"uint64\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i16\",\"type\":\"int16\"}],\"name\":\"setInt16\",\"outputs\":[{\"name\":\"\",\"type\":\"int16\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i8\",\"type\":\"int8\"}],\"name\":\"setInt8\",\"outputs\":[{\"name\":\"\",\"type\":\"int8\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u256_l\",\"type\":\"uint256[]\"}],\"name\":\"setUint256List\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i256\",\"type\":\"int256\"}],\"name\":\"setInt256\",\"outputs\":[{\"name\":\"\",\"type\":\"int256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i32\",\"type\":\"int32\"}],\"name\":\"setInt32\",\"outputs\":[{\"name\":\"\",\"type\":\"int32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b32\",\"type\":\"bytes32\"}],\"name\":\"setBytes32\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"s_l\",\"type\":\"string[]\"}],\"name\":\"setStringList\",\"outputs\":[{\"name\":\"\",\"type\":\"string[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u256\",\"type\":\"uint256\"}],\"name\":\"setUint256\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"bs\",\"type\":\"bytes\"}],\"name\":\"setBytes\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a\",\"type\":\"address\"}],\"name\":\"setAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i64\",\"type\":\"int64\"}],\"name\":\"setInt64\",\"outputs\":[{\"name\":\"\",\"type\":\"int64\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b1\",\"type\":\"bytes1\"}],\"name\":\"setBytes1\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes1\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"; - - // BYTECODE is the compiled bytecode used for deploying new contracts. - public final static String BYTECODE = "0x608060405234801561001057600080fd5b5061265a806100206000396000f3fe608060405234801561001057600080fd5b50600436106101e1576000357c0100000000000000000000000000000000000000000000000000000000900480637fcaf66611610116578063c2b12a73116100b4578063da359dc81161008e578063da359dc814610666578063e30081a014610696578063e673eb32146106c6578063fba1a1c3146106f6576101e1565b8063c2b12a73146105d6578063c577796114610606578063d2282dc514610636576101e1565b80639a19a953116100f05780639a19a95314610516578063a0709e1914610546578063a53b1c1e14610576578063b7d5df31146105a6576101e1565b80637fcaf66614610486578063822cba69146104b657806386114cea146104e6576101e1565b806322722302116101835780635119655d1161015d5780635119655d146103c65780635be6b37e146103f65780636aa482fc146104265780637173b69514610456576101e1565b806322722302146103365780632766a755146103665780634d5ee6da14610396576101e1565b806316c105e2116101bf57806316c105e2146102765780631774e646146102a65780631c9352e2146102d65780631e26fd3314610306576101e1565b80630477988a146101e6578063118a971814610216578063151f547114610246575b600080fd5b61020060048036036101fb9190810190611599565b610726565b60405161020d9190611f01565b60405180910390f35b610230600480360361022b919081019061118d565b61072d565b60405161023d9190611ca6565b60405180910390f35b610260600480360361025b9190810190611123565b61073a565b60405161026d9190611c69565b60405180910390f35b610290600480360361028b9190810190611238565b610747565b60405161029d9190611d05565b60405180910390f35b6102c060048036036102bb919081019061163d565b61074e565b6040516102cd9190611f6d565b60405180910390f35b6102f060048036036102eb91908101906115eb565b610755565b6040516102fd9190611f37565b60405180910390f35b610320600480360361031b91908101906113cf565b61075c565b60405161032d9190611de5565b60405180910390f35b610350600480360361034b91908101906112a2565b610763565b60405161035d9190611d42565b60405180910390f35b610380600480360361037b9190810190611365565b61076a565b60405161038d9190611da8565b60405180910390f35b6103b060048036036103ab91908101906111b6565b610777565b6040516103bd9190611cc1565b60405180910390f35b6103e060048036036103db91908101906111f7565b61077e565b6040516103ed9190611ce3565b60405180910390f35b610410600480360361040b919081019061114c565b61078b565b60405161041d9190611c84565b60405180910390f35b610440600480360361043b9190810190611279565b610792565b60405161044d9190611d27565b60405180910390f35b610470600480360361046b91908101906112e3565b61079f565b60405161047d9190611d64565b60405180910390f35b6104a0600480360361049b9190810190611558565b6107ac565b6040516104ad9190611edf565b60405180910390f35b6104d060048036036104cb9190810190611614565b6107b3565b6040516104dd9190611f52565b60405180910390f35b61050060048036036104fb919081019061148b565b6107ba565b60405161050d9190611e58565b60405180910390f35b610530600480360361052b919081019061152f565b6107c1565b60405161053d9190611ec4565b60405180910390f35b610560600480360361055b919081019061138e565b6107c8565b60405161056d9190611dc3565b60405180910390f35b610590600480360361058b91908101906114b4565b6107cf565b60405161059d9190611e73565b60405180910390f35b6105c060048036036105bb91908101906114dd565b6107d6565b6040516105cd9190611e8e565b60405180910390f35b6105f060048036036105eb9190810190611421565b6107dd565b6040516105fd9190611e1b565b60405180910390f35b610620600480360361061b9190810190611324565b6107e4565b60405161062d9190611d86565b60405180910390f35b610650600480360361064b91908101906115c2565b6107eb565b60405161065d9190611f1c565b60405180910390f35b610680600480360361067b919081019061144a565b6107f2565b60405161068d9190611e36565b60405180910390f35b6106b060048036036106ab91908101906110fa565b6107f9565b6040516106bd9190611c4e565b60405180910390f35b6106e060048036036106db9190810190611506565b610800565b6040516106ed9190611ea9565b60405180910390f35b610710600480360361070b91908101906113f8565b610807565b60405161071d9190611e00565b60405180910390f35b6000919050565b61073561080e565b919050565b610742610830565b919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b610772610852565b919050565b6060919050565b610786610874565b919050565b6060919050565b61079a61089b565b919050565b6107a76108bd565b919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b6000919050565b6060919050565b6000919050565b6000919050565b6000919050565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816108835790505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816108cc5790505090565b60006108f082356124f2565b905092915050565b600082601f830112151561090b57600080fd5b600261091e61091982611fb5565b611f88565b9150818385602084028201111561093457600080fd5b60005b83811015610964578161094a88826108e4565b845260208401935060208301925050600181019050610937565b5050505092915050565b600082601f830112151561098157600080fd5b813561099461098f82611fd7565b611f88565b915081818352602084019350602081019050838560208402820111156109b957600080fd5b60005b838110156109e957816109cf88826108e4565b8452602084019350602083019250506001810190506109bc565b5050505092915050565b600082601f8301121515610a0657600080fd5b6002610a19610a1482611fff565b611f88565b91508183856020840282011115610a2f57600080fd5b60005b83811015610a5f5781610a458882610e9e565b845260208401935060208301925050600181019050610a32565b5050505092915050565b600082601f8301121515610a7c57600080fd5b8135610a8f610a8a82612021565b611f88565b91508181835260208401935060208101905083856020840282011115610ab457600080fd5b60005b83811015610ae45781610aca8882610e9e565b845260208401935060208301925050600181019050610ab7565b5050505092915050565b600082601f8301121515610b0157600080fd5b6002610b14610b0f82612049565b611f88565b9150818360005b83811015610b4b5781358601610b318882610eda565b845260208401935060208301925050600181019050610b1b565b5050505092915050565b600082601f8301121515610b6857600080fd5b8135610b7b610b768261206b565b611f88565b9150818183526020840193506020810190508360005b83811015610bc15781358601610ba78882610eda565b845260208401935060208301925050600181019050610b91565b5050505092915050565b600082601f8301121515610bde57600080fd5b6002610bf1610bec82612093565b611f88565b91508183856020840282011115610c0757600080fd5b60005b83811015610c375781610c1d8882610f9a565b845260208401935060208301925050600181019050610c0a565b5050505092915050565b600082601f8301121515610c5457600080fd5b8135610c67610c62826120b5565b611f88565b91508181835260208401935060208101905083856020840282011115610c8c57600080fd5b60005b83811015610cbc5781610ca28882610f9a565b845260208401935060208301925050600181019050610c8f565b5050505092915050565b600082601f8301121515610cd957600080fd5b6002610cec610ce7826120dd565b611f88565b9150818360005b83811015610d235781358601610d098882610fea565b845260208401935060208301925050600181019050610cf3565b5050505092915050565b600082601f8301121515610d4057600080fd5b8135610d53610d4e826120ff565b611f88565b9150818183526020840193506020810190508360005b83811015610d995781358601610d7f8882610fea565b845260208401935060208301925050600181019050610d69565b5050505092915050565b600082601f8301121515610db657600080fd5b6002610dc9610dc482612127565b611f88565b91508183856020840282011115610ddf57600080fd5b60005b83811015610e0f5781610df588826110aa565b845260208401935060208301925050600181019050610de2565b5050505092915050565b600082601f8301121515610e2c57600080fd5b8135610e3f610e3a82612149565b611f88565b91508181835260208401935060208101905083856020840282011115610e6457600080fd5b60005b83811015610e945781610e7a88826110aa565b845260208401935060208301925050600181019050610e67565b5050505092915050565b6000610eaa8235612504565b905092915050565b6000610ebe8235612510565b905092915050565b6000610ed2823561253c565b905092915050565b600082601f8301121515610eed57600080fd5b8135610f00610efb82612171565b611f88565b91508082526020830160208301858383011115610f1c57600080fd5b610f278382846125cd565b50505092915050565b600082601f8301121515610f4357600080fd5b8135610f56610f518261219d565b611f88565b91508082526020830160208301858383011115610f7257600080fd5b610f7d8382846125cd565b50505092915050565b6000610f928235612546565b905092915050565b6000610fa68235612553565b905092915050565b6000610fba823561255d565b905092915050565b6000610fce823561256a565b905092915050565b6000610fe28235612577565b905092915050565b600082601f8301121515610ffd57600080fd5b813561101061100b826121c9565b611f88565b9150808252602083016020830185838301111561102c57600080fd5b6110378382846125cd565b50505092915050565b600082601f830112151561105357600080fd5b8135611066611061826121f5565b611f88565b9150808252602083016020830185838301111561108257600080fd5b61108d8382846125cd565b50505092915050565b60006110a28235612584565b905092915050565b60006110b68235612592565b905092915050565b60006110ca823561259c565b905092915050565b60006110de82356125ac565b905092915050565b60006110f282356125c0565b905092915050565b60006020828403121561110c57600080fd5b600061111a848285016108e4565b91505092915050565b60006040828403121561113557600080fd5b6000611143848285016108f8565b91505092915050565b60006020828403121561115e57600080fd5b600082013567ffffffffffffffff81111561117857600080fd5b6111848482850161096e565b91505092915050565b60006040828403121561119f57600080fd5b60006111ad848285016109f3565b91505092915050565b6000602082840312156111c857600080fd5b600082013567ffffffffffffffff8111156111e257600080fd5b6111ee84828501610a69565b91505092915050565b60006020828403121561120957600080fd5b600082013567ffffffffffffffff81111561122357600080fd5b61122f84828501610aee565b91505092915050565b60006020828403121561124a57600080fd5b600082013567ffffffffffffffff81111561126457600080fd5b61127084828501610b55565b91505092915050565b60006040828403121561128b57600080fd5b600061129984828501610bcb565b91505092915050565b6000602082840312156112b457600080fd5b600082013567ffffffffffffffff8111156112ce57600080fd5b6112da84828501610c41565b91505092915050565b6000602082840312156112f557600080fd5b600082013567ffffffffffffffff81111561130f57600080fd5b61131b84828501610cc6565b91505092915050565b60006020828403121561133657600080fd5b600082013567ffffffffffffffff81111561135057600080fd5b61135c84828501610d2d565b91505092915050565b60006040828403121561137757600080fd5b600061138584828501610da3565b91505092915050565b6000602082840312156113a057600080fd5b600082013567ffffffffffffffff8111156113ba57600080fd5b6113c684828501610e19565b91505092915050565b6000602082840312156113e157600080fd5b60006113ef84828501610e9e565b91505092915050565b60006020828403121561140a57600080fd5b600061141884828501610eb2565b91505092915050565b60006020828403121561143357600080fd5b600061144184828501610ec6565b91505092915050565b60006020828403121561145c57600080fd5b600082013567ffffffffffffffff81111561147657600080fd5b61148284828501610f30565b91505092915050565b60006020828403121561149d57600080fd5b60006114ab84828501610f86565b91505092915050565b6000602082840312156114c657600080fd5b60006114d484828501610f9a565b91505092915050565b6000602082840312156114ef57600080fd5b60006114fd84828501610fae565b91505092915050565b60006020828403121561151857600080fd5b600061152684828501610fc2565b91505092915050565b60006020828403121561154157600080fd5b600061154f84828501610fd6565b91505092915050565b60006020828403121561156a57600080fd5b600082013567ffffffffffffffff81111561158457600080fd5b61159084828501611040565b91505092915050565b6000602082840312156115ab57600080fd5b60006115b984828501611096565b91505092915050565b6000602082840312156115d457600080fd5b60006115e2848285016110aa565b91505092915050565b6000602082840312156115fd57600080fd5b600061160b848285016110be565b91505092915050565b60006020828403121561162657600080fd5b6000611634848285016110d2565b91505092915050565b60006020828403121561164f57600080fd5b600061165d848285016110e6565b91505092915050565b61166f816123f7565b82525050565b61167e816122ab565b61168782612221565b60005b828110156116b95761169d858351611666565b6116a68261235b565b915060208501945060018101905061168a565b5050505050565b60006116cb826122b6565b8084526020840193506116dd8361222b565b60005b8281101561170f576116f3868351611666565b6116fc82612368565b91506020860195506001810190506116e0565b50849250505092915050565b611724816122c1565b61172d82612238565b60005b8281101561175f57611743858351611ab3565b61174c82612375565b9150602085019450600181019050611730565b5050505050565b6000611771826122cc565b80845260208401935061178383612242565b60005b828110156117b557611799868351611ab3565b6117a282612382565b9150602086019550600181019050611786565b50849250505092915050565b60006117cc826122d7565b836020820285016117dc8561224f565b60005b848110156118155783830388526117f7838351611b16565b92506118028261238f565b91506020880197506001810190506117df565b508196508694505050505092915050565b6000611831826122e2565b8084526020840193508360208202850161184a85612259565b60005b84811015611883578383038852611865838351611b16565b92506118708261239c565b915060208801975060018101905061184d565b508196508694505050505092915050565b61189d816122ed565b6118a682612266565b60005b828110156118d8576118bc858351611b5b565b6118c5826123a9565b91506020850194506001810190506118a9565b5050505050565b60006118ea826122f8565b8084526020840193506118fc83612270565b60005b8281101561192e57611912868351611b5b565b61191b826123b6565b91506020860195506001810190506118ff565b50849250505092915050565b600061194582612303565b836020820285016119558561227d565b60005b8481101561198e578383038852611970838351611bcd565b925061197b826123c3565b9150602088019750600181019050611958565b508196508694505050505092915050565b60006119aa8261230e565b808452602084019350836020820285016119c385612287565b60005b848110156119fc5783830388526119de838351611bcd565b92506119e9826123d0565b91506020880197506001810190506119c6565b508196508694505050505092915050565b611a1681612319565b611a1f82612294565b60005b82811015611a5157611a35858351611c12565b611a3e826123dd565b9150602085019450600181019050611a22565b5050505050565b6000611a6382612324565b808452602084019350611a758361229e565b60005b82811015611aa757611a8b868351611c12565b611a94826123ea565b9150602086019550600181019050611a78565b50849250505092915050565b611abc81612409565b82525050565b611acb81612415565b82525050565b611ada81612441565b82525050565b6000611aeb8261233a565b808452611aff8160208601602086016125dc565b611b088161260f565b602085010191505092915050565b6000611b218261232f565b808452611b358160208601602086016125dc565b611b3e8161260f565b602085010191505092915050565b611b558161244b565b82525050565b611b6481612458565b82525050565b611b7381612462565b82525050565b611b828161246f565b82525050565b611b918161247c565b82525050565b6000611ba282612350565b808452611bb68160208601602086016125dc565b611bbf8161260f565b602085010191505092915050565b6000611bd882612345565b808452611bec8160208601602086016125dc565b611bf58161260f565b602085010191505092915050565b611c0c81612489565b82525050565b611c1b816124b7565b82525050565b611c2a816124c1565b82525050565b611c39816124d1565b82525050565b611c48816124e5565b82525050565b6000602082019050611c636000830184611666565b92915050565b6000604082019050611c7e6000830184611675565b92915050565b60006020820190508181036000830152611c9e81846116c0565b905092915050565b6000604082019050611cbb600083018461171b565b92915050565b60006020820190508181036000830152611cdb8184611766565b905092915050565b60006020820190508181036000830152611cfd81846117c1565b905092915050565b60006020820190508181036000830152611d1f8184611826565b905092915050565b6000604082019050611d3c6000830184611894565b92915050565b60006020820190508181036000830152611d5c81846118df565b905092915050565b60006020820190508181036000830152611d7e818461193a565b905092915050565b60006020820190508181036000830152611da0818461199f565b905092915050565b6000604082019050611dbd6000830184611a0d565b92915050565b60006020820190508181036000830152611ddd8184611a58565b905092915050565b6000602082019050611dfa6000830184611ab3565b92915050565b6000602082019050611e156000830184611ac2565b92915050565b6000602082019050611e306000830184611ad1565b92915050565b60006020820190508181036000830152611e508184611ae0565b905092915050565b6000602082019050611e6d6000830184611b4c565b92915050565b6000602082019050611e886000830184611b5b565b92915050565b6000602082019050611ea36000830184611b6a565b92915050565b6000602082019050611ebe6000830184611b79565b92915050565b6000602082019050611ed96000830184611b88565b92915050565b60006020820190508181036000830152611ef98184611b97565b905092915050565b6000602082019050611f166000830184611c03565b92915050565b6000602082019050611f316000830184611c12565b92915050565b6000602082019050611f4c6000830184611c21565b92915050565b6000602082019050611f676000830184611c30565b92915050565b6000602082019050611f826000830184611c3f565b92915050565b6000604051905081810181811067ffffffffffffffff82111715611fab57600080fd5b8060405250919050565b600067ffffffffffffffff821115611fcc57600080fd5b602082029050919050565b600067ffffffffffffffff821115611fee57600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561201657600080fd5b602082029050919050565b600067ffffffffffffffff82111561203857600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561206057600080fd5b602082029050919050565b600067ffffffffffffffff82111561208257600080fd5b602082029050602081019050919050565b600067ffffffffffffffff8211156120aa57600080fd5b602082029050919050565b600067ffffffffffffffff8211156120cc57600080fd5b602082029050602081019050919050565b600067ffffffffffffffff8211156120f457600080fd5b602082029050919050565b600067ffffffffffffffff82111561211657600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561213e57600080fd5b602082029050919050565b600067ffffffffffffffff82111561216057600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561218857600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff8211156121b457600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff8211156121e057600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff82111561220c57600080fd5b601f19601f8301169050602081019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b600081519050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b600061240282612497565b9050919050565b60008115159050919050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60008160010b9050919050565b6000819050919050565b60008160030b9050919050565b60008160070b9050919050565b60008160000b9050919050565b600061ffff82169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600063ffffffff82169050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b60006124fd82612497565b9050919050565b60008115159050919050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60008160010b9050919050565b6000819050919050565b60008160030b9050919050565b60008160070b9050919050565b60008160000b9050919050565b600061ffff82169050919050565b6000819050919050565b600063ffffffff82169050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b838110156125fa5780820151818401526020810190506125df565b83811115612609576000848401525b50505050565b6000601f19601f830116905091905056fea265627a7a723058206fe37171cf1b10ebd291cfdca61d67e7fc3c208795e999c833c42a14d86cf00d6c6578706572696d656e74616cf50037"; - - // deploy deploys a new Ethereum contract, binding an instance of Test to it. - public static Test deploy(TransactOpts auth, EthereumClient client) throws Exception { - Interfaces args = Geth.newInterfaces(0); - String bytecode = BYTECODE; - return new Test(Geth.deployContract(auth, ABI, Geth.decodeFromHex(bytecode), client, args)); - } - - // Internal constructor used by contract deployment. - private Test(BoundContract deployment) { - this.Address = deployment.getAddress(); - this.Deployer = deployment.getDeployer(); - this.Contract = deployment; - } - - // Ethereum address where this contract is located at. - public final Address Address; - - // Ethereum transaction in which this contract was deployed (if known!). - public final Transaction Deployer; - - // Contract instance bound to a blockchain address. - private final BoundContract Contract; - - // Creates a new instance of Test, bound to a specific deployed contract. - public Test(Address address, EthereumClient client) throws Exception { - this(Geth.bindContract(address, ABI, client)); - } - - // setAddress is a paid mutator transaction binding the contract method 0xe30081a0. - // - // Solidity: function setAddress(address a) returns(address) - public Transaction setAddress(TransactOpts opts, Address a) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setAddress(a);args.set(0,arg0); - - return this.Contract.transact(opts, "setAddress" , args); - } - - // setAddressArray is a paid mutator transaction binding the contract method 0x151f5471. - // - // Solidity: function setAddressArray(address[2] a_a) returns(address[2]) - public Transaction setAddressArray(TransactOpts opts, Addresses a_a) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setAddresses(a_a);args.set(0,arg0); - - return this.Contract.transact(opts, "setAddressArray" , args); - } - - // setAddressList is a paid mutator transaction binding the contract method 0x5be6b37e. - // - // Solidity: function setAddressList(address[] a_l) returns(address[]) - public Transaction setAddressList(TransactOpts opts, Addresses a_l) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setAddresses(a_l);args.set(0,arg0); - - return this.Contract.transact(opts, "setAddressList" , args); - } - - // setBool is a paid mutator transaction binding the contract method 0x1e26fd33. - // - // Solidity: function setBool(bool b) returns(bool) - public Transaction setBool(TransactOpts opts, boolean b) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBool(b);args.set(0,arg0); - - return this.Contract.transact(opts, "setBool" , args); - } - - // setBoolArray is a paid mutator transaction binding the contract method 0x118a9718. - // - // Solidity: function setBoolArray(bool[2] b_a) returns(bool[2]) - public Transaction setBoolArray(TransactOpts opts, Bools b_a) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBools(b_a);args.set(0,arg0); - - return this.Contract.transact(opts, "setBoolArray" , args); - } - - // setBoolList is a paid mutator transaction binding the contract method 0x4d5ee6da. - // - // Solidity: function setBoolList(bool[] b_l) returns(bool[]) - public Transaction setBoolList(TransactOpts opts, Bools b_l) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBools(b_l);args.set(0,arg0); - - return this.Contract.transact(opts, "setBoolList" , args); - } - - // setBytes is a paid mutator transaction binding the contract method 0xda359dc8. - // - // Solidity: function setBytes(bytes bs) returns(bytes) - public Transaction setBytes(TransactOpts opts, byte[] bs) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBinary(bs);args.set(0,arg0); - - return this.Contract.transact(opts, "setBytes" , args); - } - - // setBytes1 is a paid mutator transaction binding the contract method 0xfba1a1c3. - // - // Solidity: function setBytes1(bytes1 b1) returns(bytes1) - public Transaction setBytes1(TransactOpts opts, byte[] b1) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBinary(b1);args.set(0,arg0); - - return this.Contract.transact(opts, "setBytes1" , args); - } - - // setBytes32 is a paid mutator transaction binding the contract method 0xc2b12a73. - // - // Solidity: function setBytes32(bytes32 b32) returns(bytes32) - public Transaction setBytes32(TransactOpts opts, byte[] b32) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBinary(b32);args.set(0,arg0); - - return this.Contract.transact(opts, "setBytes32" , args); - } - - // setBytesArray is a paid mutator transaction binding the contract method 0x5119655d. - // - // Solidity: function setBytesArray(bytes[2] bs_a) returns(bytes[2]) - public Transaction setBytesArray(TransactOpts opts, Binaries bs_a) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBinaries(bs_a);args.set(0,arg0); - - return this.Contract.transact(opts, "setBytesArray" , args); - } - - // setBytesList is a paid mutator transaction binding the contract method 0x16c105e2. - // - // Solidity: function setBytesList(bytes[] bs_l) returns(bytes[]) - public Transaction setBytesList(TransactOpts opts, Binaries bs_l) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBinaries(bs_l);args.set(0,arg0); - - return this.Contract.transact(opts, "setBytesList" , args); - } - - // setInt16 is a paid mutator transaction binding the contract method 0x86114cea. - // - // Solidity: function setInt16(int16 i16) returns(int16) - public Transaction setInt16(TransactOpts opts, short i16) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setInt16(i16);args.set(0,arg0); - - return this.Contract.transact(opts, "setInt16" , args); - } - - // setInt256 is a paid mutator transaction binding the contract method 0xa53b1c1e. - // - // Solidity: function setInt256(int256 i256) returns(int256) - public Transaction setInt256(TransactOpts opts, BigInt i256) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBigInt(i256);args.set(0,arg0); - - return this.Contract.transact(opts, "setInt256" , args); - } - - // setInt256Array is a paid mutator transaction binding the contract method 0x6aa482fc. - // - // Solidity: function setInt256Array(int256[2] i256_a) returns(int256[2]) - public Transaction setInt256Array(TransactOpts opts, BigInts i256_a) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBigInts(i256_a);args.set(0,arg0); - - return this.Contract.transact(opts, "setInt256Array" , args); - } - - // setInt256List is a paid mutator transaction binding the contract method 0x22722302. - // - // Solidity: function setInt256List(int256[] i256_l) returns(int256[]) - public Transaction setInt256List(TransactOpts opts, BigInts i256_l) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBigInts(i256_l);args.set(0,arg0); - - return this.Contract.transact(opts, "setInt256List" , args); - } - - // setInt32 is a paid mutator transaction binding the contract method 0xb7d5df31. - // - // Solidity: function setInt32(int32 i32) returns(int32) - public Transaction setInt32(TransactOpts opts, int i32) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setInt32(i32);args.set(0,arg0); - - return this.Contract.transact(opts, "setInt32" , args); - } - - // setInt64 is a paid mutator transaction binding the contract method 0xe673eb32. - // - // Solidity: function setInt64(int64 i64) returns(int64) - public Transaction setInt64(TransactOpts opts, long i64) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setInt64(i64);args.set(0,arg0); - - return this.Contract.transact(opts, "setInt64" , args); - } - - // setInt8 is a paid mutator transaction binding the contract method 0x9a19a953. - // - // Solidity: function setInt8(int8 i8) returns(int8) - public Transaction setInt8(TransactOpts opts, byte i8) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setInt8(i8);args.set(0,arg0); - - return this.Contract.transact(opts, "setInt8" , args); - } - - // setString is a paid mutator transaction binding the contract method 0x7fcaf666. - // - // Solidity: function setString(string s) returns(string) - public Transaction setString(TransactOpts opts, String s) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setString(s);args.set(0,arg0); - - return this.Contract.transact(opts, "setString" , args); - } - - // setStringArray is a paid mutator transaction binding the contract method 0x7173b695. - // - // Solidity: function setStringArray(string[2] s_a) returns(string[2]) - public Transaction setStringArray(TransactOpts opts, Strings s_a) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setStrings(s_a);args.set(0,arg0); - - return this.Contract.transact(opts, "setStringArray" , args); - } - - // setStringList is a paid mutator transaction binding the contract method 0xc5777961. - // - // Solidity: function setStringList(string[] s_l) returns(string[]) - public Transaction setStringList(TransactOpts opts, Strings s_l) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setStrings(s_l);args.set(0,arg0); - - return this.Contract.transact(opts, "setStringList" , args); - } - - // setUint16 is a paid mutator transaction binding the contract method 0x0477988a. - // - // Solidity: function setUint16(uint16 u16) returns(uint16) - public Transaction setUint16(TransactOpts opts, BigInt u16) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setUint16(u16);args.set(0,arg0); - - return this.Contract.transact(opts, "setUint16" , args); - } - - // setUint256 is a paid mutator transaction binding the contract method 0xd2282dc5. - // - // Solidity: function setUint256(uint256 u256) returns(uint256) - public Transaction setUint256(TransactOpts opts, BigInt u256) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBigInt(u256);args.set(0,arg0); - - return this.Contract.transact(opts, "setUint256" , args); - } - - // setUint256Array is a paid mutator transaction binding the contract method 0x2766a755. - // - // Solidity: function setUint256Array(uint256[2] u256_a) returns(uint256[2]) - public Transaction setUint256Array(TransactOpts opts, BigInts u256_a) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBigInts(u256_a);args.set(0,arg0); - - return this.Contract.transact(opts, "setUint256Array" , args); - } - - // setUint256List is a paid mutator transaction binding the contract method 0xa0709e19. - // - // Solidity: function setUint256List(uint256[] u256_l) returns(uint256[]) - public Transaction setUint256List(TransactOpts opts, BigInts u256_l) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setBigInts(u256_l);args.set(0,arg0); - - return this.Contract.transact(opts, "setUint256List" , args); - } - - // setUint32 is a paid mutator transaction binding the contract method 0x1c9352e2. - // - // Solidity: function setUint32(uint32 u32) returns(uint32) - public Transaction setUint32(TransactOpts opts, BigInt u32) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setUint32(u32);args.set(0,arg0); - - return this.Contract.transact(opts, "setUint32" , args); - } - - // setUint64 is a paid mutator transaction binding the contract method 0x822cba69. - // - // Solidity: function setUint64(uint64 u64) returns(uint64) - public Transaction setUint64(TransactOpts opts, BigInt u64) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setUint64(u64);args.set(0,arg0); - - return this.Contract.transact(opts, "setUint64" , args); - } - - // setUint8 is a paid mutator transaction binding the contract method 0x1774e646. - // - // Solidity: function setUint8(uint8 u8) returns(uint8) - public Transaction setUint8(TransactOpts opts, BigInt u8) throws Exception { - Interfaces args = Geth.newInterfaces(1); - Interface arg0 = Geth.newInterface();arg0.setUint8(u8);args.set(0,arg0); - - return this.Contract.transact(opts, "setUint8" , args); - } -} -`, - }, - } - for i, c := range cases { - binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil, nil) - if err != nil { - t.Fatalf("test %d: failed to generate binding: %v", i, err) - } - // Remove empty lines - removeEmptys := func(input string) string { - lines := strings.Split(input, "\n") - var index int - for _, line := range lines { - if strings.TrimSpace(line) != "" { - lines[index] = line - index += 1 - } - } - lines = lines[:index] - return strings.Join(lines, "\n") - } - binding = removeEmptys(binding) - expect := removeEmptys(c.expected) - if binding != expect { - t.Fatalf("test %d: generated binding mismatch, has %s, want %s", i, binding, c.expected) - } - } -} diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 2269ec44..c22eb4ae 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -24,7 +24,6 @@ type tmplData struct { Contracts map[string]*tmplContract // List of contracts to generate into this file Libraries map[string]string // Map the bytecode's link pattern to the library name Structs map[string]*tmplStruct // Contract struct type definitions - Zion bool } // tmplContract contains the data needed to generate an individual contract binding. @@ -76,8 +75,7 @@ type tmplStruct struct { // tmplSource is language to template mapping containing all the supported // programming languages the package can generate to. var tmplSource = map[Lang]string{ - LangGo: tmplSourceGo, - LangJava: tmplSourceJava, + LangGo: tmplSourceGo, } // tmplSourceGo is the Go source template that the generated Go contract binding @@ -91,6 +89,7 @@ package {{.Package}} import ( "math/big" "strings" + "errors" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -102,6 +101,7 @@ import ( // Reference imports to suppress errors if they are not otherwise used. var ( + _ = errors.New _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound @@ -109,6 +109,7 @@ var ( _ = common.Big1 _ = types.BloomLookup _ = event.NewSubscription + _ = abi.ConvertType ) {{$structs := .Structs}} @@ -120,60 +121,49 @@ var ( } {{end}} - - {{range $contract := .Contracts}} - {{if $.Zion}} - var ( - {{range .Calls}} - Method{{.Normalized.Name}} = "{{.Original.Name}}" - {{end}} - {{range .Transacts}} - Method{{.Normalized.Name}} = "{{.Original.Name}}" - {{end}} - ) - {{end}} - - {{if not $.Zion}} - var ( - {{range .Transacts}} - Method{{.Normalized.Name}} = "{{.Original.Name}}" - {{end}} - {{range .Calls}} - Method{{.Normalized.Name}} = "{{.Original.Name}}" - {{end}} - {{range .Events}} - Event{{.Normalized.Name}} = "{{.Original.Name}}" + // {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract. + var {{.Type}}MetaData = &bind.MetaData{ + ABI: "{{.InputABI}}", + {{if $contract.FuncSigs -}} + Sigs: map[string]string{ + {{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}", {{end}} - ) - {{end}} - + }, + {{end -}} + {{if .InputBin -}} + Bin: "0x{{.InputBin}}", + {{end}} + } // {{.Type}}ABI is the input ABI used to generate the binding from. - const {{.Type}}ABI = "{{.InputABI}}" + // Deprecated: Use {{.Type}}MetaData.ABI instead. + var {{.Type}}ABI = {{.Type}}MetaData.ABI {{if $contract.FuncSigs}} + // Deprecated: Use {{.Type}}MetaData.Sigs instead. // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. - var {{.Type}}FuncSigs = map[string]string{ - {{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}", - {{end}} - } + var {{.Type}}FuncSigs = {{.Type}}MetaData.Sigs {{end}} {{if .InputBin}} // {{.Type}}Bin is the compiled bytecode used for deploying new contracts. - var {{.Type}}Bin = "0x{{.InputBin}}" + // Deprecated: Use {{.Type}}MetaData.Bin instead. + var {{.Type}}Bin = {{.Type}}MetaData.Bin // Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it. func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) { - parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI)) + parsed, err := {{.Type}}MetaData.GetAbi() if err != nil { return common.Address{}, nil, nil, err } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } {{range $pattern, $name := .Libraries}} {{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend) - {{$contract.Type}}Bin = strings.Replace({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:], -1) + {{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:]) {{end}} - address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}}) + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}}) if err != nil { return common.Address{}, nil, nil, err } @@ -278,11 +268,11 @@ var ( // bind{{.Type}} binds a generic wrapper to an already deployed contract. func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI)) + parsed, err := {{.Type}}MetaData.GetAbi() if err != nil { return nil, err } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil } // Call invokes the (constant) contract method with params as input values and @@ -579,140 +569,3 @@ var ( {{end}} {{end}} ` - -// 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! - -package {{.Package}}; - -import org.ethereum.geth.*; -import java.util.*; - -{{$structs := .Structs}} -{{range $contract := .Contracts}} -{{if not .Library}}public {{end}}class {{.Type}} { - // ABI is the input ABI used to generate the binding from. - public final static String ABI = "{{.InputABI}}"; - {{if $contract.FuncSigs}} - // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. - public final static Map {{.Type}}FuncSigs; - static { - Hashtable temp = new Hashtable(); - {{range $strsig, $binsig := .FuncSigs}}temp.put("{{$binsig}}", "{{$strsig}}"); - {{end}} - {{.Type}}FuncSigs = Collections.unmodifiableMap(temp); - } - {{end}} - {{if .InputBin}} - // BYTECODE is the compiled bytecode used for deploying new contracts. - public final static String BYTECODE = "0x{{.InputBin}}"; - - // deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it. - public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { - Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}}); - String bytecode = BYTECODE; - {{if .Libraries}} - - // "link" contract to dependent libraries by deploying them first. - {{range $pattern, $name := .Libraries}} - {{capitalise $name}} {{decapitalise $name}}Inst = {{capitalise $name}}.deploy(auth, client); - bytecode = bytecode.replace("__${{$pattern}}$__", {{decapitalise $name}}Inst.Address.getHex().substring(2)); - {{end}} - {{end}} - {{range $index, $element := .Constructor.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}}); - {{end}} - return new {{.Type}}(Geth.deployContract(auth, ABI, Geth.decodeFromHex(bytecode), client, args)); - } - - // Internal constructor used by contract deployment. - private {{.Type}}(BoundContract deployment) { - this.Address = deployment.getAddress(); - this.Deployer = deployment.getDeployer(); - this.Contract = deployment; - } - {{end}} - - // Ethereum address where this contract is located at. - public final Address Address; - - // Ethereum transaction in which this contract was deployed (if known!). - public final Transaction Deployer; - - // Contract instance bound to a blockchain address. - private final BoundContract Contract; - - // Creates a new instance of {{.Type}}, bound to a specific deployed contract. - public {{.Type}}(Address address, EthereumClient client) throws Exception { - this(Geth.bindContract(address, ABI, client)); - } - - {{range .Calls}} - {{if gt (len .Normalized.Outputs) 1}} - // {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}. - public class {{capitalise .Normalized.Name}}Results { - {{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type $structs}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}}; - {{end}} - } - {{end}} - - // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. - // - // Solidity: {{.Original.String}} - public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else if eq (len .Normalized.Outputs) 0}}void{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { - Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); - {{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}}); - {{end}} - - Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}}); - {{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type $structs) .Type}}(); results.set({{$index}}, result{{$index}}); - {{end}} - - if (opts == null) { - opts = Geth.newCallOpts(); - } - this.Contract.call(opts, results, "{{.Original.Name}}", args); - {{if gt (len .Normalized.Outputs) 1}} - {{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results(); - {{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type $structs) .Type}}(); - {{end}} - return result; - {{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type $structs) .Type}}();{{end}} - {{end}} - } - {{end}} - - {{range .Transacts}} - // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. - // - // Solidity: {{.Original.String}} - public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { - Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); - {{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}}); - {{end}} - return this.Contract.transact(opts, "{{.Original.Name}}" , args); - } - {{end}} - - {{if .Fallback}} - // Fallback is a paid mutator transaction binding the contract fallback function. - // - // Solidity: {{.Fallback.Original.String}} - public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception { - return this.Contract.rawTransact(opts, calldata); - } - {{end}} - - {{if .Receive}} - // Receive is a paid mutator transaction binding the contract receive function. - // - // Solidity: {{.Receive.Original.String}} - public Transaction Receive(TransactOpts opts) throws Exception { - return this.Contract.rawTransact(opts, null); - } - {{end}} -} -{{end}} -` diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index 9f9b7a00..679cdc84 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -53,17 +53,21 @@ var waitDeployedTests = map[string]struct { } func TestWaitDeployed(t *testing.T) { + core.CheckAllocWithTotalSupply = false for name, test := range waitDeployedTests { backend := backends.NewSimulatedBackend( core.GenesisAlloc{ - crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)}, + crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, }, 10000000, ) defer backend.Close() - // Create the transaction. - tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code)) + // Create the transaction + head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + + tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code)) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) // Wait for it to get mined in the background. @@ -99,15 +103,18 @@ func TestWaitDeployed(t *testing.T) { func TestWaitDeployedCornerCases(t *testing.T) { backend := backends.NewSimulatedBackend( core.GenesisAlloc{ - crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)}, + crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, }, 10000000, ) defer backend.Close() + head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + // Create a transaction to an account. code := "6060604052600a8060106000396000f360606040526008565b00" - tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, big.NewInt(1), common.FromHex(code)) + tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -119,7 +126,7 @@ func TestWaitDeployedCornerCases(t *testing.T) { } // Create a transaction that is not mined. - tx = types.NewContractCreation(1, big.NewInt(0), 3000000, big.NewInt(1), common.FromHex(code)) + tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) go func() { diff --git a/accounts/abi/error.go b/accounts/abi/error.go index f0f71b6c..f53c996d 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -17,66 +17,77 @@ package abi import ( + "bytes" "errors" "fmt" - "reflect" -) + "strings" -var ( - errBadBool = errors.New("abi: improperly encoded boolean value") + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" ) -// formatSliceString formats the reflection kind with the given slice size -// and returns a formatted string representation. -func formatSliceString(kind reflect.Kind, sliceSize int) string { - if sliceSize == -1 { - return fmt.Sprintf("[]%v", kind) - } - return fmt.Sprintf("[%d]%v", sliceSize, kind) -} +type Error struct { + Name string + Inputs Arguments + str string -// sliceTypeCheck checks that the given slice can by assigned to the reflection -// type in t. -func sliceTypeCheck(t Type, val reflect.Value) error { - if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { - return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type()) - } + // Sig contains the string signature according to the ABI spec. + // e.g. error foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string - if t.T == ArrayTy && val.Len() != t.Size { - return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) - } + // ID returns the canonical representation of the error's signature used by the + // abi definition to identify event names and types. + ID common.Hash +} - if t.Elem.T == SliceTy || t.Elem.T == ArrayTy { - if val.Len() > 0 { - return sliceTypeCheck(*t.Elem, val.Index(0)) +func NewError(name string, inputs Arguments) Error { + // sanitize inputs to remove inputs without names + // and precompute string and sig representation. + names := make([]string, len(inputs)) + types := make([]string, len(inputs)) + for i, input := range inputs { + if input.Name == "" { + inputs[i] = Argument{ + Name: fmt.Sprintf("arg%d", i), + Indexed: input.Indexed, + Type: input.Type, + } + } else { + inputs[i] = input } + // string representation + names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name) + if input.Indexed { + names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name) + } + // sig representation + types[i] = input.Type.String() } - if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { - return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) - } - return nil -} - -// typeCheck checks that the given reflection value can be assigned to the reflection -// type in t. -func typeCheck(t Type, value reflect.Value) error { - if t.T == SliceTy || t.T == ArrayTy { - return sliceTypeCheck(t, value) - } + str := fmt.Sprintf("error %v(%v)", name, strings.Join(names, ", ")) + sig := fmt.Sprintf("%v(%v)", name, strings.Join(types, ",")) + id := common.BytesToHash(crypto.Keccak256([]byte(sig))) - // Check base type validity. Element types will be checked later on. - if t.GetType().Kind() != value.Kind() { - return typeErr(t.GetType().Kind(), value.Kind()) - } else if t.T == FixedBytesTy && t.Size != value.Len() { - return typeErr(t.GetType(), value.Type()) - } else { - return nil + return Error{ + Name: name, + Inputs: inputs, + str: str, + Sig: sig, + ID: id, } +} +func (e *Error) String() string { + return e.str } -// typeErr returns a formatted type casting error. -func typeErr(expected, got interface{}) error { - return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected) +func (e *Error) Unpack(data []byte) (interface{}, error) { + if len(data) < 4 { + return "", errors.New("invalid data for unpacking") + } + if !bytes.Equal(data[:4], e.ID[:4]) { + return "", errors.New("invalid data for unpacking") + } + return e.Inputs.Unpack(data[4:]) } diff --git a/accounts/abi/error_handling.go b/accounts/abi/error_handling.go new file mode 100644 index 00000000..c106e9ac --- /dev/null +++ b/accounts/abi/error_handling.go @@ -0,0 +1,89 @@ +// 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 abi + +import ( + "errors" + "fmt" + "reflect" +) + +var ( + errBadBool = errors.New("abi: improperly encoded boolean value") + errBadUint8 = errors.New("abi: improperly encoded uint8 value") + errBadUint16 = errors.New("abi: improperly encoded uint16 value") + errBadUint32 = errors.New("abi: improperly encoded uint32 value") + errBadUint64 = errors.New("abi: improperly encoded uint64 value") + errBadInt8 = errors.New("abi: improperly encoded int8 value") + errBadInt16 = errors.New("abi: improperly encoded int16 value") + errBadInt32 = errors.New("abi: improperly encoded int32 value") + errBadInt64 = errors.New("abi: improperly encoded int64 value") +) + +// formatSliceString formats the reflection kind with the given slice size +// and returns a formatted string representation. +func formatSliceString(kind reflect.Kind, sliceSize int) string { + if sliceSize == -1 { + return fmt.Sprintf("[]%v", kind) + } + return fmt.Sprintf("[%d]%v", sliceSize, kind) +} + +// sliceTypeCheck checks that the given slice can by assigned to the reflection +// type in t. +func sliceTypeCheck(t Type, val reflect.Value) error { + if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { + return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type()) + } + + if t.T == ArrayTy && val.Len() != t.Size { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) + } + + if t.Elem.T == SliceTy || t.Elem.T == ArrayTy { + if val.Len() > 0 { + return sliceTypeCheck(*t.Elem, val.Index(0)) + } + } + + if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) + } + return nil +} + +// typeCheck checks that the given reflection value can be assigned to the reflection +// type in t. +func typeCheck(t Type, value reflect.Value) error { + if t.T == SliceTy || t.T == ArrayTy { + return sliceTypeCheck(t, value) + } + + // Check base type validity. Element types will be checked later on. + if t.GetType().Kind() != value.Kind() { + return typeErr(t.GetType().Kind(), value.Kind()) + } else if t.T == FixedBytesTy && t.Size != value.Len() { + return typeErr(t.GetType(), value.Type()) + } else { + return nil + } +} + +// typeErr returns a formatted type casting error. +func typeErr(expected, got interface{}) error { + return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected) +} diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 11248e07..1f84b111 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -25,16 +25,19 @@ import ( ) // 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 } +// given type, e.g. turn this code: +// +// 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) { @@ -99,7 +102,7 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value { func set(dst, src reflect.Value) error { dstType, srcType := dst.Type(), src.Type() switch { - case dstType.Kind() == reflect.Interface && dst.Elem().IsValid(): + case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Ptr || dst.Elem().CanSet()): return set(dst.Elem(), src) case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeOf(big.Int{}): return set(dst.Elem(), src) @@ -123,15 +126,8 @@ func set(dst, src reflect.Value) error { func setSlice(dst, src reflect.Value) error { slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len()) for i := 0; i < src.Len(); i++ { - if src.Index(i).Kind() == reflect.Struct { - if err := set(slice.Index(i), src.Index(i)); err != nil { - return err - } - } else { - // e.g. [][32]uint8 to []common.Hash - if err := set(slice.Index(i), src.Index(i)); err != nil { - return err - } + if err := set(slice.Index(i), src.Index(i)); err != nil { + return err } } if dst.CanSet() { @@ -177,11 +173,13 @@ func setStruct(dst, src reflect.Value) error { } // mapArgNamesToStructFields maps a slice of argument names to struct fields. -// first round: for each Exportable field that contains a `abi:""` tag -// and this field name exists in the given argument name list, pair them together. -// second round: for each argument name that has not been already linked, -// find what variable is expected to be mapped into, if it exists and has not been -// used, pair them. +// +// first round: for each Exportable field that contains a `abi:""` tag and this field name +// exists in the given argument name list, pair them together. +// +// second round: for each argument name that has not been already linked, find what +// variable is expected to be mapped into, if it exists and has not been used, pair them. +// // Note this function assumes the given value is a struct value. func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) { typ := value.Type() @@ -227,7 +225,6 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri // second round ~~~ for _, argName := range argNames { - structFieldName := ToCamelCase(argName) if structFieldName == "" { diff --git a/accounts/abi/type.go b/accounts/abi/type.go index ffa3acaf..7f74907a 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -23,6 +23,8 @@ import ( "regexp" "strconv" "strings" + "unicode" + "unicode/utf8" "github.com/ethereum/go-ethereum/common" ) @@ -152,6 +154,9 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty if varSize == 0 { typ.T = BytesTy } else { + if varSize > 32 { + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } typ.T = FixedBytesTy typ.Size = varSize } @@ -161,19 +166,26 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty elems []*Type names []string expression string // canonical parameter expression + used = make(map[string]bool) ) expression += "(" - overloadedNames := make(map[string]string) for idx, c := range components { cType, err := NewType(c.Type, c.InternalType, c.Components) if err != nil { return Type{}, err } - fieldName, err := overloadedArgName(c.Name, overloadedNames) + name := ToCamelCase(c.Name) + if name == "" { + return Type{}, errors.New("abi: purely anonymous or underscored field is not supported") + } + fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] }) if err != nil { return Type{}, err } - overloadedNames[fieldName] = fieldName + used[fieldName] = true + if !isValidFieldName(fieldName) { + return Type{}, fmt.Errorf("field %d has invalid name", idx) + } fields = append(fields, reflect.StructField{ Name: fieldName, // reflect.StructOf will panic for any exported field. Type: cType.GetType(), @@ -201,7 +213,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty if internalType != "" && strings.HasPrefix(internalType, structPrefix) { // Foo.Bar type definition is not allowed in golang, // convert the format to FooBar - typ.TupleRawName = strings.Replace(internalType[len(structPrefix):], ".", "", -1) + typ.TupleRawName = strings.ReplaceAll(internalType[len(structPrefix):], ".", "") } case "function": @@ -250,20 +262,6 @@ func (t Type) GetType() reflect.Type { } } -func overloadedArgName(rawName string, names map[string]string) (string, error) { - fieldName := ToCamelCase(rawName) - if fieldName == "" { - return "", errors.New("abi: purely anonymous or underscored field is not supported") - } - // Handle overloaded fieldNames - _, ok := names[fieldName] - for idx := 0; ok; idx++ { - fieldName = fmt.Sprintf("%s%d", ToCamelCase(rawName), idx) - _, ok = names[fieldName] - } - return fieldName, nil -} - // String implements Stringer. func (t Type) String() (out string) { return t.stringKind @@ -399,3 +397,30 @@ func getTypeSize(t Type) int { } return 32 } + +// isLetter reports whether a given 'rune' is classified as a Letter. +// This method is copied from reflect/type.go +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) +} + +// isValidFieldName checks if a string is a valid (struct) field name or not. +// +// According to the language spec, a field name should be an identifier. +// +// identifier = letter { letter | unicode_digit } . +// letter = unicode_letter | "_" . +// This method is copied from reflect/type.go +func isValidFieldName(fieldName string) bool { + for i, c := range fieldName { + if i == 0 && !isLetter(c) { + return false + } + + if !(isLetter(c) || unicode.IsDigit(c)) { + return false + } + } + + return len(fieldName) > 0 +} diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index ec069849..1e778a6f 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -19,6 +19,7 @@ package abi import ( "encoding/binary" "fmt" + "math" "math/big" "reflect" @@ -33,43 +34,72 @@ var ( ) // ReadInteger reads the integer based on its kind and returns the appropriate value. -func ReadInteger(typ Type, b []byte) interface{} { +func ReadInteger(typ Type, b []byte) (interface{}, error) { + ret := new(big.Int).SetBytes(b) + if typ.T == UintTy { + u64, isu64 := ret.Uint64(), ret.IsUint64() switch typ.Size { case 8: - return b[len(b)-1] + if !isu64 || u64 > math.MaxUint8 { + return nil, errBadUint8 + } + return byte(u64), nil case 16: - return binary.BigEndian.Uint16(b[len(b)-2:]) + if !isu64 || u64 > math.MaxUint16 { + return nil, errBadUint16 + } + return uint16(u64), nil case 32: - return binary.BigEndian.Uint32(b[len(b)-4:]) + if !isu64 || u64 > math.MaxUint32 { + return nil, errBadUint32 + } + return uint32(u64), nil case 64: - return binary.BigEndian.Uint64(b[len(b)-8:]) + if !isu64 { + return nil, errBadUint64 + } + return u64, nil default: // the only case left for unsigned integer is uint256. - return new(big.Int).SetBytes(b) + return ret, nil } } + + // big.SetBytes can't tell if a number is negative or positive in itself. + // On EVM, if the returned number > max int256, it is negative. + // A number is > max int256 if the bit at position 255 is set. + if ret.Bit(255) == 1 { + ret.Add(MaxUint256, new(big.Int).Neg(ret)) + ret.Add(ret, common.Big1) + ret.Neg(ret) + } + i64, isi64 := ret.Int64(), ret.IsInt64() switch typ.Size { case 8: - return int8(b[len(b)-1]) + if !isi64 || i64 < math.MinInt8 || i64 > math.MaxInt8 { + return nil, errBadInt8 + } + return int8(i64), nil case 16: - return int16(binary.BigEndian.Uint16(b[len(b)-2:])) + if !isi64 || i64 < math.MinInt16 || i64 > math.MaxInt16 { + return nil, errBadInt16 + } + return int16(i64), nil case 32: - return int32(binary.BigEndian.Uint32(b[len(b)-4:])) + if !isi64 || i64 < math.MinInt32 || i64 > math.MaxInt32 { + return nil, errBadInt32 + } + return int32(i64), nil case 64: - return int64(binary.BigEndian.Uint64(b[len(b)-8:])) + if !isi64 { + return nil, errBadInt64 + } + return i64, nil default: // the only case left for integer is int256 - // big.SetBytes can't tell if a number is negative or positive in itself. - // On EVM, if the returned number > max int256, it is negative. - // A number is > max int256 if the bit at position 255 is set. - ret := new(big.Int).SetBytes(b) - if ret.Bit(255) == 1 { - ret.Add(MaxUint256, new(big.Int).Neg(ret)) - ret.Add(ret, common.Big1) - ret.Neg(ret) - } - return ret + + return ret, nil } } @@ -115,7 +145,6 @@ func ReadFixedBytes(t Type, word []byte) (interface{}, error) { reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) return array.Interface(), nil - } // forEachUnpack iteratively unpack elements. @@ -124,7 +153,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) } if start+32*size > len(output) { - return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) + return nil, fmt.Errorf("abi: cannot marshal into go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) } // this value will become our slice or our array, depending on the type @@ -163,6 +192,9 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) { virtualArgs := 0 for index, elem := range t.TupleElems { marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) + if err != nil { + return nil, err + } if elem.T == ArrayTy && !isDynamicType(*elem) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -180,9 +212,6 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) { // coded as just like uint256,bool,uint256 virtualArgs += getTypeSize(*elem)/32 - 1 } - if err != nil { - return nil, err - } retval.Field(index).Set(reflect.ValueOf(marshalledValue)) } return retval.Interface(), nil @@ -235,7 +264,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { case StringTy: // variable arrays are written at the end of the return bytes return string(output[begin : begin+length]), nil case IntTy, UintTy: - return ReadInteger(t, returnOutput), nil + return ReadInteger(t, returnOutput) case BoolTy: return readBool(returnOutput) case AddressTy: @@ -255,7 +284,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { // 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 := new(big.Int).SetBytes(output[index : index+32]) bigOffsetEnd.Add(bigOffsetEnd, common.Big32) outputLength := big.NewInt(int64(len(output))) @@ -268,11 +297,9 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err } offsetEnd := int(bigOffsetEnd.Uint64()) - lengthBig := big.NewInt(0).SetBytes(output[offsetEnd-32 : offsetEnd]) + lengthBig := new(big.Int).SetBytes(output[offsetEnd-32 : offsetEnd]) - totalSize := big.NewInt(0) - totalSize.Add(totalSize, bigOffsetEnd) - totalSize.Add(totalSize, lengthBig) + totalSize := new(big.Int).Add(bigOffsetEnd, lengthBig) if totalSize.BitLen() > 63 { return 0, 0, fmt.Errorf("abi: length larger than int64: %v", totalSize) } @@ -287,10 +314,10 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err // tuplePointsTo resolves the location reference for dynamic tuple. func tuplePointsTo(index int, output []byte) (start int, err error) { - offset := big.NewInt(0).SetBytes(output[index : index+32]) + offset := new(big.Int).SetBytes(output[index : index+32]) outputLen := big.NewInt(int64(len(output))) - if offset.Cmp(big.NewInt(int64(len(output)))) > 0 { + if offset.Cmp(outputLen) > 0 { return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen) } if offset.BitLen() > 63 { diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index b88f7780..6dd2db0d 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/hex" "fmt" + "math" "math/big" "reflect" "strconv" @@ -201,6 +202,23 @@ var unpackTests = []unpackTest{ IntOne *big.Int }{big.NewInt(1)}, }, + { + def: `[{"type":"bool"}]`, + enc: "", + want: false, + err: "abi: attempting to unmarshall an empty string while arguments are expected", + }, + { + def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`, + enc: "", + want: false, + err: "abi: attempting to unmarshall an empty string while arguments are expected", + }, + { + def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`, + enc: "", + want: false, + }, } // TestLocalUnpackTests runs test specially designed only for unpacking. @@ -335,6 +353,11 @@ func TestMethodMultiReturn(t *testing.T) { &[]interface{}{&expected.Int, &expected.String}, "", "Can unpack into a slice", + }, { + &[]interface{}{&bigint, ""}, + &[]interface{}{&expected.Int, expected.String}, + "", + "Can unpack into a slice without indirection", }, { &[2]interface{}{&bigint, new(string)}, &[2]interface{}{&expected.Int, &expected.String}, @@ -407,7 +430,7 @@ func TestMultiReturnWithStringArray(t *testing.T) { } buff := new(bytes.Buffer) buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000")) - temp, _ := big.NewInt(0).SetString("30000000000000000000", 10) + temp, _ := new(big.Int).SetString("30000000000000000000", 10) ret1, ret1Exp := new([3]*big.Int), [3]*big.Int{big.NewInt(1545304298), big.NewInt(6), temp} ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f") ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"} @@ -762,20 +785,24 @@ func TestUnpackTuple(t *testing.T) { buff.Write(common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) // ret[b] = -1 // If the result is single tuple, use struct as return value container directly. - v := struct { + type v struct { A *big.Int B *big.Int - }{new(big.Int), new(big.Int)} + } + type r struct { + Result v + } + var ret0 = new(r) + err = abi.UnpackIntoInterface(ret0, "tuple", buff.Bytes()) - err = abi.UnpackIntoInterface(&v, "tuple", buff.Bytes()) if err != nil { t.Error(err) } else { - if v.A.Cmp(big.NewInt(1)) != 0 { - t.Errorf("unexpected value unpacked: want %x, got %x", 1, v.A) + if ret0.Result.A.Cmp(big.NewInt(1)) != 0 { + t.Errorf("unexpected value unpacked: want %x, got %x", 1, ret0.Result.A) } - if v.B.Cmp(big.NewInt(-1)) != 0 { - t.Errorf("unexpected value unpacked: want %x, got %x", -1, v.B) + if ret0.Result.B.Cmp(big.NewInt(-1)) != 0 { + t.Errorf("unexpected value unpacked: want %x, got %x", -1, ret0.Result.B) } } @@ -917,3 +944,164 @@ func TestOOMMaliciousInput(t *testing.T) { } } } + +func TestPackAndUnpackIncompatibleNumber(t *testing.T) { + var encodeABI Arguments + uint256Ty, err := NewType("uint256", "", nil) + if err != nil { + panic(err) + } + encodeABI = Arguments{ + {Type: uint256Ty}, + } + + maxU64, ok := new(big.Int).SetString(strconv.FormatUint(math.MaxUint64, 10), 10) + if !ok { + panic("bug") + } + maxU64Plus1 := new(big.Int).Add(maxU64, big.NewInt(1)) + cases := []struct { + decodeType string + inputValue *big.Int + err error + expectValue interface{} + }{ + { + decodeType: "uint8", + inputValue: big.NewInt(math.MaxUint8 + 1), + err: errBadUint8, + }, + { + decodeType: "uint8", + inputValue: big.NewInt(math.MaxUint8), + err: nil, + expectValue: uint8(math.MaxUint8), + }, + { + decodeType: "uint16", + inputValue: big.NewInt(math.MaxUint16 + 1), + err: errBadUint16, + }, + { + decodeType: "uint16", + inputValue: big.NewInt(math.MaxUint16), + err: nil, + expectValue: uint16(math.MaxUint16), + }, + { + decodeType: "uint32", + inputValue: big.NewInt(math.MaxUint32 + 1), + err: errBadUint32, + }, + { + decodeType: "uint32", + inputValue: big.NewInt(math.MaxUint32), + err: nil, + expectValue: uint32(math.MaxUint32), + }, + { + decodeType: "uint64", + inputValue: maxU64Plus1, + err: errBadUint64, + }, + { + decodeType: "uint64", + inputValue: maxU64, + err: nil, + expectValue: uint64(math.MaxUint64), + }, + { + decodeType: "uint256", + inputValue: maxU64Plus1, + err: nil, + expectValue: maxU64Plus1, + }, + { + decodeType: "int8", + inputValue: big.NewInt(math.MaxInt8 + 1), + err: errBadInt8, + }, + { + decodeType: "int8", + inputValue: big.NewInt(math.MinInt8 - 1), + err: errBadInt8, + }, + { + decodeType: "int8", + inputValue: big.NewInt(math.MaxInt8), + err: nil, + expectValue: int8(math.MaxInt8), + }, + { + decodeType: "int16", + inputValue: big.NewInt(math.MaxInt16 + 1), + err: errBadInt16, + }, + { + decodeType: "int16", + inputValue: big.NewInt(math.MinInt16 - 1), + err: errBadInt16, + }, + { + decodeType: "int16", + inputValue: big.NewInt(math.MaxInt16), + err: nil, + expectValue: int16(math.MaxInt16), + }, + { + decodeType: "int32", + inputValue: big.NewInt(math.MaxInt32 + 1), + err: errBadInt32, + }, + { + decodeType: "int32", + inputValue: big.NewInt(math.MinInt32 - 1), + err: errBadInt32, + }, + { + decodeType: "int32", + inputValue: big.NewInt(math.MaxInt32), + err: nil, + expectValue: int32(math.MaxInt32), + }, + { + decodeType: "int64", + inputValue: new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(1)), + err: errBadInt64, + }, + { + decodeType: "int64", + inputValue: new(big.Int).Sub(big.NewInt(math.MinInt64), big.NewInt(1)), + err: errBadInt64, + }, + { + decodeType: "int64", + inputValue: big.NewInt(math.MaxInt64), + err: nil, + expectValue: int64(math.MaxInt64), + }, + } + for i, testCase := range cases { + packed, err := encodeABI.Pack(testCase.inputValue) + if err != nil { + panic(err) + } + ty, err := NewType(testCase.decodeType, "", nil) + if err != nil { + panic(err) + } + decodeABI := Arguments{ + {Type: ty}, + } + decoded, err := decodeABI.Unpack(packed) + if err != testCase.err { + t.Fatalf("Expected error %v, actual error %v. case %d", testCase.err, err, i) + } + if err != nil { + continue + } + if !reflect.DeepEqual(decoded[0], testCase.expectValue) { + t.Fatalf("Expected value %v, actual value %v", testCase.expectValue, decoded[0]) + } + } +} diff --git a/accounts/abi/utils.go b/accounts/abi/utils.go new file mode 100644 index 00000000..e4e6034c --- /dev/null +++ b/accounts/abi/utils.go @@ -0,0 +1,40 @@ +// Copyright 2022 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 "fmt" + +// ResolveNameConflict returns the next available name for a given thing. +// This helper can be used for lots of purposes: +// +// - In solidity function overloading is supported, this function can fix +// the name conflicts of overloaded functions. +// - In golang binding generation, the parameter(in function, event, error, +// and struct definition) name will be converted to camelcase style which +// may eventually lead to name conflicts. +// +// Name conflicts are mostly resolved by adding number suffix. e.g. if the abi contains +// Methods "send" and "send1", ResolveNameConflict would return "send2" for input "send". +func ResolveNameConflict(rawName string, used func(string) bool) string { + name := rawName + ok := used(name) + for idx := 0; ok; idx++ { + name = fmt.Sprintf("%s%d", rawName, idx) + ok = used(name) + } + return name +} \ No newline at end of file diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 15d519a6..19da9ff5 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -135,11 +135,6 @@ func abigen(c *cli.Context) error { switch c.GlobalString(langFlag.Name) { case "go": lang = bind.LangGo - case "java": - lang = bind.LangJava - case "objc": - lang = bind.LangObjC - utils.Fatalf("Objc binding generation is uncompleted") default: utils.Fatalf("Unsupported destination language \"%s\" (--lang)", c.GlobalString(langFlag.Name)) } diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index ec98833a..d1e99acd 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/core" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/p2p" "github.com/stretchr/testify/assert" @@ -124,6 +125,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) { + core.CheckAllocWithTotalSupply = false chainFile, err := filepath.Abs("./testdata/chain.rlp") if err != nil { t.Fatal(err) diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go index 6e321715..bee000cd 100644 --- a/cmd/devp2p/internal/ethtest/suite_test.go +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -35,6 +35,7 @@ var ( ) func TestEthSuite(t *testing.T) { + t.Skipf("Ethash blocks inserts will be skipped for now") geth, err := runGeth() if err != nil { t.Fatalf("could not run geth: %v", err) @@ -101,7 +102,6 @@ func setupGeth(stack *node.Node) error { if err != nil { return err } - _, err = backend.BlockChain().InsertChain(chain.blocks[1:]) return err } diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json index d8b5d225..0782c39f 100644 --- a/cmd/devp2p/internal/ethtest/testdata/genesis.json +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -8,6 +8,8 @@ "byzantiumBlock": 0, "ethash": {} }, + "community_rate": 2000, + "community_address": "0x79ad3ca3faa0F30f4A0A2839D2DaEb4Eb6B6820D", "nonce": "0xdeadbeefdeadbeef", "timestamp": "0x0", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/cmd/faucet/faucet_test.go b/cmd/faucet/faucet_test.go index 4f3e4708..58a1f22b 100644 --- a/cmd/faucet/faucet_test.go +++ b/cmd/faucet/faucet_test.go @@ -23,6 +23,8 @@ import ( ) func TestFacebook(t *testing.T) { + // TODO: Remove facebook auth or implement facebook api, which seems to require an API key + t.Skipf("The facebook access is flaky, needs to be reimplemented or removed") for _, tt := range []struct { url string want common.Address diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c4ebf648..5d1fe7a1 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/core" "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -129,6 +130,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if err != nil { utils.Fatalf("Failed to create the protocol stack: %v", err) } + if ctx.GlobalBool(utils.DeveloperFlag.Name) { + core.CheckAllocWithTotalSupply = false + } utils.SetEthConfig(ctx, stack, &cfg.Eth) if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) @@ -156,9 +160,12 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { } } - // Configure GraphQL if requested - if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { - utils.RegisterGraphQLService(stack, backend, cfg.Node) + // Configure log filter RPC API. + filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth) + + // Configure GraphQL if requested. + if ctx.IsSet(utils.GraphQLEnabledFlag.Name) { + utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index b7f26b36..3e3545ec 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -25,20 +25,21 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/params" ) // Genesis block for nodes which don't care about the DAO fork (i.e. not configured) var daoOldGenesis = `{ - "alloc" : {}, + "alloc" : { "0x0000000000000000000000000000000000000001": { "balance": "0x52b7d2dcc80cd2e4000000" }}, "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", - "extraData" : "", + "extraData" : "0x0000000000000000000000000000000000000000000000000000000000000000c68001c080c080", "gasLimit" : "0x2fefd8", "nonce" : "0x0000000000000042", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", + "community_rate": 2000, + "community_address": "0x79ad3ca3faa0F30f4A0A2839D2DaEb4Eb6B6820D", "config" : { "homesteadBlock" : 0 } @@ -46,15 +47,17 @@ var daoOldGenesis = `{ // Genesis block for nodes which actively oppose the DAO fork var daoNoForkGenesis = `{ - "alloc" : {}, + "alloc" : { "0x0000000000000000000000000000000000000001": { "balance": "0x52b7d2dcc80cd2e4000000" }}, "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", - "extraData" : "", + "extraData" : "0x0000000000000000000000000000000000000000000000000000000000000000c68001c080c080", "gasLimit" : "0x2fefd8", "nonce" : "0x0000000000000042", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", + "community_rate": 2000, + "community_address": "0x79ad3ca3faa0F30f4A0A2839D2DaEb4Eb6B6820D", "config" : { "homesteadBlock" : 0, "daoForkBlock" : 314, @@ -64,15 +67,17 @@ var daoNoForkGenesis = `{ // Genesis block for nodes which actively support the DAO fork var daoProForkGenesis = `{ - "alloc" : {}, + "alloc" : { "0x0000000000000000000000000000000000000001": { "balance": "0x52b7d2dcc80cd2e4000000" }}, "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", - "extraData" : "", + "extraData" : "0x0000000000000000000000000000000000000000000000000000000000000000c68001c080c080", "gasLimit" : "0x2fefd8", "nonce" : "0x0000000000000042", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", + "community_rate": 2000, + "community_address": "0x79ad3ca3faa0F30f4A0A2839D2DaEb4Eb6B6820D", "config" : { "homesteadBlock" : 0, "daoForkBlock" : 314, @@ -80,7 +85,7 @@ var daoProForkGenesis = `{ } }` -var daoGenesisHash = common.HexToHash("5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0") +var daoGenesisHash = common.HexToHash("0x6d8fc923564013394cab956685849483496544fdc301fd2b00aa7aca9193d374") var daoGenesisForkBlock = big.NewInt(314) // TestDAOForkBlockNewChain tests that the DAO hard-fork number and the nodes support/opposition is correctly @@ -91,8 +96,6 @@ func TestDAOForkBlockNewChain(t *testing.T) { expectBlock *big.Int expectVote bool }{ - // Test DAO Default Mainnet - {"", params.MainnetChainConfig.DAOForkBlock, true}, // test DAO Init Old Privnet {daoOldGenesis, nil, false}, // test DAO Default No Fork Privnet diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index 0563ef3c..5330f54a 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -31,10 +31,12 @@ var customGenesisTests = []struct { // Genesis file with an empty chain configuration (ensure missing fields work) { genesis: `{ - "alloc" : {}, + "alloc" : { "0x0000000000000000000000000000000000000001": { "balance": "0x52b7d2dcc80cd2e4000000" }}, + "extraData" : "0x0000000000000000000000000000000000000000000000000000000000000000c68001c080c080", + "community_rate": 2000, + "community_address": "0x79ad3ca3faa0F30f4A0A2839D2DaEb4Eb6B6820D", "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", - "extraData" : "", "gasLimit" : "0x2fefd8", "nonce" : "0x0000000000001338", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -48,10 +50,12 @@ var customGenesisTests = []struct { // Genesis file with specific chain configurations { genesis: `{ - "alloc" : {}, + "alloc" : { "0x0000000000000000000000000000000000000001": { "balance": "0x52b7d2dcc80cd2e4000000" }}, + "extraData" : "0x0000000000000000000000000000000000000000000000000000000000000000c68001c080c080", + "community_rate": 2000, + "community_address": "0x79ad3ca3faa0F30f4A0A2839D2DaEb4Eb6B6820D", "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", - "extraData" : "", "gasLimit" : "0x2fefd8", "nonce" : "0x0000000000001339", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go index 527c38a6..cb5de332 100644 --- a/cmd/geth/run_test.go +++ b/cmd/geth/run_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/docker/docker/pkg/reexec" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/internal/cmdtest" "github.com/ethereum/go-ethereum/rpc" ) @@ -46,6 +47,7 @@ type testgeth struct { } func init() { + core.CheckAllocWithTotalSupply = false // Run the app if we've been exec'd as "geth-test" in runGeth. reexec.Register("geth-test", func() { if err := app.Run(os.Args); err != nil { diff --git a/cmd/geth/testdata/clique.json b/cmd/geth/testdata/clique.json index b54b4a7d..f9dea8cb 100644 --- a/cmd/geth/testdata/clique.json +++ b/cmd/geth/testdata/clique.json @@ -20,5 +20,6 @@ "02f0d131f1f97aef08aec6e3291b957d9efe7105": { "balance": "300000" } - } + }, + "community_rate": 20 } \ No newline at end of file diff --git a/cmd/puppeth/genesis_test.go b/cmd/puppeth/genesis_test.go index aaa72d73..7bd8950b 100644 --- a/cmd/puppeth/genesis_test.go +++ b/cmd/puppeth/genesis_test.go @@ -19,6 +19,7 @@ package main import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "reflect" "strings" @@ -90,6 +91,7 @@ func TestParitySturebyConverter(t *testing.T) { t.Fatalf("could not read file: %v", err) } if !bytes.Equal(expBlob, enc) { + fmt.Println(string(enc)) t.Fatalf("chainspec mismatch") } } diff --git a/cmd/puppeth/testdata/stureby_aleth.json b/cmd/puppeth/testdata/stureby_aleth.json index d18ba385..2ba083d3 100644 --- a/cmd/puppeth/testdata/stureby_aleth.json +++ b/cmd/puppeth/testdata/stureby_aleth.json @@ -14,7 +14,7 @@ "minGasLimit": "0x1388", "maxGasLimit": "0x7fffffffffffffff", "tieBreakingGas": false, - "gasLimitBoundDivisor": "0x400", + "gasLimitBoundDivisor": "0x0400", "minimumDifficulty": "0x20000", "difficultyBoundDivisor": "0x800", "durationLimit": "0xd", diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c41286e4..67cb6b24 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -47,6 +47,7 @@ import ( "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/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" @@ -66,6 +67,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" pcsclite "github.com/gballet/go-libpcsclite" gopsutil "github.com/shirou/gopsutil/mem" "gopkg.in/urfave/cli.v1" @@ -1750,13 +1752,27 @@ func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url strin } } -// RegisterGraphQLService is a utility function to construct a new service and register it against a node. -func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.Config) { - if err := graphql.New(stack, backend, cfg.GraphQLCors, cfg.GraphQLVirtualHosts); err != nil { +// RegisterGraphQLService adds the GraphQL API to the node. +func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSystem *filters.FilterSystem, cfg *node.Config) { + err := graphql.New(stack, backend, filterSystem, cfg.GraphQLCors, cfg.GraphQLVirtualHosts) + if err != nil { Fatalf("Failed to register the GraphQL service: %v", err) } } +// RegisterFilterAPI adds the eth log filtering RPC API to the node. +func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem { + isLightClient := ethcfg.SyncMode == downloader.LightSync + filterSystem := filters.NewFilterSystem(backend, filters.Config{ + LogCacheSize: 32, + }) + stack.RegisterAPIs([]rpc.API{{ + Namespace: "eth", + Service: filters.NewFilterAPI(filterSystem, isLightClient), + }}) + return filterSystem +} + func SetupMetrics(ctx *cli.Context) { if metrics.Enabled { log.Info("Enabling metrics collection") diff --git a/common/lru/basiclru.go b/common/lru/basiclru.go new file mode 100644 index 00000000..a429157f --- /dev/null +++ b/common/lru/basiclru.go @@ -0,0 +1,223 @@ +// Copyright 2022 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 lru implements generically-typed LRU caches. +package lru + +// BasicLRU is a simple LRU cache. +// +// This type is not safe for concurrent use. +// The zero value is not valid, instances must be created using NewCache. +type BasicLRU[K comparable, V any] struct { + list *list[K] + items map[K]cacheItem[K, V] + cap int +} + +type cacheItem[K any, V any] struct { + elem *listElem[K] + value V +} + +// NewBasicLRU creates a new LRU cache. +func NewBasicLRU[K comparable, V any](capacity int) BasicLRU[K, V] { + if capacity <= 0 { + capacity = 1 + } + c := BasicLRU[K, V]{ + items: make(map[K]cacheItem[K, V]), + list: newList[K](), + cap: capacity, + } + return c +} + +// Add adds a value to the cache. Returns true if an item was evicted to store the new item. +func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) { + item, ok := c.items[key] + if ok { + // Already exists in cache. + item.value = value + c.items[key] = item + c.list.moveToFront(item.elem) + return false + } + + var elem *listElem[K] + if c.Len() >= c.cap { + elem = c.list.removeLast() + delete(c.items, elem.v) + evicted = true + } else { + elem = new(listElem[K]) + } + + // Store the new item. + // Note that, if another item was evicted, we re-use its list element here. + elem.v = key + c.items[key] = cacheItem[K, V]{elem, value} + c.list.pushElem(elem) + return evicted +} + +// Contains reports whether the given key exists in the cache. +func (c *BasicLRU[K, V]) Contains(key K) bool { + _, ok := c.items[key] + return ok +} + +// Get retrieves a value from the cache. This marks the key as recently used. +func (c *BasicLRU[K, V]) Get(key K) (value V, ok bool) { + item, ok := c.items[key] + if !ok { + return value, false + } + c.list.moveToFront(item.elem) + return item.value, true +} + +// GetOldest retrieves the least-recently-used item. +// Note that this does not update the item's recency. +func (c *BasicLRU[K, V]) GetOldest() (key K, value V, ok bool) { + lastElem := c.list.last() + if lastElem == nil { + return key, value, false + } + key = lastElem.v + item := c.items[key] + return key, item.value, true +} + +// Len returns the current number of items in the cache. +func (c *BasicLRU[K, V]) Len() int { + return len(c.items) +} + +// Peek retrieves a value from the cache, but does not mark the key as recently used. +func (c *BasicLRU[K, V]) Peek(key K) (value V, ok bool) { + item, ok := c.items[key] + return item.value, ok +} + +// Purge empties the cache. +func (c *BasicLRU[K, V]) Purge() { + c.list.init() + for k := range c.items { + delete(c.items, k) + } +} + +// Remove drops an item from the cache. Returns true if the key was present in cache. +func (c *BasicLRU[K, V]) Remove(key K) bool { + item, ok := c.items[key] + if ok { + delete(c.items, key) + c.list.remove(item.elem) + } + return ok +} + +// RemoveOldest drops the least recently used item. +func (c *BasicLRU[K, V]) RemoveOldest() (key K, value V, ok bool) { + lastElem := c.list.last() + if lastElem == nil { + return key, value, false + } + + key = lastElem.v + item := c.items[key] + delete(c.items, key) + c.list.remove(lastElem) + return key, item.value, true +} + +// Keys returns all keys in the cache. +func (c *BasicLRU[K, V]) Keys() []K { + keys := make([]K, 0, len(c.items)) + return c.list.appendTo(keys) +} + +// list is a doubly-linked list holding items of type he. +// The zero value is not valid, use newList to create lists. +type list[T any] struct { + root listElem[T] +} + +type listElem[T any] struct { + next *listElem[T] + prev *listElem[T] + v T +} + +func newList[T any]() *list[T] { + l := new(list[T]) + l.init() + return l +} + +// init reinitializes the list, making it empty. +func (l *list[T]) init() { + l.root.next = &l.root + l.root.prev = &l.root +} + +// push adds an element to the front of the list. +func (l *list[T]) pushElem(e *listElem[T]) { + e.prev = &l.root + e.next = l.root.next + l.root.next = e + e.next.prev = e +} + +// moveToFront makes 'node' the head of the list. +func (l *list[T]) moveToFront(e *listElem[T]) { + e.prev.next = e.next + e.next.prev = e.prev + l.pushElem(e) +} + +// remove removes an element from the list. +func (l *list[T]) remove(e *listElem[T]) { + e.prev.next = e.next + e.next.prev = e.prev + e.next, e.prev = nil, nil +} + +// removeLast removes the last element of the list. +func (l *list[T]) removeLast() *listElem[T] { + last := l.last() + if last != nil { + l.remove(last) + } + return last +} + +// last returns the last element of the list, or nil if the list is empty. +func (l *list[T]) last() *listElem[T] { + e := l.root.prev + if e == &l.root { + return nil + } + return e +} + +// appendTo appends all list elements to a slice. +func (l *list[T]) appendTo(slice []T) []T { + for e := l.root.prev; e != &l.root; e = e.prev { + slice = append(slice, e.v) + } + return slice +} diff --git a/common/lru/basiclru_test.go b/common/lru/basiclru_test.go new file mode 100644 index 00000000..68e13bfc --- /dev/null +++ b/common/lru/basiclru_test.go @@ -0,0 +1,240 @@ +// Copyright 2022 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 lru + +import ( + "fmt" + "io" + "math/rand" + "testing" +) + +// Some of these test cases were adapted +// from https://github.com/hashicorp/golang-lru/blob/master/simplelru/lru_test.go + +func TestBasicLRU(t *testing.T) { + cache := NewBasicLRU[int, int](128) + + for i := 0; i < 256; i++ { + cache.Add(i, i) + } + if cache.Len() != 128 { + t.Fatalf("bad len: %v", cache.Len()) + } + + // Check that Keys returns least-recent key first. + keys := cache.Keys() + if len(keys) != 128 { + t.Fatal("wrong Keys() length", len(keys)) + } + for i, k := range keys { + v, ok := cache.Peek(k) + if !ok { + t.Fatalf("expected key %d be present", i) + } + if v != k { + t.Fatalf("expected %d == %d", k, v) + } + if v != i+128 { + t.Fatalf("wrong value at key %d: %d, want %d", i, v, i+128) + } + } + + for i := 0; i < 128; i++ { + _, ok := cache.Get(i) + if ok { + t.Fatalf("%d should be evicted", i) + } + } + for i := 128; i < 256; i++ { + _, ok := cache.Get(i) + if !ok { + t.Fatalf("%d should not be evicted", i) + } + } + + for i := 128; i < 192; i++ { + ok := cache.Remove(i) + if !ok { + t.Fatalf("%d should be in cache", i) + } + ok = cache.Remove(i) + if ok { + t.Fatalf("%d should not be in cache", i) + } + _, ok = cache.Get(i) + if ok { + t.Fatalf("%d should be deleted", i) + } + } + + // Request item 192. + cache.Get(192) + // It should be the last item returned by Keys(). + for i, k := range cache.Keys() { + if (i < 63 && k != i+193) || (i == 63 && k != 192) { + t.Fatalf("out of order key: %v", k) + } + } + + cache.Purge() + if cache.Len() != 0 { + t.Fatalf("bad len: %v", cache.Len()) + } + if _, ok := cache.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +func TestBasicLRUAddExistingKey(t *testing.T) { + cache := NewBasicLRU[int, int](1) + + cache.Add(1, 1) + cache.Add(1, 2) + + v, _ := cache.Get(1) + if v != 2 { + t.Fatal("wrong value:", v) + } +} + +// This test checks GetOldest and RemoveOldest. +func TestBasicLRUGetOldest(t *testing.T) { + cache := NewBasicLRU[int, int](128) + for i := 0; i < 256; i++ { + cache.Add(i, i) + } + + k, _, ok := cache.GetOldest() + if !ok { + t.Fatalf("missing") + } + if k != 128 { + t.Fatalf("bad: %v", k) + } + + k, _, ok = cache.RemoveOldest() + if !ok { + t.Fatalf("missing") + } + if k != 128 { + t.Fatalf("bad: %v", k) + } + + k, _, ok = cache.RemoveOldest() + if !ok { + t.Fatalf("missing oldest item") + } + if k != 129 { + t.Fatalf("wrong oldest item: %v", k) + } +} + +// Test that Add returns true/false if an eviction occurred +func TestBasicLRUAddReturnValue(t *testing.T) { + cache := NewBasicLRU[int, int](1) + if cache.Add(1, 1) { + t.Errorf("first add shouldn't have evicted") + } + if !cache.Add(2, 2) { + t.Errorf("second add should have evicted") + } +} + +// This test verifies that Contains doesn't change item recency. +func TestBasicLRUContains(t *testing.T) { + cache := NewBasicLRU[int, int](2) + cache.Add(1, 1) + cache.Add(2, 2) + if !cache.Contains(1) { + t.Errorf("1 should be in the cache") + } + cache.Add(3, 3) + if cache.Contains(1) { + t.Errorf("Contains should not have updated recency of 1") + } +} + +func BenchmarkLRU(b *testing.B) { + var ( + capacity = 1000 + indexes = make([]int, capacity*20) + keys = make([]string, capacity) + values = make([][]byte, capacity) + ) + for i := range indexes { + indexes[i] = rand.Intn(capacity) + } + for i := range keys { + b := make([]byte, 32) + rand.Read(b) + keys[i] = string(b) + rand.Read(b) + values[i] = b + } + + var sink []byte + + b.Run("Add/BasicLRU", func(b *testing.B) { + cache := NewBasicLRU[int, int](capacity) + for i := 0; i < b.N; i++ { + cache.Add(i, i) + } + }) + b.Run("Get/BasicLRU", func(b *testing.B) { + cache := NewBasicLRU[string, []byte](capacity) + for i := 0; i < capacity; i++ { + index := indexes[i] + cache.Add(keys[index], values[index]) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + k := keys[indexes[i%len(indexes)]] + v, ok := cache.Get(k) + if ok { + sink = v + } + } + }) + + // // vs. github.com/hashicorp/golang-lru/simplelru + // b.Run("Add/simplelru.LRU", func(b *testing.B) { + // cache, _ := simplelru.NewLRU(capacity, nil) + // for i := 0; i < b.N; i++ { + // cache.Add(i, i) + // } + // }) + // b.Run("Get/simplelru.LRU", func(b *testing.B) { + // cache, _ := simplelru.NewLRU(capacity, nil) + // for i := 0; i < capacity; i++ { + // index := indexes[i] + // cache.Add(keys[index], values[index]) + // } + // + // b.ResetTimer() + // for i := 0; i < b.N; i++ { + // k := keys[indexes[i%len(indexes)]] + // v, ok := cache.Get(k) + // if ok { + // sink = v.([]byte) + // } + // } + // }) + + fmt.Fprintln(io.Discard, sink) +} diff --git a/common/lru/blob_lru.go b/common/lru/blob_lru.go new file mode 100644 index 00000000..c9b33985 --- /dev/null +++ b/common/lru/blob_lru.go @@ -0,0 +1,84 @@ +// Copyright 2022 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 lru + +import ( + "math" + "sync" +) + +// blobType is the type constraint for values stored in SizeConstrainedCache. +type blobType interface { + ~[]byte | ~string +} + +// SizeConstrainedCache is a cache where capacity is in bytes (instead of item count). When the cache +// is at capacity, and a new item is added, older items are evicted until the size +// constraint is met. +// +// OBS: This cache assumes that items are content-addressed: keys are unique per content. +// In other words: two Add(..) with the same key K, will always have the same value V. +type SizeConstrainedCache[K comparable, V blobType] struct { + size uint64 + maxSize uint64 + lru BasicLRU[K, V] + lock sync.Mutex +} + +// NewSizeConstrainedCache creates a new size-constrained LRU cache. +func NewSizeConstrainedCache[K comparable, V blobType](maxSize uint64) *SizeConstrainedCache[K, V] { + return &SizeConstrainedCache[K, V]{ + size: 0, + maxSize: maxSize, + lru: NewBasicLRU[K, V](math.MaxInt), + } +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +// OBS: This cache assumes that items are content-addressed: keys are unique per content. +// In other words: two Add(..) with the same key K, will always have the same value V. +// OBS: The value is _not_ copied on Add, so the caller must not modify it afterwards. +func (c *SizeConstrainedCache[K, V]) Add(key K, value V) (evicted bool) { + c.lock.Lock() + defer c.lock.Unlock() + + // Unless it is already present, might need to evict something. + // OBS: If it is present, we still call Add internally to bump the recentness. + if !c.lru.Contains(key) { + targetSize := c.size + uint64(len(value)) + for targetSize > c.maxSize { + evicted = true + _, v, ok := c.lru.RemoveOldest() + if !ok { + // list is now empty. Break + break + } + targetSize -= uint64(len(v)) + } + c.size = targetSize + } + c.lru.Add(key, value) + return evicted +} + +// Get looks up a key's value from the cache. +func (c *SizeConstrainedCache[K, V]) Get(key K) (V, bool) { + c.lock.Lock() + defer c.lock.Unlock() + + return c.lru.Get(key) +} diff --git a/common/lru/blob_lru_test.go b/common/lru/blob_lru_test.go new file mode 100644 index 00000000..ca1b0ddd --- /dev/null +++ b/common/lru/blob_lru_test.go @@ -0,0 +1,155 @@ +// Copyright 2022 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 lru + +import ( + "encoding/binary" + "fmt" + "testing" +) + +type testKey [8]byte + +func mkKey(i int) (key testKey) { + binary.LittleEndian.PutUint64(key[:], uint64(i)) + return key +} + +func TestSizeConstrainedCache(t *testing.T) { + lru := NewSizeConstrainedCache[testKey, []byte](100) + var want uint64 + // Add 11 items of 10 byte each. First item should be swapped out + for i := 0; i < 11; i++ { + k := mkKey(i) + v := fmt.Sprintf("value-%04d", i) + lru.Add(k, []byte(v)) + want += uint64(len(v)) + if want > 100 { + want = 100 + } + if have := lru.size; have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } + } + // Zero:th should be evicted + { + k := mkKey(0) + if _, ok := lru.Get(k); ok { + t.Fatalf("should be evicted: %v", k) + } + } + // Elems 1-11 should be present + for i := 1; i < 11; i++ { + k := mkKey(i) + want := fmt.Sprintf("value-%04d", i) + have, ok := lru.Get(k) + if !ok { + t.Fatalf("missing key %v", k) + } + if string(have) != want { + t.Fatalf("wrong value, have %v want %v", have, want) + } + } +} + +// This test adds inserting an element exceeding the max size. +func TestSizeConstrainedCacheOverflow(t *testing.T) { + lru := NewSizeConstrainedCache[testKey, []byte](100) + + // Add 10 items of 10 byte each, filling the cache + for i := 0; i < 10; i++ { + k := mkKey(i) + v := fmt.Sprintf("value-%04d", i) + lru.Add(k, []byte(v)) + } + // Add one single large elem. We expect it to swap out all entries. + { + k := mkKey(1337) + v := make([]byte, 200) + lru.Add(k, v) + } + // Elems 0-9 should be missing + for i := 1; i < 10; i++ { + k := mkKey(i) + if _, ok := lru.Get(k); ok { + t.Fatalf("should be evicted: %v", k) + } + } + // The size should be accurate + if have, want := lru.size, uint64(200); have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } + // Adding one small item should swap out the large one + { + i := 0 + k := mkKey(i) + v := fmt.Sprintf("value-%04d", i) + lru.Add(k, []byte(v)) + if have, want := lru.size, uint64(10); have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } + } +} + +// This checks what happens when inserting the same k/v multiple times. +func TestSizeConstrainedCacheSameItem(t *testing.T) { + lru := NewSizeConstrainedCache[testKey, []byte](100) + + // Add one 10 byte-item 10 times. + k := mkKey(0) + v := fmt.Sprintf("value-%04d", 0) + for i := 0; i < 10; i++ { + lru.Add(k, []byte(v)) + } + + // The size should be accurate. + if have, want := lru.size, uint64(10); have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } +} + +// This tests that empty/nil values are handled correctly. +func TestSizeConstrainedCacheEmpties(t *testing.T) { + lru := NewSizeConstrainedCache[testKey, []byte](100) + + // This test abuses the lru a bit, using different keys for identical value(s). + for i := 0; i < 10; i++ { + lru.Add(testKey{byte(i)}, []byte{}) + lru.Add(testKey{byte(255 - i)}, nil) + } + + // The size should not count, only the values count. So this could be a DoS + // since it basically has no cap, and it is intentionally overloaded with + // different-keyed 0-length values. + if have, want := lru.size, uint64(0); have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } + + for i := 0; i < 10; i++ { + if v, ok := lru.Get(testKey{byte(i)}); !ok { + t.Fatalf("test %d: expected presence", i) + } else if v == nil { + t.Fatalf("test %d, v is nil", i) + } + + if v, ok := lru.Get(testKey{byte(255 - i)}); !ok { + t.Fatalf("test %d: expected presence", i) + } else if v != nil { + t.Fatalf("test %d, v is not nil", i) + } + } +} diff --git a/common/lru/lru.go b/common/lru/lru.go new file mode 100644 index 00000000..45965adb --- /dev/null +++ b/common/lru/lru.go @@ -0,0 +1,95 @@ +// Copyright 2022 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 lru + +import "sync" + +// Cache is a LRU cache. +// This type is safe for concurrent use. +type Cache[K comparable, V any] struct { + cache BasicLRU[K, V] + mu sync.Mutex +} + +// NewCache creates an LRU cache. +func NewCache[K comparable, V any](capacity int) *Cache[K, V] { + return &Cache[K, V]{cache: NewBasicLRU[K, V](capacity)} +} + +// Add adds a value to the cache. Returns true if an item was evicted to store the new item. +func (c *Cache[K, V]) Add(key K, value V) (evicted bool) { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Add(key, value) +} + +// Contains reports whether the given key exists in the cache. +func (c *Cache[K, V]) Contains(key K) bool { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Contains(key) +} + +// Get retrieves a value from the cache. This marks the key as recently used. +func (c *Cache[K, V]) Get(key K) (value V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Get(key) +} + +// Len returns the current number of items in the cache. +func (c *Cache[K, V]) Len() int { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Len() +} + +// Peek retrieves a value from the cache, but does not mark the key as recently used. +func (c *Cache[K, V]) Peek(key K) (value V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Peek(key) +} + +// Purge empties the cache. +func (c *Cache[K, V]) Purge() { + c.mu.Lock() + defer c.mu.Unlock() + + c.cache.Purge() +} + +// Remove drops an item from the cache. Returns true if the key was present in cache. +func (c *Cache[K, V]) Remove(key K) bool { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Remove(key) +} + +// Keys returns all keys of items currently in the LRU. +func (c *Cache[K, V]) Keys() []K { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Keys() +} diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go deleted file mode 100644 index e33a212a..00000000 --- a/consensus/clique/clique_test.go +++ /dev/null @@ -1,113 +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 clique - -import ( - "math/big" - "testing" - - "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/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" -) - -// This test case is a repro of an annoying bug that took us forever to catch. -// In Clique PoA networks (Rinkeby, Görli, etc), consecutive blocks might have -// the same state root (no block subsidy, empty block). If a node crashes, the -// chain ends up losing the recent state and needs to regenerate it from blocks -// already in the database. The bug was that processing the block *prior* to an -// empty one **also completes** the empty one, ending up in a known-block error. -func TestReimportMirroredState(t *testing.T) { - // Initialize a Clique chain with a single signer - var ( - db = rawdb.NewMemoryDatabase() - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - addr = crypto.PubkeyToAddress(key.PublicKey) - engine = New(params.AllCliqueProtocolChanges.Clique, db) - signer = new(types.HomesteadSigner) - ) - genspec := &core.Genesis{ - ExtraData: make([]byte, extraVanity+common.AddressLength+extraSeal), - Alloc: map[common.Address]core.GenesisAccount{ - addr: {Balance: big.NewInt(1)}, - }, - } - copy(genspec.ExtraData[extraVanity:], addr[:]) - genesis := genspec.MustCommit(db) - - // Generate a batch of blocks, each properly signed - chain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil) - defer chain.Stop() - - blocks, _ := core.GenerateChain(params.AllCliqueProtocolChanges, genesis, engine, db, 3, func(i int, block *core.BlockGen) { - // The chain maker doesn't have access to a chain, so the difficulty will be - // lets unset (nil). Set it here to the correct value. - block.SetDifficulty(diffInTurn) - - // We want to simulate an empty middle block, having the same state as the - // first one. The last is needs a state change again to force a reorg. - if i != 1 { - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr), common.Address{0x00}, new(big.Int), params.TxGas, nil, nil), signer, key) - if err != nil { - panic(err) - } - block.AddTxWithChain(chain, tx) - } - }) - for i, block := range blocks { - header := block.Header() - if i > 0 { - header.ParentHash = blocks[i-1].Hash() - } - header.Extra = make([]byte, extraVanity+extraSeal) - header.Difficulty = diffInTurn - - sig, _ := crypto.Sign(SealHash(header).Bytes(), key) - copy(header.Extra[len(header.Extra)-extraSeal:], sig) - blocks[i] = block.WithSeal(header) - } - // Insert the first two blocks and make sure the chain is valid - db = rawdb.NewMemoryDatabase() - genspec.MustCommit(db) - - chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil) - defer chain.Stop() - - if _, err := chain.InsertChain(blocks[:2]); err != nil { - t.Fatalf("failed to insert initial blocks: %v", err) - } - if head := chain.CurrentBlock().NumberU64(); head != 2 { - t.Fatalf("chain head mismatch: have %d, want %d", head, 2) - } - - // Simulate a crash by creating a new chain on top of the database, without - // flushing the dirty states out. Insert the last block, triggering a sidechain - // reimport. - chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil) - defer chain.Stop() - - if _, err := chain.InsertChain(blocks[2:]); err != nil { - t.Fatalf("failed to insert final block: %v", err) - } - if head := chain.CurrentBlock().NumberU64(); head != 3 { - t.Fatalf("chain head mismatch: have %d, want %d", head, 3) - } -} diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go deleted file mode 100644 index 039ba919..00000000 --- a/consensus/clique/snapshot_test.go +++ /dev/null @@ -1,504 +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 clique - -import ( - "bytes" - "crypto/ecdsa" - "sort" - "testing" - - "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/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" -) - -// testerAccountPool is a pool to maintain currently active tester accounts, -// mapped from textual names used in the tests below to actual Ethereum private -// keys capable of signing transactions. -type testerAccountPool struct { - accounts map[string]*ecdsa.PrivateKey -} - -func newTesterAccountPool() *testerAccountPool { - return &testerAccountPool{ - accounts: make(map[string]*ecdsa.PrivateKey), - } -} - -// checkpoint creates a Clique checkpoint signer section from the provided list -// of authorized signers and embeds it into the provided header. -func (ap *testerAccountPool) checkpoint(header *types.Header, signers []string) { - auths := make([]common.Address, len(signers)) - for i, signer := range signers { - auths[i] = ap.address(signer) - } - sort.Sort(signersAscending(auths)) - for i, auth := range auths { - copy(header.Extra[extraVanity+i*common.AddressLength:], auth.Bytes()) - } -} - -// address retrieves the Ethereum address of a tester account by label, creating -// a new account if no previous one exists yet. -func (ap *testerAccountPool) address(account string) common.Address { - // Return the zero account for non-addresses - if account == "" { - return common.Address{} - } - // Ensure we have a persistent key for the account - if ap.accounts[account] == nil { - ap.accounts[account], _ = crypto.GenerateKey() - } - // Resolve and return the Ethereum address - return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) -} - -// sign calculates a Clique digital signature for the given block and embeds it -// back into the header. -func (ap *testerAccountPool) sign(header *types.Header, signer string) { - // Ensure we have a persistent key for the signer - if ap.accounts[signer] == nil { - ap.accounts[signer], _ = crypto.GenerateKey() - } - // Sign the header and embed the signature in extra data - sig, _ := crypto.Sign(SealHash(header).Bytes(), ap.accounts[signer]) - copy(header.Extra[len(header.Extra)-extraSeal:], sig) -} - -// testerVote represents a single block signed by a parcitular account, where -// the account may or may not have cast a Clique vote. -type testerVote struct { - signer string - voted string - auth bool - checkpoint []string - newbatch bool -} - -// Tests that Clique signer voting is evaluated correctly for various simple and -// complex scenarios, as well as that a few special corner cases fail correctly. -func TestClique(t *testing.T) { - // Define the various voting scenarios to test - tests := []struct { - epoch uint64 - signers []string - votes []testerVote - results []string - failure error - }{ - { - // Single signer, no votes cast - signers: []string{"A"}, - votes: []testerVote{{signer: "A"}}, - results: []string{"A"}, - }, { - // Single signer, voting to add two others (only accept first, second needs 2 votes) - signers: []string{"A"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Two signers, voting to add three others (only accept first two, third needs 3 votes already) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B", voted: "C", auth: true}, - {signer: "A", voted: "D", auth: true}, - {signer: "B", voted: "D", auth: true}, - {signer: "C"}, - {signer: "A", voted: "E", auth: true}, - {signer: "B", voted: "E", auth: true}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Single signer, dropping itself (weird, but one less cornercase by explicitly allowing this) - signers: []string{"A"}, - votes: []testerVote{ - {signer: "A", voted: "A", auth: false}, - }, - results: []string{}, - }, { - // Two signers, actually needing mutual consent to drop either of them (not fulfilled) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Two signers, actually needing mutual consent to drop either of them (fulfilled) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - {signer: "B", voted: "B", auth: false}, - }, - results: []string{"A"}, - }, { - // Three signers, two of them deciding to drop the third - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Four signers, consensus of two not being enough to drop anyone - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Four signers, consensus of three already being enough to drop someone - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - }, - results: []string{"A", "B", "C"}, - }, { - // Authorizations are counted once per signer per target - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Authorizing multiple accounts concurrently is permitted - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "D", auth: true}, - {signer: "B"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: true}, - {signer: "A"}, - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Deauthorizations are counted once per signer per target - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - {signer: "B"}, - {signer: "A", voted: "B", auth: false}, - {signer: "B"}, - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Deauthorizing multiple accounts concurrently is permitted - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Votes from deauthorized signers are discarded immediately (deauth votes) - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "C", voted: "B", auth: false}, - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Votes from deauthorized signers are discarded immediately (auth votes) - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "C", voted: "D", auth: true}, - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "A", voted: "D", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Cascading changes are not allowed, only the account being voted on may change - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - }, - results: []string{"A", "B", "C"}, - }, { - // Changes reaching consensus out of bounds (via a deauth) execute on touch - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "C", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B", "C"}, - }, { - // Ensure that pending votes don't survive authorization status changes. This - // corner case can only appear if a signer is quickly added, removed and then - // readded (or the inverse), while one of the original voters dropped. If a - // past vote is left cached in the system somewhere, this will interfere with - // the final signer outcome. - signers: []string{"A", "B", "C", "D", "E"}, - votes: []testerVote{ - {signer: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed - {signer: "B", voted: "F", auth: true}, - {signer: "C", voted: "F", auth: true}, - {signer: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged") - {signer: "E", voted: "F", auth: false}, - {signer: "B", voted: "F", auth: false}, - {signer: "C", voted: "F", auth: false}, - {signer: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed - {signer: "E", voted: "F", auth: true}, - {signer: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed - {signer: "C", voted: "A", auth: false}, - {signer: "D", voted: "A", auth: false}, - {signer: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed - }, - results: []string{"B", "C", "D", "E", "F"}, - }, { - // Epoch transitions reset all votes to allow chain checkpointing - epoch: 3, - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", checkpoint: []string{"A", "B"}}, - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // An unauthorized signer should not be able to sign blocks - signers: []string{"A"}, - votes: []testerVote{ - {signer: "B"}, - }, - failure: errUnauthorizedSigner, - }, { - // An authorized signer that signed recenty should not be able to sign again - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A"}, - {signer: "A"}, - }, - failure: errRecentlySigned, - }, { - // Recent signatures should not reset on checkpoint blocks imported in a batch - epoch: 3, - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "A"}, - {signer: "B"}, - {signer: "A", checkpoint: []string{"A", "B", "C"}}, - {signer: "A"}, - }, - failure: errRecentlySigned, - }, { - // Recent signatures should not reset on checkpoint blocks imported in a new - // batch (https://github.com/ethereum/go-ethereum/issues/17593). Whilst this - // seems overly specific and weird, it was a Rinkeby consensus split. - epoch: 3, - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "A"}, - {signer: "B"}, - {signer: "A", checkpoint: []string{"A", "B", "C"}}, - {signer: "A", newbatch: true}, - }, - failure: errRecentlySigned, - }, - } - // Run through the scenarios and test them - for i, tt := range tests { - // Create the account pool and generate the initial set of signers - accounts := newTesterAccountPool() - - signers := make([]common.Address, len(tt.signers)) - for j, signer := range tt.signers { - signers[j] = accounts.address(signer) - } - for j := 0; j < len(signers); j++ { - for k := j + 1; k < len(signers); k++ { - if bytes.Compare(signers[j][:], signers[k][:]) > 0 { - signers[j], signers[k] = signers[k], signers[j] - } - } - } - // Create the genesis block with the initial set of signers - genesis := &core.Genesis{ - ExtraData: make([]byte, extraVanity+common.AddressLength*len(signers)+extraSeal), - } - for j, signer := range signers { - copy(genesis.ExtraData[extraVanity+j*common.AddressLength:], signer[:]) - } - // Create a pristine blockchain with the genesis injected - db := rawdb.NewMemoryDatabase() - genesis.Commit(db) - - // Assemble a chain of headers from the cast votes - config := *params.TestChainConfig - config.Clique = ¶ms.CliqueConfig{ - Period: 1, - Epoch: tt.epoch, - } - engine := New(config.Clique, db) - engine.fakeDiff = true - - blocks, _ := core.GenerateChain(&config, genesis.ToBlock(db), engine, db, len(tt.votes), func(j int, gen *core.BlockGen) { - // Cast the vote contained in this block - gen.SetCoinbase(accounts.address(tt.votes[j].voted)) - if tt.votes[j].auth { - var nonce types.BlockNonce - copy(nonce[:], nonceAuthVote) - gen.SetNonce(nonce) - } - }) - // Iterate through the blocks and seal them individually - for j, block := range blocks { - // Get the header and prepare it for signing - header := block.Header() - if j > 0 { - header.ParentHash = blocks[j-1].Hash() - } - header.Extra = make([]byte, extraVanity+extraSeal) - if auths := tt.votes[j].checkpoint; auths != nil { - header.Extra = make([]byte, extraVanity+len(auths)*common.AddressLength+extraSeal) - accounts.checkpoint(header, auths) - } - header.Difficulty = diffInTurn // Ignored, we just need a valid number - - // Generate the signature, embed it into the header and the block - accounts.sign(header, tt.votes[j].signer) - blocks[j] = block.WithSeal(header) - } - // Split the blocks up into individual import batches (cornercase testing) - batches := [][]*types.Block{nil} - for j, block := range blocks { - if tt.votes[j].newbatch { - batches = append(batches, nil) - } - batches[len(batches)-1] = append(batches[len(batches)-1], block) - } - // Pass all the headers through clique and ensure tallying succeeds - chain, err := core.NewBlockChain(db, nil, &config, engine, vm.Config{}, nil, nil) - if err != nil { - t.Errorf("test %d: failed to create test chain: %v", i, err) - continue - } - failed := false - for j := 0; j < len(batches)-1; j++ { - if k, err := chain.InsertChain(batches[j]); err != nil { - t.Errorf("test %d: failed to import batch %d, block %d: %v", i, j, k, err) - failed = true - break - } - } - if failed { - continue - } - if _, err = chain.InsertChain(batches[len(batches)-1]); err != tt.failure { - t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) - } - if tt.failure != nil { - continue - } - // No failure was produced or requested, generate the final voting snapshot - head := blocks[len(blocks)-1] - - snap, err := engine.snapshot(chain, head.NumberU64(), head.Hash(), nil) - if err != nil { - t.Errorf("test %d: failed to retrieve voting snapshot: %v", i, err) - continue - } - // Verify the final list of signers against the expected ones - signers = make([]common.Address, len(tt.results)) - for j, signer := range tt.results { - signers[j] = accounts.address(signer) - } - for j := 0; j < len(signers); j++ { - for k := j + 1; k < len(signers); k++ { - if bytes.Compare(signers[j][:], signers[k][:]) > 0 { - signers[j], signers[k] = signers[k], signers[j] - } - } - } - result := snap.signers() - if len(result) != len(signers) { - t.Errorf("test %d: signers mismatch: have %x, want %x", i, result, signers) - continue - } - for j := 0; j < len(result); j++ { - if !bytes.Equal(result[j][:], signers[j][:]) { - t.Errorf("test %d, signer %d: signer mismatch: have %x, want %x", i, j, result[j], signers[j]) - } - } - } -} diff --git a/consensus/hotstuff/backend.go b/consensus/hotstuff/backend.go index d4ea89a3..4b6a713f 100644 --- a/consensus/hotstuff/backend.go +++ b/consensus/hotstuff/backend.go @@ -35,9 +35,6 @@ type Backend interface { // Validators returns current epoch participants Validators(height uint64, inConsensus bool) (ValidatorSet, error) - // EventMux returns the event mux in backend - EventMux() *event.TypeMux - // Broadcast sends a message to all validators (include self) Broadcast(valSet ValidatorSet, payload []byte) error @@ -69,6 +66,10 @@ type Backend interface { // CheckPoint retrieve the flag of epoch change and new epoch start height CheckPoint(height uint64) (uint64, bool) + + // Event pub/sub + SubscribeEvent(ch interface{}) event.Subscription + Send(ev interface{}) int Reset() @@ -76,8 +77,10 @@ type Backend interface { } type CoreEngine interface { + // Start a new round Start(chain consensus.ChainReader) + // Stop current round Stop() // IsProposer return true if self address equal leader/proposer address in current round/height diff --git a/consensus/hotstuff/backend/backend.go b/consensus/hotstuff/backend/backend.go index 79192b61..68860425 100644 --- a/consensus/hotstuff/backend/backend.go +++ b/consensus/hotstuff/backend/backend.go @@ -61,7 +61,7 @@ type backend struct { broadcaster consensus.Broadcaster // event subscription for ChainHeadEvent event nodesFeed event.Feed // event subscription for static nodes listen executeFeed event.Feed // event subscription for executed state - eventMux *event.TypeMux // message sender for engine + requestFeed, messageFeed, commitFeed event.Feed // message sender for engine epochMu int32 // check point mutex @@ -83,7 +83,6 @@ func New(chainConfig *params.ChainConfig, config *hotstuff.Config, privateKey *e db: db, logger: log.New(), coreStarted: false, - eventMux: new(event.TypeMux), signer: signer, recentMessages: recentMessages, knownMessages: knownMessages, @@ -104,9 +103,30 @@ func (s *backend) Address() common.Address { return s.signer.Address() } -// EventMux implements hotstuff.Backend.EventMux -func (s *backend) EventMux() *event.TypeMux { - return s.eventMux +func (s *backend) SubscribeEvent(ch interface{}) event.Subscription { + switch c := ch.(type) { + case chan hotstuff.RequestEvent: + return s.requestFeed.Subscribe(c) + case chan hotstuff.MessageEvent: + return s.messageFeed.Subscribe(c) + case chan hotstuff.FinalCommittedEvent: + return s.commitFeed.Subscribe(c) + default: + panic(fmt.Sprintf("unexpected subscriber type %t", ch)) + } +} + +func (s *backend) Send(ev interface{}) int { + switch event := ev.(type) { + case hotstuff.RequestEvent: + return s.requestFeed.Send(event) + case hotstuff.MessageEvent: + return s.messageFeed.Send(event) + case hotstuff.FinalCommittedEvent: + return s.commitFeed.Send(event) + default: + panic(fmt.Sprintf("unexpected event type %t", ev)) + } } // Broadcast implements hotstuff.Backend.Broadcast @@ -120,7 +140,7 @@ func (s *backend) Broadcast(valSet hotstuff.ValidatorSet, payload []byte) error Src: s.Address(), Payload: payload, } - go s.EventMux().Post(msg) + go s.messageFeed.Send(msg) return nil } @@ -168,7 +188,7 @@ func (s *backend) Unicast(valSet hotstuff.ValidatorSet, payload []byte) error { // send to self if s.Address() == target { - go s.EventMux().Post(msg) + go s.messageFeed.Send(msg) return nil } diff --git a/consensus/hotstuff/backend/engine.go b/consensus/hotstuff/backend/engine.go index d13e05fd..c37bb625 100644 --- a/consensus/hotstuff/backend/engine.go +++ b/consensus/hotstuff/backend/engine.go @@ -219,7 +219,7 @@ func (s *backend) Seal(chain consensus.ChainHeaderReader, block *types.Block, re } block = block.WithSeal(header) - go s.EventMux().Post(hotstuff.RequestEvent{Block: block}) + go s.requestFeed.Send(hotstuff.RequestEvent{Block: block}) s.logger.Trace("WorkerSealNewBlock", "address", s.Address(), "hash", block.Hash(), "number", block.Number()) return nil diff --git a/consensus/hotstuff/backend/engine_test.go b/consensus/hotstuff/backend/engine_test.go index 387d9a86..8d48a899 100644 --- a/consensus/hotstuff/backend/engine_test.go +++ b/consensus/hotstuff/backend/engine_test.go @@ -21,15 +21,13 @@ package backend import ( "math/big" "os" - "reflect" "testing" "time" - "github.com/ethereum/go-ethereum/consensus/hotstuff/signer" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/hotstuff" + "github.com/ethereum/go-ethereum/consensus/hotstuff/signer" tu "github.com/ethereum/go-ethereum/consensus/hotstuff/testutils" "github.com/ethereum/go-ethereum/contracts/native/boot" "github.com/ethereum/go-ethereum/core/rawdb" @@ -70,84 +68,37 @@ func TestPrepare(t *testing.T) { assert.Error(t, engine.Prepare(chain, header), consensus.ErrUnknownAncestor) } -// go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/backend -run TestSealStopChannel -// TestSealStopChannel stop seal and result channel should be empty -func TestSealStopChannel(t *testing.T) { - chain, engine := singleNodeChain() - defer engine.Stop() - - block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) - stop := make(chan struct{}, 1) - eventSub := engine.EventMux().Subscribe(hotstuff.RequestEvent{}) - blockSub := engine.SubscribeBlock(make(chan consensus.ExecutedBlock)) - eventLoop := func() { - ev := <-eventSub.Chan() - if _, ok := ev.Data.(hotstuff.RequestEvent); !ok { - t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) - } - stop <- struct{}{} - eventSub.Unsubscribe() - blockSub.Unsubscribe() - } - resultCh := make(chan *types.Block, 10) - go func() { - if err := engine.Seal(chain, block, resultCh, stop); err != nil { - t.Errorf("error mismatch: have error %v, want nil", err) - } - }() - go eventLoop() - - finalBlock := <-resultCh - if finalBlock != nil { - t.Errorf("block mismatch: have final block %v, want nil", finalBlock) - } -} - // go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/backend -run TestSealOtherHash // TestSealCommittedOtherHash result channel should be empty if engine commit another block before seal func TestSealOtherHash(t *testing.T) { chain, engine := singleNodeChain() defer engine.Stop() block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) - otherBlock := makeBlockWithoutSeal(chain, engine, chain.Genesis()) - otherBlock.Header().GasUsed = 10 blockCh := make(chan consensus.ExecutedBlock) blockSub := engine.SubscribeBlock(blockCh) - eventSub := engine.EventMux().Subscribe(hotstuff.RequestEvent{}) + requestCh := make(chan hotstuff.RequestEvent) + eventSub := engine.SubscribeEvent(requestCh) blockOutputChannel := make(chan *types.Block) stopChannel := make(chan struct{}) go func() { - ev := <-eventSub.Chan() - if _, ok := ev.Data.(hotstuff.RequestEvent); !ok { - t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) - } - if err := engine.Commit(&consensus.ExecutedBlock{Block: otherBlock}); err != nil { + if err := engine.Seal(chain, block, blockOutputChannel, stopChannel); err != nil { t.Error(err.Error()) } - eventSub.Unsubscribe() - blockSub.Unsubscribe() - }() - - go func() { - if err := engine.Seal(chain, block, blockOutputChannel, stopChannel); err != nil { + if err := engine.Commit(&consensus.ExecutedBlock{Block: block}); err != nil { t.Error(err.Error()) } }() - select { - case <-blockOutputChannel: - t.Error("Wrong block found!") - default: - //no block found, stop the sealing - close(stopChannel) - } + evt := <- requestCh + assert.Equal(t, evt.Block.SealHash(), block.SealHash()) - output := <-blockOutputChannel - if output != nil { - t.Error("Block not nil!") - } + data := <-blockCh + assert.Equal(t, data.Block.SealHash(), block.SealHash()) + + eventSub.Unsubscribe() + blockSub.Unsubscribe() } func updateTestBlock(block *types.Block, addr common.Address) *types.Block { @@ -171,14 +122,18 @@ func TestSealCommitted(t *testing.T) { block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) expectedBlock := updateTestBlock(block, engine.Address()) - resultCh := make(chan *types.Block, 10) go func() { - if err := engine.Seal(chain, block, resultCh, make(chan struct{})); err != nil { + if err := engine.Seal(chain, block, make(chan *types.Block), make(chan struct{})); err != nil { t.Errorf("error mismatch: have %v, want %v", err, expectedBlock) } }() - finalBlock := <-resultCh + requestCh := make(chan hotstuff.RequestEvent) + eventSub := engine.SubscribeEvent(requestCh) + ev := <- requestCh + eventSub.Unsubscribe() + finalBlock := ev.Block + if finalBlock.Hash() != expectedBlock.Hash() { t.Errorf("hash mismatch: have %v, want %v", finalBlock.Hash(), expectedBlock.Hash()) } diff --git a/consensus/hotstuff/backend/handler.go b/consensus/hotstuff/backend/handler.go index 7d4af4f1..ceeaaff9 100644 --- a/consensus/hotstuff/backend/handler.go +++ b/consensus/hotstuff/backend/handler.go @@ -77,7 +77,7 @@ func (s *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) { } s.knownMessages.Add(hash, true) - go s.eventMux.Post(hotstuff.MessageEvent{ + go s.messageFeed.Send(hotstuff.MessageEvent{ Src: addr, Payload: data, }) @@ -127,7 +127,7 @@ func (s *backend) NewChainHead(header *types.Header) error { if !s.coreStarted { return ErrStoppedEngine } - go s.eventMux.Post(hotstuff.FinalCommittedEvent{Header: header}) + go s.commitFeed.Send(hotstuff.FinalCommittedEvent{Header: header}) return nil } diff --git a/consensus/hotstuff/backend/handler_test.go b/consensus/hotstuff/backend/handler_test.go index 5da28160..a005ae57 100644 --- a/consensus/hotstuff/backend/handler_test.go +++ b/consensus/hotstuff/backend/handler_test.go @@ -22,6 +22,7 @@ import ( "io/ioutil" "math/big" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/hotstuff" @@ -70,6 +71,8 @@ func TestHotstuffMessage(t *testing.T) { // go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/backend -run TestHandleNewBlockMessage_whenTypical func TestHandleNewBlockMessage_whenTypical(t *testing.T) { _, backend := singleNodeChain() + // Ensure new view is handled + time.Sleep(time.Millisecond * 100) arbitraryAddress := common.HexToAddress("arbitrary") arbitraryBlock, arbitraryP2PMessage := buildArbitraryP2PNewBlockMessage(t, false) postAndWait(backend, arbitraryBlock, t) @@ -77,7 +80,7 @@ func TestHandleNewBlockMessage_whenTypical(t *testing.T) { handled, err := backend.HandleMsg(arbitraryAddress, arbitraryP2PMessage) assert.NoError(t, err, "expected message being handled successfully but got", err) t.Log("handled", handled) - assert.False(t, handled, "expected message not being handled") + assert.True(t, handled, "expected message not being handled") _, err = ioutil.ReadAll(arbitraryP2PMessage.Payload) assert.NoError(t, err, "expected p2p message payload is restored") @@ -86,10 +89,12 @@ func TestHandleNewBlockMessage_whenTypical(t *testing.T) { // go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/backend -run TestHandleNewBlockMessage_whenNotAProposedBlock func TestHandleNewBlockMessage_whenNotAProposedBlock(t *testing.T) { _, backend := singleNodeChain() + // Ensure new view is handled + time.Sleep(time.Millisecond * 100) arbitraryAddress := common.HexToAddress("arbitrary") _, arbitraryP2PMessage := buildArbitraryP2PNewBlockMessage(t, false) postAndWait(backend, types.NewBlock(&types.Header{ - Number: big.NewInt(1), + Number: big.NewInt(2), Root: common.HexToHash("someroot"), GasLimit: 1, MixDigest: types.HotstuffDigest, @@ -106,6 +111,8 @@ func TestHandleNewBlockMessage_whenNotAProposedBlock(t *testing.T) { // go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/backend -run TestHandleNewBlockMessage_whenFailToDecode func TestHandleNewBlockMessage_whenFailToDecode(t *testing.T) { _, backend := singleNodeChain() + // Ensure new view is handled + time.Sleep(time.Millisecond * 100) arbitraryAddress := common.HexToAddress("arbitrary") _, arbitraryP2PMessage := buildArbitraryP2PNewBlockMessage(t, true) postAndWait(backend, types.NewBlock(&types.Header{ diff --git a/consensus/hotstuff/backend/test_utils.go b/consensus/hotstuff/backend/test_utils.go index fac89114..afc36dad 100644 --- a/consensus/hotstuff/backend/test_utils.go +++ b/consensus/hotstuff/backend/test_utils.go @@ -20,6 +20,7 @@ package backend import ( "bytes" + "context" "crypto/ecdsa" "io/ioutil" "math/big" @@ -54,7 +55,23 @@ func singleNodeChain() (*core.BlockChain, *backend) { genesis, nodeKeys, _ := tu.GenesisAndKeys(1) memDB := rawdb.NewMemoryDatabase() config := hotstuff.DefaultBasicConfig - chainConfig := ¶ms.ChainConfig{ChainID: big.NewInt(60801)} + chainConfig := ¶ms.ChainConfig{ + ChainID: big.NewInt(60801), + HomesteadBlock: new(big.Int), + DAOForkBlock: new(big.Int), + DAOForkSupport: false, + EIP150Block: new(big.Int), + EIP150Hash: common.Hash{}, + EIP155Block: new(big.Int), + EIP158Block: new(big.Int), + ByzantiumBlock: new(big.Int), + ConstantinopleBlock: new(big.Int), + PetersburgBlock: new(big.Int), + IstanbulBlock: new(big.Int), + MuirGlacierBlock: new(big.Int), + BerlinBlock: new(big.Int), + LondonBlock: nil, + } // Use the first key as private key backend := New(chainConfig, config, nodeKeys[0], memDB, true) genesis.MustCommit(memDB) @@ -86,7 +103,7 @@ func makeHeader(parent *types.Block) *types.Header { header := &types.Header{ ParentHash: parent.Hash(), Number: blockNumber, - GasLimit: core.CalcGasLimit(parent.GasUsed(), parent.GasLimit(), GasFloor, GasCeil), + GasLimit: core.CalcGasLimit(parent.GasLimit(), GasCeil), GasUsed: 0, Time: parent.Time() + hotstuff.DefaultBasicConfig.BlockPeriod, Difficulty: defaultDifficulty, @@ -143,20 +160,24 @@ func makeMsg(msgcode uint64, data interface{}) p2p.Msg { } func postAndWait(backend *backend, block *types.Block, t *testing.T) { - eventSub := backend.EventMux().Subscribe(hotstuff.RequestEvent{}) + requestCh := make(chan hotstuff.RequestEvent, 10) + eventSub := backend.SubscribeEvent(requestCh) defer eventSub.Unsubscribe() - stop := make(chan struct{}, 1) - eventLoop := func() { - <-eventSub.Chan() - stop <- struct{}{} - } - go eventLoop() - if err := backend.EventMux().Post(hotstuff.RequestEvent{ + ctx, cancel := context.WithCancel(context.Background()) + go func() { + for { + _, ok := <- requestCh + cancel() + if !ok { + return + } + } + } () + backend.Send(hotstuff.RequestEvent{ Block: block, - }); err != nil { - t.Fatalf("%s", err) - } - <-stop + }) + + <-ctx.Done() } func buildArbitraryP2PNewBlockMessage(t *testing.T, invalidMsg bool) (*types.Block, p2p.Msg) { diff --git a/consensus/hotstuff/core/backlog.go b/consensus/hotstuff/core/backlog.go index 48d0825d..c96a7465 100644 --- a/consensus/hotstuff/core/backlog.go +++ b/consensus/hotstuff/core/backlog.go @@ -78,7 +78,7 @@ func (c *core) processBacklog() { } logger.Trace("Replay the backlog", "msg", msg) - go c.sendEvent(backlogEvent{src: src, msg: msg}) + go c.backlogFeed.Send(backlogEvent{src: src, msg: msg}) } } } diff --git a/consensus/hotstuff/core/backlog_test.go b/consensus/hotstuff/core/backlog_test.go index 5e72fc7c..880baae8 100644 --- a/consensus/hotstuff/core/backlog_test.go +++ b/consensus/hotstuff/core/backlog_test.go @@ -24,7 +24,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/event" ) // go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/core -run TestStoreBacklog @@ -93,10 +92,11 @@ func TestStoreBacklog(t *testing.T) { // go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/core -run TestProcessFutureBacklog func TestProcessFutureBacklog(t *testing.T) { c, vals := singerTestCore(t, 2, 0, 1) - c.backend = &testSystemBackend{events: new(event.TypeMux)} + c.backend = &testSystemBackend{} sender := vals.GetByIndex(1) - c.subscribeEvents() - defer c.unsubscribeEvents() + backlogCh := make(chan backlogEvent) + backlogSub := c.backlogFeed.Subscribe(backlogCh) + defer backlogSub.Unsubscribe() // push a future msg subject := []byte{'c', 'm', 't', 'p', 'l', 'd'} @@ -113,7 +113,7 @@ func TestProcessFutureBacklog(t *testing.T) { const timeoutDura = 2 * time.Second timeout := time.NewTimer(timeoutDura) select { - case e, ok := <-c.events.Chan(): + case e, ok := <-backlogCh: if !ok { return } @@ -158,9 +158,10 @@ func TestProcessBacklog(t *testing.T) { func testProcessBacklog(t *testing.T, msg *Message) { c, vals := singerTestCore(t, 2, 1, 0) - c.backend = &testSystemBackend{events: new(event.TypeMux)} - c.subscribeEvents() - defer c.unsubscribeEvents() + c.backend = &testSystemBackend{} + backlogCh := make(chan backlogEvent) + backlogSub := c.backlogFeed.Subscribe(backlogCh) + defer backlogSub.Unsubscribe() sender := vals.GetByIndex(1) msg.address = sender.Address() @@ -171,13 +172,9 @@ func testProcessBacklog(t *testing.T, msg *Message) { const timeoutDura = 2 * time.Second timeout := time.NewTimer(timeoutDura) select { - case ev := <-c.events.Chan(): - e, ok := ev.Data.(backlogEvent) - if !ok { - t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) - } - if e.msg.Code != msg.Code { - t.Errorf("message code mismatch: have %v, want %v", e.msg.Code, msg.Code) + case ev := <- backlogCh: + if ev.msg.Code != msg.Code { + t.Errorf("message code mismatch: have %v, want %v", ev.msg.Code, msg.Code) } // success case <-timeout.C: diff --git a/consensus/hotstuff/core/core.go b/consensus/hotstuff/core/core.go index 147c875a..28891b6d 100644 --- a/consensus/hotstuff/core/core.go +++ b/consensus/hotstuff/core/core.go @@ -43,9 +43,8 @@ type core struct { valSet hotstuff.ValidatorSet backlogs *backlog - events *event.TypeMuxSubscription - timeoutSub *event.TypeMuxSubscription - finalCommittedSub *event.TypeMuxSubscription + backlogFeed event.Feed + newRoundFeed event.Feed roundChangeTimer *time.Timer @@ -60,6 +59,7 @@ type core struct { isRunning bool wg sync.WaitGroup + exit chan struct{} } // New creates an HotStuff consensus core @@ -73,6 +73,7 @@ func New(backend hotstuff.Backend, config *hotstuff.Config, signer hotstuff.Sign backlogs: newBackLog(), pendingRequests: prque.New(nil), pendingRequestsMu: new(sync.Mutex), + exit: make(chan struct{}), } c.validateFn = c.checkValidatorSignature c.checkPointFn = checkPointFn diff --git a/consensus/hotstuff/core/handler.go b/consensus/hotstuff/core/handler.go index cf2455b3..2b00d49b 100644 --- a/consensus/hotstuff/core/handler.go +++ b/consensus/hotstuff/core/handler.go @@ -31,19 +31,17 @@ func (c *core) Start(chain consensus.ChainReader) { c.isRunning = true c.current = nil - c.subscribeEvents() - - // Start a new round from last sequence + 1 - c.startNewRound(common.Big0) c.wg.Add(1) + c.exit = make(chan struct{}) go c.handleEvents() } // Stop implements core.Engine.Stop func (c *core) Stop() { c.stopTimer() - c.unsubscribeEvents() c.isRunning = false + close(c.exit) + c.wg.Wait() } @@ -75,80 +73,53 @@ func (c *core) CurrentSequence() (uint64, uint64) { return view.HeightU64(), view.RoundU64() } -// ---------------------------------------------------------------------------- - -// Subscribe both internal and external events -func (c *core) subscribeEvents() { - c.events = c.backend.EventMux().Subscribe( - // external events - hotstuff.RequestEvent{}, - // internal events - hotstuff.MessageEvent{}, - backlogEvent{}, - ) - c.timeoutSub = c.backend.EventMux().Subscribe( - timeoutEvent{}, - ) - c.finalCommittedSub = c.backend.EventMux().Subscribe( - hotstuff.FinalCommittedEvent{}, - ) -} - -// Unsubscribe all events -func (c *core) unsubscribeEvents() { - c.events.Unsubscribe() - c.timeoutSub.Unsubscribe() - c.finalCommittedSub.Unsubscribe() -} - func (c *core) handleEvents() { defer c.wg.Done() logger := c.logger.New("handleEvents") + requestCh := make(chan hotstuff.RequestEvent, 16) + requestSub := c.backend.SubscribeEvent(requestCh) + defer requestSub.Unsubscribe() + + messageCh := make(chan hotstuff.MessageEvent, 16) + messageSub := c.backend.SubscribeEvent(messageCh) + defer messageSub.Unsubscribe() + + commitCh := make(chan hotstuff.FinalCommittedEvent, 16) + commitSub := c.backend.SubscribeEvent(commitCh) + defer commitSub.Unsubscribe() + + backlogCh := make(chan backlogEvent, 16) + backlogSub := c.backlogFeed.Subscribe(backlogCh) + defer backlogSub.Unsubscribe() + + newRoundCh := make(chan newRoundEvent, 16) + newRoundSub := c.newRoundFeed.Subscribe(newRoundCh) + defer newRoundSub.Unsubscribe() + + c.startNewRound(common.Big0) + for { select { - case event, ok := <-c.events.Chan(): - if !ok { - logger.Error("Failed to receive msg Event", "err", "subscribe event chan out empty") - return - } - // A real Event arrived, process interesting content - switch ev := event.Data.(type) { - case hotstuff.RequestEvent: - c.handleRequest(&Request{block: ev.Block}) - - case hotstuff.MessageEvent: - c.handleMsg(ev.Src, ev.Payload) - - case backlogEvent: - c.handleCheckedMsg(ev.msg) - } - - case _, ok := <-c.timeoutSub.Chan(): - if !ok { - logger.Error("Failed to receive timeout Event") - return - } - c.handleTimeoutMsg() - - case evt, ok := <-c.finalCommittedSub.Chan(): - if !ok { - logger.Error("Failed to receive finalCommitted Event") - return - } - switch ev := evt.Data.(type) { - case hotstuff.FinalCommittedEvent: - c.handleFinalCommitted(ev.Header) - } + case ev := <- requestCh: + c.handleRequest(&Request{block: ev.Block}) + case ev := <- messageCh: + c.handleMsg(ev.Src, ev.Payload) + case ev := <- backlogCh: + + c.handleCheckedMsg(ev.msg) + case ev := <- newRoundCh: + c.handleNewRoundMsg(ev) + case ev := <- commitCh: + c.handleFinalCommitted(ev.Header) + + case <- c.exit: + logger.Info("Hotstuff core is stopping...") + return } } } -// sendEvent sends events to mux -func (c *core) sendEvent(ev interface{}) { - c.backend.EventMux().Post(ev) -} - func (c *core) handleMsg(val common.Address, payload []byte) error { logger := c.logger.New() @@ -204,9 +175,12 @@ func (c *core) handleCheckedMsg(msg *Message) (err error) { return } -func (c *core) handleTimeoutMsg() { +func (c *core) handleNewRoundMsg(evt newRoundEvent) { c.logger.Trace("handleTimeout", "state", c.currentState(), "view", c.currentView()) - round := new(big.Int).Add(c.current.Round(), common.Big1) + round := common.Big0 + if !evt.Initial { + round = new(big.Int).Add(c.current.Round(), common.Big1) + } c.startNewRound(round) } diff --git a/consensus/hotstuff/core/request.go b/consensus/hotstuff/core/request.go index 2760ec74..ed5b53fb 100644 --- a/consensus/hotstuff/core/request.go +++ b/consensus/hotstuff/core/request.go @@ -113,7 +113,7 @@ func (c *core) processPendingRequests() { continue } else { c.logger.Trace("Post pending request", "number", r.block.Number(), "hash", r.block.SealHash()) - go c.sendEvent(hotstuff.RequestEvent{ + go c.backend.Send(hotstuff.RequestEvent{ Block: r.block, }) } diff --git a/consensus/hotstuff/core/request_test.go b/consensus/hotstuff/core/request_test.go index fb3e8382..9bd98779 100644 --- a/consensus/hotstuff/core/request_test.go +++ b/consensus/hotstuff/core/request_test.go @@ -20,14 +20,12 @@ package core import ( "math/big" - "reflect" "sync" "testing" "time" "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/consensus/hotstuff" - "github.com/ethereum/go-ethereum/event" ) // go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/core -run TestCheckRequestMsg @@ -68,11 +66,13 @@ func TestCheckRequestMsg(t *testing.T) { // go test -v github.com/ethereum/go-ethereum/consensus/hotstuff/core -run TestStoreRequestMsg func TestStoreRequestMsg(t *testing.T) { c, _ := singerTestCore(t, 4, 0, 0) - c.backend = &testSystemBackend{events: new(event.TypeMux)} + c.backend = &testSystemBackend{} c.pendingRequests = prque.New(nil) c.pendingRequestsMu = new(sync.Mutex) - c.subscribeEvents() - defer c.unsubscribeEvents() + + requestCh := make(chan hotstuff.RequestEvent) + requestSub := c.backend.SubscribeEvent(requestCh) + defer requestSub.Unsubscribe() requests := []*Request{ { @@ -99,13 +99,9 @@ func TestStoreRequestMsg(t *testing.T) { const timeoutDura = 2 * time.Second timeout := time.NewTimer(timeoutDura) select { - case ev := <-c.events.Chan(): - e, ok := ev.Data.(hotstuff.RequestEvent) - if !ok { - t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) - } - if e.Block.Number().Cmp(requests[2].block.Number()) != 0 { - t.Errorf("the number of proposal mismatch: have %v, want %v", e.Block.Number(), requests[2].block.Number()) + case ev := <- requestCh: + if ev.Block.Number().Cmp(requests[2].block.Number()) != 0 { + t.Errorf("the number of proposal mismatch: have %v, want %v", ev.Block.Number(), requests[2].block.Number()) } case <-timeout.C: t.Error("unexpected timeout occurs") diff --git a/consensus/hotstuff/core/test_utils.go b/consensus/hotstuff/core/test_utils.go index 130a537b..64f9be2c 100644 --- a/consensus/hotstuff/core/test_utils.go +++ b/consensus/hotstuff/core/test_utils.go @@ -38,7 +38,6 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" - elog "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/trie" ) @@ -109,7 +108,7 @@ func makeBlockWithParentHash(number int, parentHash common.Hash) *types.Block { // // ============================================== -var testLogger = elog.New() +var testLogger = log.New() type testSystemBackend struct { id int @@ -117,7 +116,7 @@ type testSystemBackend struct { engine *core peers hotstuff.ValidatorSet - events *event.TypeMux + requestFeed, messageFeed, commitFeed event.Feed // message sender for engine committedMsgs []testCommittedMsgs sentMsgs [][]byte // store the message when Send is called by core @@ -135,15 +134,38 @@ func (ts *testSystemBackend) Address() common.Address { return ts.address } +func (ts *testSystemBackend) SubscribeEvent(ch interface{}) event.Subscription { + switch c := ch.(type) { + case chan hotstuff.RequestEvent: + return ts.requestFeed.Subscribe(c) + case chan hotstuff.MessageEvent: + return ts.messageFeed.Subscribe(c) + case chan hotstuff.FinalCommittedEvent: + return ts.commitFeed.Subscribe(c) + default: + panic(fmt.Sprintf("unexpected subscriber type %t", ch)) + } +} + +func (ts *testSystemBackend) Send(ev interface{}) int { + switch event := ev.(type) { + case hotstuff.RequestEvent: + return ts.requestFeed.Send(event) + case hotstuff.MessageEvent: + return ts.messageFeed.Send(event) + case hotstuff.FinalCommittedEvent: + return ts.commitFeed.Send(event) + default: + panic(fmt.Sprintf("unexpected event type %t", ev)) + } +} + + // Peers returns all connected peers func (ts *testSystemBackend) Validators(height uint64, mining bool) (hotstuff.ValidatorSet, error) { return ts.peers, nil } -func (ts *testSystemBackend) EventMux() *event.TypeMux { - return ts.events -} - func (ts *testSystemBackend) Broadcast(valSet hotstuff.ValidatorSet, message []byte) error { //return nil testLogger.Info("enqueuing a message...", "address", ts.Address()) @@ -183,7 +205,7 @@ func (ts *testSystemBackend) Commit(executed *consensus.ExecutedBlock) error { }) // fake new head events - go ts.events.Post(hotstuff.FinalCommittedEvent{}) + go ts.Send(hotstuff.FinalCommittedEvent{}) return nil } @@ -223,7 +245,7 @@ type testSystem struct { } func newTestSystem(n int) *testSystem { - testLogger.SetHandler(elog.StdoutHandler) + testLogger.SetHandler(log.StdoutHandler) return &testSystem{ backends: make([]*testSystemBackend, n), queuedMessage: make(chan hotstuff.MessageEvent), @@ -241,7 +263,7 @@ func generateValidators(n int) []common.Address { } func NewTestSystemWithBackend(n, h, r int) *testSystem { - testLogger.SetHandler(elog.StdoutHandler) + testLogger.SetHandler(log.StdoutHandler) vset, keys := newTestValidatorSet(n) sys := newTestSystem(n) @@ -273,7 +295,7 @@ func (t *testSystem) listen() { case queuedMessage := <-t.queuedMessage: testLogger.Info("consuming a queue message...") for _, backend := range t.backends { - go backend.EventMux().Post(queuedMessage) + go backend.Send(queuedMessage) } } } @@ -311,7 +333,6 @@ func (t *testSystem) NewBackend(id int) *testSystemBackend { backend := &testSystemBackend{ id: id, sys: t, - events: new(event.TypeMux), db: ethDB, } diff --git a/consensus/hotstuff/core/timer.go b/consensus/hotstuff/core/timer.go index e3770cf7..4fefcfd2 100644 --- a/consensus/hotstuff/core/timer.go +++ b/consensus/hotstuff/core/timer.go @@ -41,7 +41,7 @@ func (c *core) newRoundChangeTimer() { timeout += time.Duration(math.Pow(2, float64(round))) * time.Second } c.roundChangeTimer = time.AfterFunc(timeout, func() { - c.sendEvent(timeoutEvent{}) + c.newRoundFeed.Send(newRoundEvent{}) }) } diff --git a/consensus/hotstuff/core/types.go b/consensus/hotstuff/core/types.go index 82ebeaa9..d76a7e60 100644 --- a/consensus/hotstuff/core/types.go +++ b/consensus/hotstuff/core/types.go @@ -454,7 +454,9 @@ func (m *Message) String() string { return fmt.Sprintf("{MsgType: %v, view: %v, address: %v}", m.Code, m.View, m.address) } -type timeoutEvent struct{} +type newRoundEvent struct{ + Initial bool +} type backlogEvent struct { src hotstuff.Validator msg *Message diff --git a/consensus/hotstuff/mock/chain.go b/consensus/hotstuff/mock/chain.go index 44910e88..732bd217 100644 --- a/consensus/hotstuff/mock/chain.go +++ b/consensus/hotstuff/mock/chain.go @@ -28,7 +28,7 @@ import ( ) func makeChain(db ethdb.Database, engine consensus.Engine, validators []common.Address) *core.BlockChain { - genesis := makeGenesis(validators) + genesis := MakeGenesis(validators) block := genesis.MustCommit(db) log.Info("Make chain with genesis block", "hash", block.Hash()) diff --git a/consensus/hotstuff/mock/engine.go b/consensus/hotstuff/mock/engine.go index 0af6c181..cdedba29 100644 --- a/consensus/hotstuff/mock/engine.go +++ b/consensus/hotstuff/mock/engine.go @@ -34,7 +34,20 @@ type Engine consensus.Engine // backend is engine but also hotstuff engine and consensus handler. func makeEngine(privateKey *ecdsa.PrivateKey, db ethdb.Database) Engine { config := hotstuff.DefaultBasicConfig - chainConfig := ¶ms.ChainConfig{ChainID: big.NewInt(60801)} + chainConfig := ¶ms.ChainConfig{ + ChainID: big.NewInt(60801), + 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), + HotStuff: ¶ms.HotStuffConfig{Protocol: "basic"}, + } engine := backend.New(chainConfig, config, privateKey, db, true) broadcaster := makeBroadcaster(engine.Address(), engine) engine.SetBroadcaster(broadcaster) diff --git a/consensus/hotstuff/mock/miner.go b/consensus/hotstuff/mock/miner.go index 6985b01a..0aee8046 100644 --- a/consensus/hotstuff/mock/miner.go +++ b/consensus/hotstuff/mock/miner.go @@ -23,8 +23,8 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -131,8 +131,16 @@ func (m *miner) newWork() { header := &types.Header{ ParentHash: parent.Hash(), Number: num.Add(num, common.Big1), - GasLimit: math.MaxUint64, Time: uint64(timestamp), + GasLimit: parent.GasLimit, + } + // Set baseFee and GasLimit if we are on an EIP-1559 chain + if m.chain.Config().IsLondon(header.Number) { + header.BaseFee = misc.CalcBaseFee(m.chain.Config(), parent) + if !m.chain.Config().IsLondon(num) { + parentGasLimit := parent.GasLimit * m.chain.Config().ElasticityMultiplier() + header.GasLimit = core.CalcGasLimit(parentGasLimit, parentGasLimit) + } } m.makeCurrent(header) diff --git a/consensus/hotstuff/mock/mock_commit_test.go b/consensus/hotstuff/mock/mock_commit_test.go index 3a71dcba..6bcf64b5 100644 --- a/consensus/hotstuff/mock/mock_commit_test.go +++ b/consensus/hotstuff/mock/mock_commit_test.go @@ -24,6 +24,9 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/contracts/native/boot" + "github.com/ethereum/go-ethereum/contracts/native/governance/node_manager" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/hotstuff/core" "github.com/ethereum/go-ethereum/log" @@ -33,6 +36,8 @@ import ( // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitCase1 // net scale is 4, leader send fake message of commit with wrong height, repos change view. func TestMockCommitCase1(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH := uint64(4), uint64(0), uint64(5) sys := makeSystem(4) @@ -79,6 +84,8 @@ func TestMockCommitCase1(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitCase2 // net scale is 4, leader send fake message of commit with wrong round, repos change view. func TestMockCommitCase2(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR := uint64(4), uint64(0), uint64(1) sys := makeSystem(4) @@ -125,6 +132,8 @@ func TestMockCommitCase2(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitCase3 // net scale is 4, leader send fake message of commit with wrong qc.height, repos change view. func TestMockCommitCase3(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH := uint64(4), uint64(0), uint64(3) sys := makeSystem(4) @@ -185,6 +194,8 @@ func TestMockCommitCase3(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitCase4 // net scale is 4, leader send fake message of commit with wrong qc.round, repos change view. func TestMockCommitCase4(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR := uint64(4), uint64(0), uint64(1) sys := makeSystem(4) @@ -245,6 +256,8 @@ func TestMockCommitCase4(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitCase5 // net scale is 4, leader send fake message of commit with wrong qc.digest, repos change view. func TestMockCommitCase5(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R := uint64(4), uint64(0) sys := makeSystem(4) @@ -305,6 +318,8 @@ func TestMockCommitCase5(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitCase6 // net scale is 4, leader send fake message of commit without enough qc.committedSeal, repos change view. func TestMockCommitCase6(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R := uint64(4), uint64(0) sys := makeSystem(4) @@ -365,6 +380,8 @@ func TestMockCommitCase6(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitCase7 // net scale is 4, leader send fake message of commit to some one repo, repos WONT change view. func TestMockCommitCase7(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), int32(1) var locked int32 @@ -433,6 +450,8 @@ func TestMockCommitCase7(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitVoteCase1 // net scale is 4, leader send fake message of commitVote with wrong height. repos wont change view func TestMockCommitVoteCase1(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH, fN := uint64(4), uint64(0), uint64(5), int32(1) var locked int32 @@ -489,6 +508,8 @@ func TestMockCommitVoteCase1(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitVoteCase2 // net scale is 4, leader send fake message of commitVote with wrong height. repos change view func TestMockCommitVoteCase2(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH, fN := uint64(4), uint64(0), uint64(5), int32(2) var locked int32 @@ -545,6 +566,8 @@ func TestMockCommitVoteCase2(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitVoteCase3 // net scale is 4, leader send fake message of commitVote with wrong round. repos WONT change view func TestMockCommitVoteCase3(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR, fN := uint64(4), uint64(0), uint64(1), int32(1) var locked int32 @@ -601,6 +624,8 @@ func TestMockCommitVoteCase3(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitVoteCase4 // net scale is 4, leader send fake message of commitVote with wrong round. repos change view func TestMockCommitVoteCase4(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR, fN := uint64(4), uint64(0), uint64(1), int32(2) var locked int32 @@ -657,6 +682,8 @@ func TestMockCommitVoteCase4(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitVoteCase5 // net scale is 4, leader send fake message of commitVote with wrong digest. repos WONT change view func TestMockCommitVoteCase5(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), int32(1) var locked int32 @@ -713,6 +740,8 @@ func TestMockCommitVoteCase5(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockCommitVoteCase6 // net scale is 4, leader send fake message of commitVote with wrong digest. repos change view func TestMockCommitVoteCase6(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), int32(2) var locked int32 diff --git a/consensus/hotstuff/mock/mock_decide_test.go b/consensus/hotstuff/mock/mock_decide_test.go index 185e3b30..d7fa573e 100644 --- a/consensus/hotstuff/mock/mock_decide_test.go +++ b/consensus/hotstuff/mock/mock_decide_test.go @@ -23,6 +23,9 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/contracts/native/boot" + "github.com/ethereum/go-ethereum/contracts/native/governance/node_manager" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/hotstuff/core" "github.com/ethereum/go-ethereum/log" @@ -32,6 +35,8 @@ import ( // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockDecideCase1 // net scale is 4, leader send fake message of decide with wrong height, repos change view. func TestMockDecideCase1(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH := uint64(4), uint64(0), uint64(5) sys := makeSystem(4) @@ -78,6 +83,8 @@ func TestMockDecideCase1(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockDecideCase2 // net scale is 4, leader send fake message of decide with wrong round, repos change view. func TestMockDecideCase2(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR := uint64(4), uint64(0), uint64(1) sys := makeSystem(4) @@ -124,6 +131,8 @@ func TestMockDecideCase2(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockDecideCase3 // net scale is 4, leader send fake message of decide with wrong block hash, repos change view. func TestMockDecideCase3(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R := uint64(4), uint64(0) sys := makeSystem(4) @@ -184,6 +193,8 @@ func TestMockDecideCase3(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockDecideCase4 // net scale is 4, leader send fake message of decide with wrong qc.node, repos change view. func TestMockDecideCase4(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R := uint64(4), uint64(0) sys := makeSystem(4) diff --git a/consensus/hotstuff/mock/mock_new_view_test.go b/consensus/hotstuff/mock/mock_new_view_test.go index b9db771e..ebe1bef7 100644 --- a/consensus/hotstuff/mock/mock_new_view_test.go +++ b/consensus/hotstuff/mock/mock_new_view_test.go @@ -24,6 +24,9 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/contracts/native/boot" + "github.com/ethereum/go-ethereum/contracts/native/governance/node_manager" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/hotstuff/core" "github.com/ethereum/go-ethereum/log" @@ -32,7 +35,9 @@ import ( // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestSimple func TestSimple(t *testing.T) { - sys := makeSystem(7) + node_manager.InitABI() + boot.InitNativeContracts() + sys := makeSystem(4) sys.Start() sys.Close(10) } @@ -40,6 +45,8 @@ func TestSimple(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockNewViewCase1 // net scale is 7, 2 of them send fake message of newView with wrong height. func TestMockNewViewCase1(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR, fN := uint64(4), uint64(0), uint64(1), int(1) fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) @@ -104,6 +111,8 @@ func TestMockNewViewCase1(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockNewViewCase2 // net scale is 4, one of them send fake message of newView with wrong node. err should be "failed to verify prepareQC" func TestMockNewViewCase2(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), 1 fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) @@ -179,6 +188,8 @@ func TestMockNewViewCase2(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockNewViewCase3 // net scale is 4, one of them send message of newView to wrong leader func TestMockNewViewCase3(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), 1 fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) @@ -243,6 +254,8 @@ func TestMockNewViewCase3(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockNewViewCase4 // net scale is 4, one of them send fake message of newView with wrong height. err should be "failed to verify prepareQC" func TestMockNewViewCase4(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH, fN := uint64(4), uint64(0), uint64(5), 1 fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) @@ -318,6 +331,8 @@ func TestMockNewViewCase4(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockNewViewCase5 // net scale is 4, one of them send fake message of newView with wrong round. err should be "failed to verify prepareQC" func TestMockNewViewCase5(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR, fN := uint64(4), uint64(0), uint64(1), 1 fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) @@ -392,6 +407,8 @@ func TestMockNewViewCase5(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockNewViewCase6 // net scale is 4, one of them send fake message of newView without enough signatures. err should be "failed to verify prepareQC" func TestMockNewViewCase6(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), 1 fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) diff --git a/consensus/hotstuff/mock/mock_precommit_test.go b/consensus/hotstuff/mock/mock_precommit_test.go index 4a4f87dc..a4c84f2c 100644 --- a/consensus/hotstuff/mock/mock_precommit_test.go +++ b/consensus/hotstuff/mock/mock_precommit_test.go @@ -24,6 +24,9 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/contracts/native/boot" + "github.com/ethereum/go-ethereum/contracts/native/governance/node_manager" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/hotstuff/core" "github.com/ethereum/go-ethereum/log" @@ -33,6 +36,8 @@ import ( // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitCase1 // net scale is 4, leader send fake message of preCommit with wrong height, repos change view. func TestMockPreCommitCase1(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH := uint64(4), uint64(0), uint64(5) sys := makeSystem(4) @@ -79,6 +84,8 @@ func TestMockPreCommitCase1(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitCase2 // net scale is 4, leader send fake message of preCommit with wrong round, repos change view. func TestMockPreCommitCase2(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR := uint64(4), uint64(0), uint64(1) sys := makeSystem(4) @@ -126,6 +133,8 @@ func TestMockPreCommitCase2(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitCase3 // net scale is 4, leader send fake message of preCommit with wrong qc.height, repos change view. func TestMockPreCommitCase3(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH := uint64(4), uint64(0), uint64(3) sys := makeSystem(4) @@ -186,6 +195,8 @@ func TestMockPreCommitCase3(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitCase4 // net scale is 4, leader send fake message of preCommit with wrong qc.height, repos change view. func TestMockPreCommitCase4(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH := uint64(4), uint64(0), uint64(5) sys := makeSystem(4) @@ -246,6 +257,8 @@ func TestMockPreCommitCase4(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitCase5 // net scale is 4, leader send fake message of preCommit with wrong qc.round, repos change view. func TestMockPreCommitCase5(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR := uint64(4), uint64(0), uint64(1) sys := makeSystem(4) @@ -306,6 +319,8 @@ func TestMockPreCommitCase5(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitCase6 // net scale is 4, leader send fake message of preCommit with wrong qc.digest, repos change view. func TestMockPreCommitCase6(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R := uint64(4), uint64(0) sys := makeSystem(4) @@ -366,6 +381,8 @@ func TestMockPreCommitCase6(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitCase7 // net scale is 4, leader send fake message of preCommit without enough qc.committedSeal, repos change view. func TestMockPreCommitCase7(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R := uint64(4), uint64(0) sys := makeSystem(4) @@ -426,6 +443,8 @@ func TestMockPreCommitCase7(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitCase8 // net scale is 4, leader send fake message of preCommit to some one repo, repos WONT change view. func TestMockPreCommitCase8(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), int32(1) var locked int32 @@ -494,6 +513,8 @@ func TestMockPreCommitCase8(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitVoteCase1 // net scale is 4, leader send fake message of preCommitVote with wrong height. repos wont change view func TestMockPreCommitVoteCase1(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH, fN := uint64(4), uint64(0), uint64(5), int32(1) var locked int32 @@ -550,6 +571,8 @@ func TestMockPreCommitVoteCase1(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitVoteCase2 // net scale is 4, leader send fake message of preCommitVote with wrong height. repos change view func TestMockPreCommitVoteCase2(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH, fN := uint64(4), uint64(0), uint64(5), int32(2) var locked int32 @@ -606,6 +629,8 @@ func TestMockPreCommitVoteCase2(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitVoteCase3 // net scale is 4, leader send fake message of preCommitVote with wrong round. repos WONT change view func TestMockPreCommitVoteCase3(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR, fN := uint64(4), uint64(0), uint64(1), int32(1) var locked int32 @@ -662,6 +687,8 @@ func TestMockPreCommitVoteCase3(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitVoteCase4 // net scale is 4, leader send fake message of preCommitVote with wrong round. repos change view func TestMockPreCommitVoteCase4(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR, fN := uint64(4), uint64(0), uint64(1), int32(2) var locked int32 @@ -718,6 +745,8 @@ func TestMockPreCommitVoteCase4(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitVoteCase5 // net scale is 4, leader send fake message of preCommitVote with wrong digest. repos WONT change view func TestMockPreCommitVoteCase5(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), int32(1) var locked int32 @@ -774,6 +803,8 @@ func TestMockPreCommitVoteCase5(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPreCommitVoteCase6 // net scale is 4, leader send fake message of preCommitVote with wrong digest. repos change view func TestMockPreCommitVoteCase6(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), int32(2) var locked int32 diff --git a/consensus/hotstuff/mock/mock_prepare_test.go b/consensus/hotstuff/mock/mock_prepare_test.go index 1e488ab3..716ad282 100644 --- a/consensus/hotstuff/mock/mock_prepare_test.go +++ b/consensus/hotstuff/mock/mock_prepare_test.go @@ -24,6 +24,9 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/contracts/native/boot" + "github.com/ethereum/go-ethereum/contracts/native/governance/node_manager" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/hotstuff/core" "github.com/ethereum/go-ethereum/log" @@ -33,6 +36,8 @@ import ( // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPrepareCase1 // net scale is 4, leader send fake message of prepare with wrong height, repos change view. func TestMockPrepareCase1(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH := uint64(4), uint64(0), uint64(5) sys := makeSystem(4) @@ -79,6 +84,8 @@ func TestMockPrepareCase1(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPrepareCase2 // net scale is 4, leader send fake message of prepare with wrong height, repos change view. func TestMockPrepareCase2(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR := uint64(4), uint64(0), uint64(1) sys := makeSystem(4) @@ -126,6 +133,8 @@ func TestMockPrepareCase2(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPrepareCase3 // net scale is 4, leader send fake message of prepare with wrong qc.view.height, repos change view. func TestMockPrepareCase3(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH := uint64(4), uint64(0), uint64(4) sys := makeSystem(4) @@ -200,6 +209,8 @@ func TestMockPrepareCase3(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPrepareCase4 // net scale is 4, leader send fake message of prepare with wrong qc.view.round, repos change view. func TestMockPrepareCase4(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR := uint64(4), uint64(0), uint64(1) sys := makeSystem(4) @@ -274,6 +285,8 @@ func TestMockPrepareCase4(t *testing.T) { // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPrepareCase5 // net scale is 4, leader send fake message of prepare with wrong qc.hash, repos change view. func TestMockPrepareCase5(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R := uint64(4), uint64(0) sys := makeSystem(4) @@ -346,8 +359,10 @@ func TestMockPrepareCase5(t *testing.T) { } // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPrepareVoteCase1 -// net scale is 4, leader send fake message of prepareVote with wrong height. +// net scale is 4, leader send fake message of prepareVote with wrong height. not change view func TestMockPrepareVoteCase1(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fH, fN := uint64(4), uint64(0), uint64(5), 1 fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) @@ -411,8 +426,10 @@ func TestMockPrepareVoteCase1(t *testing.T) { } // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPrepareVoteCase2 -// net scale is 4, leader send fake message of prepareVote with wrong round. +// net scale is 4, 1 replica send fake message of prepareVote with wrong round. not change view func TestMockPrepareVoteCase2(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fR, fN := uint64(4), uint64(0), uint64(1), 1 fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) @@ -476,8 +493,10 @@ func TestMockPrepareVoteCase2(t *testing.T) { } // go test -v -count=1 github.com/ethereum/go-ethereum/consensus/hotstuff/mock -run TestMockPrepareVoteCase3 -// net scale is 4, leader send fake message of prepareVote with wrong digest. +// net scale is 4, 1 replica send fake message of prepareVote with wrong digest. not change view func TestMockPrepareVoteCase3(t *testing.T) { + node_manager.InitABI() + boot.InitNativeContracts() H, R, fN := uint64(4), uint64(0), 1 fakeNodes := make(map[common.Address]struct{}) mu := new(sync.Mutex) diff --git a/consensus/hotstuff/mock/params.go b/consensus/hotstuff/mock/params.go index 958c1de3..ac861816 100644 --- a/consensus/hotstuff/mock/params.go +++ b/consensus/hotstuff/mock/params.go @@ -44,9 +44,7 @@ func init() { log.Root().SetHandler(glogger) } -func makeGenesis(vals []common.Address) *core.Genesis { - core.RegGenesis = nil - +func MakeGenesis(vals []common.Address) *core.Genesis { genesis := &core.Genesis{ Config: ¶ms.ChainConfig{ ChainID: big.NewInt(60801), @@ -71,14 +69,16 @@ func makeGenesis(vals []common.Address) *core.Genesis { Mixhash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), Timestamp: 0, + Alloc: make(map[common.Address]core.GenesisAccount, len(vals)), } govAccs := make([]core.GovernanceAccount, len(vals)) for i := 0; i < len(vals); i++ { govAccs[i] = core.GovernanceAccount{ Validator: vals[i], - Signer: common.EmptyAddress, + Signer: vals[i], } + genesis.Alloc[vals[i]] = core.GenesisAccount{Balance: new(big.Int).Div(params.GenesisSupply, big.NewInt(int64(len(vals))))} } genesis.Governance = govAccs diff --git a/consensus/hotstuff/mock/system.go b/consensus/hotstuff/mock/system.go index 04b9c8fe..b848c24d 100644 --- a/consensus/hotstuff/mock/system.go +++ b/consensus/hotstuff/mock/system.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/hotstuff/backend" hcore "github.com/ethereum/go-ethereum/consensus/hotstuff/core" "github.com/ethereum/go-ethereum/consensus/hotstuff/signer" + _ "github.com/ethereum/go-ethereum/contracts/native/governance/node_manager" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" @@ -54,6 +55,7 @@ func MakeGeth(privateKey *ecdsa.PrivateKey, vals []common.Address) *Geth { db := rawdb.NewMemoryDatabase() engine := makeEngine(privateKey, db) chain := makeChain(db, engine, vals) + hotstuffEngine := engine.(consensus.HotStuff) broadcaster := engine.(consensus.Handler).GetBroadcaster().(*broadcaster) api := engine.APIs(chain)[0].Service.(*backend.API) @@ -159,7 +161,7 @@ type System struct { } func makeSystem(n int) *System { - pks, addrs := newAccountLists(n) + pks, addrs := NewAccountLists(n) nodes := make([]*Geth, n) for i := 0; i < n; i++ { @@ -218,7 +220,7 @@ func (s *System) Leader() *Geth { return nil } -func newAccountLists(n int) ([]*ecdsa.PrivateKey, []common.Address) { +func NewAccountLists(n int) ([]*ecdsa.PrivateKey, []common.Address) { pks := make([]*ecdsa.PrivateKey, n) addrs := make([]common.Address, n) for i := 0; i < n; i++ { diff --git a/consensus/misc/eip1559.go b/consensus/misc/eip1559.go index 8fca0fdc..4521b47b 100644 --- a/consensus/misc/eip1559.go +++ b/consensus/misc/eip1559.go @@ -33,7 +33,7 @@ func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Heade // Verify that the gas limit remains within allowed bounds parentGasLimit := parent.GasLimit if !config.IsLondon(parent.Number) { - parentGasLimit = parent.GasLimit * params.ElasticityMultiplier + parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() } if err := VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { return err @@ -46,7 +46,7 @@ func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Heade 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) + header.BaseFee, expectedBaseFee, parent.BaseFee, parent.GasUsed) } return nil } @@ -58,36 +58,36 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { 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) - ) + parentGasTarget := parent.GasLimit / config.ElasticityMultiplier() // 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) } + + var ( + num = new(big.Int) + denom = new(big.Int) + ) + 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, - ) + // max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + num.SetUint64(parent.GasUsed - parentGasTarget) + num.Mul(num, parent.BaseFee) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + baseFeeDelta := math.BigMax(num, common.Big1) - return x.Add(parent.BaseFee, baseFeeDelta) + return num.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) + // max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + num.SetUint64(parentGasTarget - parent.GasUsed) + num.Mul(num, parent.BaseFee) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + baseFee := num.Sub(parent.BaseFee, num) - return math.BigMax( - x.Sub(parent.BaseFee, baseFeeDelta), - common.Big0, - ) + return math.BigMax(baseFee, common.Big0) } } diff --git a/console/console_test.go b/console/console_test.go index f6ab7814..c71541e5 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -28,8 +28,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/hotstuff/mock" "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" @@ -98,8 +98,9 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { if err != nil { t.Fatalf("failed to create node: %v", err) } + _, addrs := mock.NewAccountLists(4) ethConf := ðconfig.Config{ - Genesis: core.DeveloperGenesisBlock(15, common.Address{}), + Genesis: mock.MakeGenesis(addrs), Miner: miner.Config{ Etherbase: common.HexToAddress(testAddress), }, diff --git a/contracts/checkpointoracle/oracle_test.go b/contracts/checkpointoracle/oracle_test.go index 12184819..48f7ec5d 100644 --- a/contracts/checkpointoracle/oracle_test.go +++ b/contracts/checkpointoracle/oracle_test.go @@ -165,6 +165,7 @@ 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 TestCheckpointRegister(t *testing.T) { + core.CheckAllocWithTotalSupply = false // Initialize test accounts var accounts Accounts for i := 0; i < 3; i++ { @@ -175,10 +176,11 @@ func TestCheckpointRegister(t *testing.T) { sort.Sort(accounts) // Deploy registrar contract - 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) + contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{accounts[0].addr: {Balance: big.NewInt(1000000000000000000)}, accounts[1].addr: {Balance: big.NewInt(1000000000000000000)}, accounts[2].addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer contractBackend.Close() transactOpts, _ := bind.NewKeyedTransactorWithChainID(accounts[0].key, big.NewInt(1337)) + transactOpts.GasLimit = 10000000 // 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/contracts/native/economic/economic.go b/contracts/native/economic/economic.go index 1fc72299..c65ac734 100644 --- a/contracts/native/economic/economic.go +++ b/contracts/native/economic/economic.go @@ -38,11 +38,6 @@ var ( } ) -var ( - RewardPerBlock = params.ZNT1 - GenesisSupply = params.GenesisSupply -) - func InitEconomic() { InitABI() native.Contracts[this] = RegisterEconomicContract @@ -63,9 +58,9 @@ func Name(s *native.NativeContract) ([]byte, error) { func TotalSupply(s *native.NativeContract) ([]byte, error) { height := s.ContractRef().BlockHeight() - supply := GenesisSupply + supply := params.GenesisSupply if height.Uint64() > 0 { - reward := new(big.Int).Mul(height, RewardPerBlock) + reward := new(big.Int).Mul(height, params.RewardPerBlock) supply = new(big.Int).Add(supply, reward) } return utils.PackOutputs(ABI, MethodTotalSupply, supply) @@ -79,7 +74,7 @@ func getBlockRewardList(s *native.NativeContract) ([]*RewardAmount, error) { // allow empty address as reward pool poolAddr := community.CommunityAddress - rewardPerBlock := utils.NewDecFromBigInt(RewardPerBlock) + rewardPerBlock := utils.NewDecFromBigInt(params.RewardPerBlock) rewardFactor := utils.NewDecFromBigInt(community.CommunityRate) poolRwdAmt, err := rewardPerBlock.MulWithPercentDecimal(rewardFactor) if err != nil { diff --git a/contracts/native/governance/node_manager/distribute.go b/contracts/native/governance/node_manager/distribute.go index 0d7355c8..ad7769a1 100644 --- a/contracts/native/governance/node_manager/distribute.go +++ b/contracts/native/governance/node_manager/distribute.go @@ -96,53 +96,53 @@ func withdrawStakeRewards(s *native.NativeContract, validator *Validator, stakeI // end current period and calculate rewards endingPeriod, err := IncreaseValidatorPeriod(s, validator) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, IncreaseValidatorPeriod error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, IncreaseValidatorPeriod error: %v", err) } rewards, err := CalculateStakeRewards(s, stakeInfo.StakeAddress, validator.ConsensusAddress, endingPeriod) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, CalculateStakeRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, CalculateStakeRewards error: %v", err) } err = contract.NativeTransfer(s.StateDB(), this, stakeInfo.StakeAddress, rewards.BigInt()) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, nativeTransfer error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, nativeTransfer error: %v", err) } // update the outstanding rewards outstanding, err := getOutstandingRewards(s) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, getOutstandingRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, getOutstandingRewards error: %v", err) } validatorOutstanding, err := getValidatorOutstandingRewards(s, validator.ConsensusAddress) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, getValidatorOutstandingRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, getValidatorOutstandingRewards error: %v", err) } newOutstandingRewards, err := outstanding.Rewards.Sub(rewards) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, outstanding.Rewards.Sub error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, outstanding.Rewards.Sub error: %v", err) } err = setOutstandingRewards(s, &OutstandingRewards{Rewards: newOutstandingRewards}) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, setOutstandingRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, setOutstandingRewards error: %v", err) } newValidatorOutstandingRewards, err := validatorOutstanding.Rewards.Sub(rewards) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, validatorOutstanding.Rewards.Sub error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, validatorOutstanding.Rewards.Sub error: %v", err) } err = setValidatorOutstandingRewards(s, validator.ConsensusAddress, &ValidatorOutstandingRewards{Rewards: newValidatorOutstandingRewards}) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, setValidatorOutstandingRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, setValidatorOutstandingRewards error: %v", err) } // decrement reference count of starting period startingInfo, err := getStakeStartingInfo(s, stakeInfo.StakeAddress, validator.ConsensusAddress) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, getStakeStartingInfo error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, getStakeStartingInfo error: %v", err) } startPeriod := startingInfo.StartPeriod err = decreaseReferenceCount(s, validator.ConsensusAddress, startPeriod) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawStakeRewards, decreaseReferenceCount error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawStakeRewards, decreaseReferenceCount error: %v", err) } // remove stake starting info @@ -154,7 +154,7 @@ func CalculateStakeRewards(s *native.NativeContract, stakeAddress common.Address // fetch starting info for delegation startingInfo, err := getStakeStartingInfo(s, stakeAddress, consensusAddr) if err != nil { - return utils.Dec{nil}, fmt.Errorf("CalculateStakeRewards, getStakeStartingInfo error: %v", err) + return utils.Dec{}, fmt.Errorf("CalculateStakeRewards, getStakeStartingInfo error: %v", err) } startPeriod := startingInfo.StartPeriod @@ -168,19 +168,19 @@ func CalculateStakeRewards(s *native.NativeContract, stakeAddress common.Address // return staking * (ending - starting) starting, err := getValidatorSnapshotRewards(s, consensusAddr, startPeriod) if err != nil { - return utils.Dec{nil}, fmt.Errorf("CalculateStakeRewards, getValidatorSnapshotRewards start error: %v", err) + return utils.Dec{}, fmt.Errorf("CalculateStakeRewards, getValidatorSnapshotRewards start error: %v", err) } ending, err := getValidatorSnapshotRewards(s, consensusAddr, endPeriod) if err != nil { - return utils.Dec{nil}, fmt.Errorf("CalculateStakeRewards, getValidatorSnapshotRewards end error: %v", err) + return utils.Dec{}, fmt.Errorf("CalculateStakeRewards, getValidatorSnapshotRewards end error: %v", err) } difference, err := ending.AccumulatedRewardsRatio.Sub(starting.AccumulatedRewardsRatio) if err != nil { - return utils.Dec{nil}, fmt.Errorf("CalculateStakeRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("CalculateStakeRewards error: %v", err) } rewards, err := difference.MulWithTokenDecimal(stake) if err != nil { - return utils.Dec{nil}, fmt.Errorf("CalculateStakeRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("CalculateStakeRewards error: %v", err) } return rewards, nil } @@ -211,38 +211,38 @@ func initializeStake(s *native.NativeContract, stakeInfo *StakeInfo, consensusAd func withdrawCommission(s *native.NativeContract, stakeAddress common.Address, consensusAddr common.Address) (utils.Dec, error) { accumulatedCommission, err := getAccumulatedCommission(s, consensusAddr) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawCommission, getAccumulatedCommission error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawCommission, getAccumulatedCommission error: %v", err) } // update the outstanding rewards outstanding, err := getOutstandingRewards(s) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawCommission, getOutstandingRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawCommission, getOutstandingRewards error: %v", err) } validatorOutstanding, err := getValidatorOutstandingRewards(s, consensusAddr) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawCommission, getValidatorOutstandingRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawCommission, getValidatorOutstandingRewards error: %v", err) } newOutstandingRewards, err := outstanding.Rewards.Sub(accumulatedCommission.Amount) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawCommission, outstanding.Rewards.Sub error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawCommission, outstanding.Rewards.Sub error: %v", err) } err = setOutstandingRewards(s, &OutstandingRewards{Rewards: newOutstandingRewards}) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawCommission, setOutstandingRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawCommission, setOutstandingRewards error: %v", err) } newValidatorOutstandingRewards, err := validatorOutstanding.Rewards.Sub(accumulatedCommission.Amount) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawCommission, validatorOutstanding.Rewards.Sub error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawCommission, validatorOutstanding.Rewards.Sub error: %v", err) } err = setValidatorOutstandingRewards(s, consensusAddr, &ValidatorOutstandingRewards{Rewards: newValidatorOutstandingRewards}) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawCommission, setValidatorOutstandingRewards error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawCommission, setValidatorOutstandingRewards error: %v", err) } err = contract.NativeTransfer(s.StateDB(), this, stakeAddress, accumulatedCommission.Amount.BigInt()) if err != nil { - return utils.Dec{nil}, fmt.Errorf("withdrawCommission, nativeTransfer commission error: %v", err) + return utils.Dec{}, fmt.Errorf("withdrawCommission, nativeTransfer commission error: %v", err) } return accumulatedCommission.Amount, nil } diff --git a/contracts/native/governance/node_manager/external.go b/contracts/native/governance/node_manager/external.go index 8dee1b08..ec9f57a1 100644 --- a/contracts/native/governance/node_manager/external.go +++ b/contracts/native/governance/node_manager/external.go @@ -34,7 +34,7 @@ var ( GenesisMaxCommissionChange, _ = new(big.Int).SetString("500", 10) // 5% GenesisMinInitialStake = new(big.Int).Mul(big.NewInt(100000), params.ZNT1) GenesisMinProposalStake = new(big.Int).Mul(big.NewInt(1000), params.ZNT1) - GenesisBlockPerEpoch = new(big.Int).SetUint64(400000) + GenesisBlockPerEpoch = new(big.Int).SetUint64(40) GenesisConsensusValidatorNum uint64 = 4 GenesisVoterValidatorNum uint64 = 4 diff --git a/contracts/native/governance/node_manager/manager.go b/contracts/native/governance/node_manager/manager.go index bab206c1..0e3996df 100644 --- a/contracts/native/governance/node_manager/manager.go +++ b/contracts/native/governance/node_manager/manager.go @@ -143,13 +143,13 @@ func CreateValidator(s *native.NativeContract) ([]byte, error) { // check consensus address if params.ConsensusAddress == common.EmptyAddress { - return nil, fmt.Errorf("CreateValidator, invalid consensus address") + return nil, fmt.Errorf("CreateValidator, invalid consensus address") } if params.SignerAddress == common.EmptyAddress { - return nil, fmt.Errorf("CreateValidator, invalid signer address") + return nil, fmt.Errorf("CreateValidator, invalid signer address") } if params.ProposalAddress == common.EmptyAddress { - return nil, fmt.Errorf("CreateValidator, invalid proposalAddress") + return nil, fmt.Errorf("CreateValidator, invalid proposalAddress") } // check commission @@ -643,8 +643,8 @@ func ChangeEpoch(s *native.NativeContract) ([]byte, error) { // anyone can call this if height reaches if startHeight.Cmp(currentEpochInfo.EndHeight) != 0 { - return nil, fmt.Errorf("ChangeEpoch, block height does not reach, current epoch end at %s", - currentEpochInfo.EndHeight.String()) + return nil, fmt.Errorf("ChangeEpoch, block height does not reach, current epoch end at %s, pending block %s", + currentEpochInfo.EndHeight.String(), startHeight.String()) } epochInfo := &EpochInfo{ diff --git a/contracts/native/governance/node_manager/manager_test.go b/contracts/native/governance/node_manager/manager_test.go index 79fdc2e1..ba599f87 100644 --- a/contracts/native/governance/node_manager/manager_test.go +++ b/contracts/native/governance/node_manager/manager_test.go @@ -24,6 +24,9 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/contracts/native/contract" + "github.com/ethereum/go-ethereum/contracts/native/governance/community" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/contracts/native" "github.com/ethereum/go-ethereum/contracts/native/contract" @@ -44,6 +47,8 @@ var ( ) func Init() { + GenesisBlockPerEpoch = big.NewInt(399999 + 1) + key, _ := crypto.GenerateKey() acct = &key.PublicKey @@ -51,8 +56,8 @@ func Init() { sdb = native.NewTestStateDB() testGenesisPeers, _ = native.GenerateTestPeers(testGenesisNum) community.StoreCommunityInfo(sdb, big.NewInt(2000), common.EmptyAddress) - StoreGenesisEpochForTest(sdb, testGenesisPeers, testGenesisPeers, big.NewInt(400000)) - StoreGenesisGlobalConfigForTest(sdb, big.NewInt(400000)) + StoreGenesisEpoch(sdb, testGenesisPeers, testGenesisPeers) + StoreGenesisGlobalConfig(sdb) } func TestCheckGenesis(t *testing.T) { @@ -134,7 +139,7 @@ func TestCheckGenesis(t *testing.T) { func TestStake(t *testing.T) { Init() - blockNumber := big.NewInt(399999) + blockNumber := new(big.Int).Sub(GenesisBlockPerEpoch, common.Big1) extra := uint64(21000000000000) contractRefQuery := native.NewContractRef(sdb, common.EmptyAddress, common.EmptyAddress, blockNumber, common.Hash{}, extra, nil) contractQuery := native.NewNativeContract(sdb, contractRefQuery) @@ -243,7 +248,7 @@ func TestStake(t *testing.T) { epochInfo, err := GetCurrentEpochInfoImpl(contractQuery) assert.Nil(t, err) assert.Equal(t, epochInfo.ID, common.Big2) - assert.Equal(t, epochInfo.StartHeight, new(big.Int).SetUint64(400000)) + assert.Equal(t, epochInfo.StartHeight, GenesisBlockPerEpoch) assert.Equal(t, len(epochInfo.Validators), 4) assert.Equal(t, len(epochInfo.Voters), 4) validator, _, err = getValidator(contractQuery, validatorsKey[0].ConsensusAddr) @@ -514,6 +519,7 @@ func TestChangeEpoch(t *testing.T) { } func TestDistribute(t *testing.T) { + params.RewardPerBlock = common.Big0 Init() blockNumber := big.NewInt(399999) extra := uint64(21000000000000) diff --git a/contracts/native/governance/node_manager/storage.go b/contracts/native/governance/node_manager/storage.go index bffea74f..b37616bb 100644 --- a/contracts/native/governance/node_manager/storage.go +++ b/contracts/native/governance/node_manager/storage.go @@ -553,7 +553,7 @@ func filterExpiredUnlockingInfo(s *native.NativeContract, stakeAddress common.Ad height := s.ContractRef().BlockHeight() unlockingInfo, err := getUnlockingInfo(s, stakeAddress) if err != nil { - return utils.Dec{nil}, fmt.Errorf("filterExpiredUnlockingInfo, GetUnlockingInfo error: %v", err) + return utils.Dec{}, fmt.Errorf("filterExpiredUnlockingInfo, GetUnlockingInfo error: %v", err) } j := 0 expiredSum := utils.NewDecFromBigInt(new(big.Int)) @@ -564,7 +564,7 @@ func filterExpiredUnlockingInfo(s *native.NativeContract, stakeAddress common.Ad } else { expiredSum, err = expiredSum.Add(unlockingStake.Amount) if err != nil { - return utils.Dec{nil}, fmt.Errorf("filterExpiredUnlockingInfo, expiredSum.Add error: %v", err) + return utils.Dec{}, fmt.Errorf("filterExpiredUnlockingInfo, expiredSum.Add error: %v", err) } } } @@ -574,7 +574,7 @@ func filterExpiredUnlockingInfo(s *native.NativeContract, stakeAddress common.Ad } else { err = setUnlockingInfo(s, unlockingInfo) if err != nil { - return utils.Dec{nil}, fmt.Errorf("filterExpiredUnlockingInfo, setUnlockingInfo error: %v", err) + return utils.Dec{}, fmt.Errorf("filterExpiredUnlockingInfo, setUnlockingInfo error: %v", err) } } return expiredSum, nil @@ -932,4 +932,4 @@ func signKey(hash common.Hash) []byte { func signerKey(hash common.Hash) []byte { return utils.ConcatKey(this, []byte(SKP_SIGNER), hash.Bytes()) -} \ No newline at end of file +} diff --git a/contracts/native/governance/proposal_manager/proposal_manager_test.go b/contracts/native/governance/proposal_manager/proposal_manager_test.go index 61a1e9ea..a271a615 100644 --- a/contracts/native/governance/proposal_manager/proposal_manager_test.go +++ b/contracts/native/governance/proposal_manager/proposal_manager_test.go @@ -76,7 +76,7 @@ func TestProposalManager(t *testing.T) { assert.Equal(t, communityInfo.CommunityRate, big.NewInt(2000)) assert.Equal(t, communityInfo.CommunityAddress, common.EmptyAddress) - sdb.SetBalance(common.EmptyAddress, new(big.Int).Mul(big.NewInt(100000), params.ZNT1)) + sdb.SetBalance(common.EmptyAddress, new(big.Int).Mul(big.NewInt(10000000), params.ZNT1)) value := new(big.Int).Mul(big.NewInt(1000), params.ZNT1) // Propose for i := 0; i < ProposalListLen; i++ { @@ -93,6 +93,7 @@ func TestProposalManager(t *testing.T) { // Propose config param2 := new(ProposeConfigParam) globalConfig.VoterValidatorNum = 2 + globalConfig.BlockPerEpoch = big.NewInt(10000) param2.Content, err = rlp.EncodeToBytes(globalConfig) assert.Nil(t, err) input, err := param2.Encode() @@ -262,6 +263,6 @@ func TestProposalManager(t *testing.T) { communityInfo, err = community.GetCommunityInfoImpl(c) assert.Nil(t, err) assert.Equal(t, communityInfo.CommunityRate, big.NewInt(1000)) - assert.Equal(t, sdb.GetBalance(common.EmptyAddress), new(big.Int).Mul(big.NewInt(81000), params.ZNT1)) - assert.Equal(t, sdb.GetBalance(communityInfo.CommunityAddress), new(big.Int).Mul(big.NewInt(81000), params.ZNT1)) + assert.Equal(t, sdb.GetBalance(common.EmptyAddress), new(big.Int).Mul(big.NewInt(9981000), params.ZNT1)) + assert.Equal(t, sdb.GetBalance(communityInfo.CommunityAddress), new(big.Int).Mul(big.NewInt(9981000), params.ZNT1)) } diff --git a/contracts/native/utils/params.go b/contracts/native/utils/params.go index 1dd7941a..59be8461 100644 --- a/contracts/native/utils/params.go +++ b/contracts/native/utils/params.go @@ -45,4 +45,4 @@ var ( ETH_COMMON_ROUTER = uint64(2) RIPPLE_ROUTER = uint64(6) -) +) \ No newline at end of file diff --git a/core/bench_test.go b/core/bench_test.go index ce288d37..2642252d 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -75,7 +75,7 @@ var ( // This is the content of the genesis block used by the benchmarks. benchRootKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") benchRootAddr = crypto.PubkeyToAddress(benchRootKey.PublicKey) - benchRootFunds = math.BigPow(2, 100) + benchRootFunds = math.BigPow(2, 200) ) // genValueTx returns a block generator that includes a single @@ -86,7 +86,19 @@ func genValueTx(nbytes int) func(int, *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) 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) + signer := types.MakeSigner(gen.config, big.NewInt(int64(i))) + gasPrice := big.NewInt(0) + if gen.header.BaseFee != nil { + gasPrice = gen.header.BaseFee + } + tx, _ := types.SignNewTx(benchRootKey, signer, &types.LegacyTx{ + Nonce: gen.TxNonce(benchRootAddr), + To: &toaddr, + Value: big.NewInt(1), + Gas: gas, + Data: data, + GasPrice: gasPrice, + }) gen.AddTx(tx) } } @@ -110,24 +122,38 @@ func init() { // and fills the blocks with many small transactions. func genTxRing(naccounts int) func(int, *BlockGen) { from := 0 + availableFunds := new(big.Int).Set(benchRootFunds) return func(i int, gen *BlockGen) { block := gen.PrevBlock(i - 1) gas := block.GasLimit() + gasPrice := big.NewInt(0) + if gen.header.BaseFee != nil { + gasPrice = gen.header.BaseFee + } + signer := types.MakeSigner(gen.config, big.NewInt(int64(i))) for { gas -= params.TxGas if gas < params.TxGas { break } to := (from + 1) % naccounts - tx := types.NewTransaction( - gen.TxNonce(ringAddrs[from]), - ringAddrs[to], - benchRootFunds, - params.TxGas, - nil, - nil, - ) - tx, _ = types.SignTx(tx, types.HomesteadSigner{}, ringKeys[from]) + burn := new(big.Int).SetUint64(params.TxGas) + burn.Mul(burn, gen.header.BaseFee) + availableFunds.Sub(availableFunds, burn) + if availableFunds.Cmp(big.NewInt(1)) < 0 { + panic("not enough funds") + } + tx, err := types.SignNewTx(ringKeys[from], signer, + &types.LegacyTx{ + Nonce: gen.TxNonce(ringAddrs[from]), + To: &ringAddrs[to], + Value: availableFunds, + Gas: params.TxGas, + GasPrice: gasPrice, + }) + if err != nil { + panic(err) + } gen.AddTx(tx) from = to } @@ -147,6 +173,8 @@ func genUncles(i int, gen *BlockGen) { } func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { + RegGenesis = nil + CheckAllocWithTotalSupply = false // Create the database in memory or in a temporary directory. var db ethdb.Database if !disk { @@ -241,10 +269,16 @@ func makeChainForBench(db ethdb.Database, full bool, count uint64) { rawdb.WriteCanonicalHash(db, hash, n) rawdb.WriteTd(db, hash, n, big.NewInt(int64(n+1))) + if n == 0 { + rawdb.WriteChainConfig(db, hash, params.AllEthashProtocolChanges) + } + rawdb.WriteHeadHeaderHash(db, hash) + if full || n == 0 { block := types.NewBlockWithHeader(header) rawdb.WriteBody(db, hash, n, block.Body()) rawdb.WriteReceipts(db, hash, n, nil) + rawdb.WriteHeadBlockHash(db, hash) } } } @@ -266,11 +300,7 @@ func benchWriteChain(b *testing.B, full bool, count uint64) { } func benchReadChain(b *testing.B, full bool, count uint64) { - dir, err := ioutil.TempDir("", "eth-chain-bench") - if err != nil { - b.Fatalf("cannot create temporary directory: %v", err) - } - defer os.RemoveAll(dir) + dir := b.TempDir() db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) if err != nil { diff --git a/core/block_validator.go b/core/block_validator.go index d317d82e..9eb78008 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -103,44 +103,9 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD } // CalcGasLimit computes the gas limit of the next block after parent. It aims -// 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(parentGasUsed, parentGasLimit, gasFloor, gasCeil uint64) uint64 { - // contrib = (parentGasUsed * 3 / 2) / 1024 - contrib := (parentGasUsed + parentGasUsed/2) / params.GasLimitBoundDivisor - - // decay = parentGasLimit / 1024 -1 - decay := parentGasLimit/params.GasLimitBoundDivisor - 1 - - /* - strategy: gasLimit of block-to-mine is set based on parent's - gasUsed value. if parentGasUsed > parentGasLimit * (2/3) then we - increase it, otherwise lower it (or leave it unchanged if it's right - at that usage) the amount increased/decreased depends on how far away - from parentGasLimit * (2/3) parentGasUsed is. - */ - 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 = parentGasLimit + decay - if limit > gasFloor { - limit = gasFloor - } - } else if limit > gasCeil { - 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 { +// to keep the baseline gas close to the provided target, and increase it towards +// the target if the baseline gas is lower. +func CalcGasLimit(parentGasLimit, desiredLimit uint64) uint64 { delta := parentGasLimit/params.GasLimitBoundDivisor - 1 limit := parentGasLimit if desiredLimit < params.MinGasLimit { @@ -161,4 +126,4 @@ func CalcGasLimit1559(parentGasLimit, desiredLimit uint64) uint64 { } } return limit -} +} \ No newline at end of file diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 3b4de337..aade0761 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -30,6 +30,7 @@ import ( // Tests that simple header verification works, for both good and bad blocks. func TestHeaderVerification(t *testing.T) { + CheckAllocWithTotalSupply = false // Create a simple chain to verify var ( testdb = rawdb.NewMemoryDatabase() @@ -198,8 +199,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { } } -func TestCalcGasLimit1559(t *testing.T) { - +func TestCalcGasLimit(t *testing.T) { for i, tc := range []struct { pGasLimit uint64 max uint64 @@ -209,23 +209,23 @@ func TestCalcGasLimit1559(t *testing.T) { {40000000, 40039061, 39960939}, } { // Increase - if have, want := CalcGasLimit1559(tc.pGasLimit, 2*tc.pGasLimit), tc.max; have != want { + if have, want := CalcGasLimit(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 { + if have, want := CalcGasLimit(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 { + if have, want := CalcGasLimit(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 { + if have, want := CalcGasLimit(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 { + if have, want := CalcGasLimit(tc.pGasLimit, tc.pGasLimit), tc.pGasLimit; have != want { t.Errorf("test %d: have %d want %d", i, have, want) } } diff --git a/core/blockchain.go b/core/blockchain.go index 735ea60c..0b0edbb6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -23,6 +23,7 @@ import ( "io" "math/big" mrand "math/rand" + "runtime" "sort" "sync" "sync/atomic" @@ -1765,7 +1766,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er case err != nil: bc.futureBlocks.Remove(block.Hash()) stats.ignored += len(it.chain) - bc.reportBlock(block, nil, err) + bc.reportBlock(block, nil, err) return it.index, err } // No validation errors for the first block (or chain prefix skipped) @@ -2355,24 +2356,28 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { // reportBlock logs a bad block error. func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) { rawdb.WriteBadBlock(bc.db, block) + log.Error(summarizeBadBlock(block, receipts, bc.Config(), err)) +} +// summarizeBadBlock returns a string summarizing the bad block and other +// relevant information. +func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string { var receiptString string for i, receipt := range receipts { - receiptString += fmt.Sprintf("\t %d: cumulative: %v gas: %v contract: %v status: %v tx: %v logs: %v bloom: %x state: %x\n", + receiptString += fmt.Sprintf("\n %d: cumulative: %v gas: %v contract: %v status: %v tx: %v logs: %v bloom: %x state: %x", i, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.ContractAddress.Hex(), - receipt.Status, receipt.TxHash.Hex(), receipt.LogString(), receipt.Bloom, receipt.PostState) + receipt.Status, receipt.TxHash.Hex(), receipt.Logs, receipt.Bloom, receipt.PostState) } - log.Error(fmt.Sprintf(` + platform := fmt.Sprintf("%s %s %s", runtime.Version(), runtime.GOARCH, runtime.GOOS) + return fmt.Sprintf(` ########## BAD BLOCK ######### -Chain config: %v - -Number: %v -Hash: 0x%x -%v - +Block: %v (%#x) Error: %v +Platform: %v +Chain config: %#v +Receipts: %v ############################## -`, bc.chainConfig, block.Number(), block.Hash(), receiptString, err)) +`, block.Number(), block.Hash(), err, platform, config, receiptString) } // InsertHeaderChain attempts to insert the given header chain in to the local diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 8bb39d26..feac193a 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1769,8 +1769,11 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { defer db.Close() // Might double close, should be fine // Initialize a fresh chain + gspec := &Genesis{ + Config: params.TestChainConfig, + } var ( - genesis = new(Genesis).MustCommit(db) + genesis = gspec.MustCommit(db) engine = ethash.NewFullFaker() config = &CacheConfig{ TrieCleanLimit: 256, diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index e99b09cf..2a5e4526 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -1968,8 +1968,11 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { defer db.Close() // Initialize a fresh chain + gspec := &Genesis{ + Config: params.TestChainConfig, + } var ( - genesis = new(Genesis).MustCommit(db) + genesis = gspec.MustCommit(db) engine = ethash.NewFullFaker() config = &CacheConfig{ TrieCleanLimit: 256, diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 75c09b42..a55fc90d 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -69,8 +69,11 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo t.Fatalf("Failed to create persistent database: %v", err) } // Initialize a fresh chain + gspec := &Genesis{ + Config: params.TestChainConfig, + } var ( - genesis = new(Genesis).MustCommit(db) + genesis = gspec.MustCommit(db) engine = ethash.NewFullFaker() gendb = rawdb.NewMemoryDatabase() diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 7fb4c6f6..4e5e3fcf 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -52,7 +52,7 @@ var ( func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) { var ( db = rawdb.NewMemoryDatabase() - genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis = (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) ) // Initialize a fresh chain with only a genesis block @@ -1444,7 +1444,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() - genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis := (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) // Generate a bunch of fork blocks, each side forking from the canonical chain @@ -1460,7 +1460,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { // Import the canonical and fork chain side by side, verifying the current block // and current header consistency diskdb := rawdb.NewMemoryDatabase() - (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) + (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) if err != nil { @@ -1489,7 +1489,7 @@ func TestTrieForkGC(t *testing.T) { engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() - genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis := (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) // Generate a bunch of fork blocks, each side forking from the canonical chain @@ -1504,7 +1504,7 @@ func TestTrieForkGC(t *testing.T) { } // Import the canonical and fork chain side by side, forcing the trie cache to cache both diskdb := rawdb.NewMemoryDatabase() - (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) + (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) if err != nil { @@ -1535,7 +1535,7 @@ func TestLargeReorgTrieGC(t *testing.T) { engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() - genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis := (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) original, _ := GenerateChain(params.TestChainConfig, shared[len(shared)-1], engine, db, 2*TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) }) @@ -1543,7 +1543,7 @@ func TestLargeReorgTrieGC(t *testing.T) { // Import the shared chain and the original canonical one diskdb := rawdb.NewMemoryDatabase() - (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) + (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) if err != nil { @@ -1701,13 +1701,13 @@ func TestIncompleteAncientReceiptChainInsertion(t *testing.T) { // overtake the 'canon' chain until after it's passed canon by about 200 blocks. // // Details at: -// - https://github.com/ethereum/go-ethereum/issues/18977 -// - https://github.com/ethereum/go-ethereum/pull/18988 +// - https://github.com/ethereum/go-ethereum/issues/18977 +// - https://github.com/ethereum/go-ethereum/pull/18988 func TestLowDiffLongChain(t *testing.T) { // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() - genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis := (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) // We must use a pretty long chain to ensure that the fork doesn't overtake us // until after at least 128 blocks post tip @@ -1718,7 +1718,7 @@ func TestLowDiffLongChain(t *testing.T) { // Import the canonical chain diskdb := rawdb.NewMemoryDatabase() - (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) + (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) if err != nil { @@ -1761,12 +1761,12 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() - genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis := (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) // Generate and import the canonical chain blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, nil) diskdb := rawdb.NewMemoryDatabase() - (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) + (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) @@ -1821,7 +1821,8 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon // That is: the sidechain for import contains some blocks already present in canon chain. // So the blocks are // [ Cn, Cn+1, Cc, Sn+3 ... Sm] -// ^ ^ ^ pruned +// +// ^ ^ ^ pruned func TestPrunedImportSide(t *testing.T) { //glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) //glogger.Verbosity(3) @@ -1841,7 +1842,7 @@ func testInsertKnownChainData(t *testing.T, typ string) { engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() - genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis := (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) blocks, receipts := GenerateChain(params.TestChainConfig, genesis, engine, db, 32, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) // A longer chain but total difficulty is lower. @@ -1861,7 +1862,7 @@ func testInsertKnownChainData(t *testing.T, typ string) { if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } - (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(chaindb) + (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(chaindb) defer os.RemoveAll(dir) chain, err := NewBlockChain(chaindb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) @@ -1964,7 +1965,7 @@ func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, error // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() - genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis := (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) // Generate and import the canonical chain, // Offset the time, to keep the difficulty low @@ -1972,7 +1973,7 @@ func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, error b.SetCoinbase(common.Address{1}) }) diskdb := rawdb.NewMemoryDatabase() - (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) + (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) if err != nil { @@ -2324,7 +2325,11 @@ func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks in for txi := 0; txi < numTxs; txi++ { uniq := uint64(i*numTxs + txi) recipient := recipientFn(uniq) - tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testBankKey) + gasPrice := big.NewInt(0) + if block.header.BaseFee != nil { + gasPrice = block.header.BaseFee + } + tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, gasPrice, nil), signer, testBankKey) if err != nil { b.Error(err) } @@ -2409,19 +2414,19 @@ func BenchmarkBlockChain_1x1000Executions(b *testing.B) { // This internally leads to a sidechain import, since the blocks trigger an // ErrPrunedAncestor error. // This may e.g. happen if -// 1. Downloader rollbacks a batch of inserted blocks and exits -// 2. Downloader starts to sync again -// 3. The blocks fetched are all known and canonical blocks +// 1. Downloader rollbacks a batch of inserted blocks and exits +// 2. Downloader starts to sync again +// 3. The blocks fetched are all known and canonical blocks func TestSideImportPrunedBlocks(t *testing.T) { // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() - genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis := (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) // Generate and import the canonical chain blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, nil) diskdb := rawdb.NewMemoryDatabase() - (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) + (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) @@ -2923,20 +2928,19 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { // TestInitThenFailCreateContract tests a pretty notorious case that happened // on mainnet over blocks 7338108, 7338110 and 7338115. -// - Block 7338108: address e771789f5cccac282f23bb7add5690e1f6ca467c is initiated -// with 0.001 ether (thus created but no code) -// - Block 7338110: a CREATE2 is attempted. The CREATE2 would deploy code on -// the same address e771789f5cccac282f23bb7add5690e1f6ca467c. However, the -// deployment fails due to OOG during initcode execution -// - Block 7338115: another tx checks the balance of -// e771789f5cccac282f23bb7add5690e1f6ca467c, and the snapshotter returned it as -// zero. +// - Block 7338108: address e771789f5cccac282f23bb7add5690e1f6ca467c is initiated +// with 0.001 ether (thus created but no code) +// - Block 7338110: a CREATE2 is attempted. The CREATE2 would deploy code on +// the same address e771789f5cccac282f23bb7add5690e1f6ca467c. However, the +// deployment fails due to OOG during initcode execution +// - Block 7338115: another tx checks the balance of +// e771789f5cccac282f23bb7add5690e1f6ca467c, and the snapshotter returned it as +// zero. // // The problem being that the snapshotter maintains a destructset, and adds items // to the destructset in case something is created "onto" an existing item. // We need to either roll back the snapDestructs, or not place it into snapDestructs // in the first place. -// func TestInitThenFailCreateContract(t *testing.T) { var ( // Generate a canonical chain to act as the main dataset @@ -3125,13 +3129,13 @@ func TestEIP2718Transition(t *testing.T) { // TestEIP1559Transition tests the following: // -// 1. A transaction whose gasFeeCap 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 -// gasFeeCap - gasTipCap < baseFee. -// 6. Legacy transaction behave as expected (e.g. gasPrice = gasFeeCap = gasTipCap). +// 1. A transaction whose gasFeeCap 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 +// gasFeeCap - gasTipCap < baseFee. +// 6. Legacy transaction behave as expected (e.g. gasPrice = gasFeeCap = gasTipCap). func TestEIP1559Transition(t *testing.T) { var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") @@ -3165,7 +3169,7 @@ func TestEIP1559Transition(t *testing.T) { }, } ) - + gspec.Config.ChainID = big.NewInt(1) gspec.Config.BerlinBlock = common.Big0 gspec.Config.LondonBlock = common.Big0 genesis := gspec.MustCommit(db) @@ -3220,10 +3224,7 @@ func TestEIP1559Transition(t *testing.T) { // 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].GasTipCap().Uint64()), - ethash.ConstantinopleBlockReward, - ) + expected := big.NewInt(2000000000000000000) if actual.Cmp(expected) != 0 { t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) } @@ -3260,10 +3261,10 @@ func TestEIP1559Transition(t *testing.T) { // 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, - ) + expected = big.NewInt(2000000000000000000) + if actual.Cmp(expected) != 0 { + t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) + } if actual.Cmp(expected) != 0 { t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) } diff --git a/core/chain_makers.go b/core/chain_makers.go index aae2a795..7d67af80 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" + "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" @@ -250,6 +251,19 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse return blocks, receipts } +// GenerateChainWithGenesis is a wrapper of GenerateChain which will initialize +// genesis block to database first according to the provided genesis specification +// then generate chain on top. +func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) { + db := rawdb.NewMemoryDatabase() + _, err := genesis.Commit(db) + if err != nil { + panic(err) + } + blocks, receipts := GenerateChain(genesis.Config, genesis.ToBlock(db), engine, db, n, gen) + return db, blocks, receipts +} + func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { var time uint64 if parent.Time() == 0 { @@ -274,11 +288,10 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S } if chain.Config().IsLondon(header.Number) { header.BaseFee = misc.CalcBaseFee(chain.Config(), parent.Header()) - parentGasLimit := parent.GasLimit() if !chain.Config().IsLondon(parent.Number()) { - parentGasLimit = parent.GasLimit() * params.ElasticityMultiplier + parentGasLimit := parent.GasLimit() * chain.Config().ElasticityMultiplier() + header.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) } - header.GasLimit = CalcGasLimit1559(parentGasLimit, parentGasLimit) } return header diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 85a029f7..77834d48 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -41,8 +41,8 @@ func ExampleGenerateChain() { // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ - Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, - Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + Config: params.TestChainConfig, + Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000000000000)}}, } genesis := gspec.MustCommit(db) @@ -54,13 +54,13 @@ func ExampleGenerateChain() { switch i { case 0: // In block 1, addr1 sends addr2 some ether. - tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(100000000000000), params.TxGas, big.NewInt(875000000), nil), signer, key1) gen.AddTx(tx) case 1: // In block 2, addr1 sends some more ether to addr2. // addr2 passes it on to addr3. - tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, nil, nil), signer, key1) - tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil), signer, key2) + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(100000000000000), params.TxGas, big.NewInt(875000000), nil), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(100000000000000), params.TxGas, big.NewInt(875000000), nil), signer, key2) gen.AddTx(tx1) gen.AddTx(tx2) case 2: @@ -94,7 +94,7 @@ func ExampleGenerateChain() { fmt.Println("balance of addr3:", state.GetBalance(addr3)) // Output: // last block: #5 - // balance of addr1: 989000 - // balance of addr2: 10000 - // balance of addr3: 19687500000000001000 + // balance of addr1: 763250000000000 + // balance of addr2: 81625000000000 + // balance of addr3: 7875100000000000000 } diff --git a/core/dao_test.go b/core/dao_test.go index b2a9f624..abeb5420 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -33,7 +33,9 @@ func TestDAOForkRangeExtradata(t *testing.T) { // Generate a common prefix for both pro-forkers and non-forkers db := rawdb.NewMemoryDatabase() - gspec := new(Genesis) + gspec := Genesis{ + Config: params.TestChainConfig, + } genesis := gspec.MustCommit(db) prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {}) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index a20598fa..37e47fae 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -22,195 +22,9 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" ) -// TestCreation tests that different genesis and fork rule combinations result in -// the correct fork ID. -func TestCreation(t *testing.T) { - type testcase struct { - head uint64 - want ID - } - tests := []struct { - config *params.ChainConfig - genesis common.Hash - cases []testcase - }{ - // Mainnet test cases - { - 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 - }, - }, - // Ropsten test cases - { - params.RopstenChainConfig, - params.RopstenGenesisHash, - []testcase{ - {0, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Unsynced, last Frontier, Homestead and first Tangerine block - {9, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Last Tangerine block - {10, ID{Hash: checksumToBytes(0x63760190), Next: 1700000}}, // First Spurious block - {1699999, ID{Hash: checksumToBytes(0x63760190), Next: 1700000}}, // Last Spurious block - {1700000, ID{Hash: checksumToBytes(0x3ea159c7), Next: 4230000}}, // First Byzantium block - {4229999, ID{Hash: checksumToBytes(0x3ea159c7), Next: 4230000}}, // Last Byzantium block - {4230000, ID{Hash: checksumToBytes(0x97b544f3), Next: 4939394}}, // First Constantinople block - {4939393, ID{Hash: checksumToBytes(0x97b544f3), Next: 4939394}}, // Last Constantinople block - {4939394, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // First Petersburg block - {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 - }, - }, - // Rinkeby test cases - { - params.RinkebyChainConfig, - params.RinkebyGenesisHash, - []testcase{ - {0, ID{Hash: checksumToBytes(0x3b8e0691), Next: 1}}, // Unsynced, last Frontier block - {1, ID{Hash: checksumToBytes(0x60949295), Next: 2}}, // First and last Homestead block - {2, ID{Hash: checksumToBytes(0x8bde40dd), Next: 3}}, // First and last Tangerine block - {3, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // First Spurious block - {1035300, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // Last Spurious block - {1035301, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // First Byzantium block - {3660662, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // Last Byzantium block - {3660663, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // First Constantinople block - {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 - }, - }, - // Goerli test cases - { - params.GoerliChainConfig, - params.GoerliGenesisHash, - []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 - }, - }, - } - for i, tt := range tests { - for j, ttt := range tt.cases { - 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) - } - } - } -} - -// TestValidation tests that a local peer correctly validates and accepts a remote -// fork ID. -func TestValidation(t *testing.T) { - tests := []struct { - head uint64 - id ID - err error - }{ - // Local is mainnet Petersburg, remote announces the same. No future fork is announced. - {7987396, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil}, - - // Local is mainnet Petersburg, remote announces the same. Remote also announces a next fork - // at block 0xffffffff, but that is uncertain. - {7987396, ID{Hash: checksumToBytes(0x668db0af), Next: math.MaxUint64}, nil}, - - // Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces - // also Byzantium, but it's not yet aware of Petersburg (e.g. non updated node before the fork). - // In this case we don't know if Petersburg passed yet or not. - {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil}, - - // Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces - // also Byzantium, and it's also aware of Petersburg (e.g. updated node before the fork). We - // don't know if Petersburg passed yet (will pass) or not. - {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, - - // Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces - // also Byzantium, and it's also aware of some random fork (e.g. misconfigured Petersburg). As - // 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 Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote - // is simply out of sync, accept. - {7987396, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, - - // Local is mainnet Petersburg, remote announces Spurious + knowledge about Byzantium. Remote - // is definitely out of sync. It may or may not need the Petersburg update, we don't know yet. - {7987396, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil}, - - // Local is mainnet Byzantium, remote announces Petersburg. Local is out of sync, accept. - {7279999, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil}, - - // Local is mainnet Spurious, remote announces Byzantium, but is not aware of Petersburg. Local - // out of sync. Local also knows about a future fork, but that is uncertain yet. - {4369999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil}, - - // Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks. - // Remote needs software update. - {7987396, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, ErrRemoteStale}, - - // Local is mainnet Petersburg, and isn't aware of more forks. Remote announces Petersburg + - // 0xffffffff. Local needs software update, reject. - {7987396, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale}, - - // Local is mainnet Byzantium, and is aware of Petersburg. Remote announces Petersburg + - // 0xffffffff. Local needs software update, reject. - {7279999, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale}, - - // 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) - // 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(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. - {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrLocalIncompatibleOrStale}, - } - for i, tt := range tests { - filter := newFilter(params.MainnetChainConfig, params.MainnetGenesisHash, func() uint64 { return tt.head }) - if err := filter(tt.id); err != tt.err { - t.Errorf("test %d: validation error mismatch: have %v, want %v", i, err, tt.err) - } - } -} - // Tests that IDs are properly RLP encoded (specifically important because we // use uint32 to store the hash, but we need to encode it as [4]byte). func TestEncoding(t *testing.T) { diff --git a/core/gen_genesis.go b/core/gen_genesis.go index c6fe947b..95c7b4d6 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -72,7 +72,7 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { Mixhash *common.Hash `json:"mixHash"` Coinbase *common.Address `json:"coinbase"` Alloc map[common.UnprefixedAddress]GenesisAccount `json:"alloc" gencodec:"required"` - Governance *GenesisGovernance `json:"governance" gencodec:"required"` + Governance *GenesisGovernance `json:"governance"` CommunityRate *big.Int `json:"community_rate" gencodec:"required"` CommunityAddress *common.Address `json:"community_address" gencodec:"required"` Number *math.HexOrDecimal64 `json:"number"` @@ -117,18 +117,16 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { for k, v := range dec.Alloc { g.Alloc[common.Address(k)] = v } - if dec.Governance == nil { - return errors.New("missing required field 'governance' for Genesis") + if dec.Governance != nil { + g.Governance = *dec.Governance } - g.Governance = *dec.Governance - if dec.CommunityRate == nil { - return errors.New("missing required field 'community_rate' for Genesis") + if dec.CommunityRate != nil { + g.CommunityRate = dec.CommunityRate } g.CommunityRate = dec.CommunityRate - if dec.CommunityAddress == nil { - return errors.New("missing required field 'community_address' for Genesis") + if dec.CommunityAddress != nil { + g.CommunityAddress = *dec.CommunityAddress } - g.CommunityAddress = *dec.CommunityAddress if dec.Number != nil { g.Number = uint64(*dec.Number) } diff --git a/core/genesis.go b/core/genesis.go index 112953de..c28b3557 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -58,10 +58,10 @@ type Genesis struct { Mixhash common.Hash `json:"mixHash"` Coinbase common.Address `json:"coinbase"` Alloc GenesisAlloc `json:"alloc" gencodec:"required"` - Governance GenesisGovernance `json:"governance" gencodec:"required"` + Governance GenesisGovernance `json:"governance"` // config of community pool - CommunityRate *big.Int `json:"community_rate" gencodec:"required"` - CommunityAddress common.Address `json:"community_address" gencodec:"required"` + CommunityRate *big.Int `json:"community_rate"` + CommunityAddress common.Address `json:"community_address"` // These fields are used for consensus tests. Please don't use them // in actual genesis blocks. @@ -318,7 +318,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { Coinbase: g.Coinbase, Root: root, } - if g.Config.HotStuff != nil { + if g.Config != nil && g.Config.HotStuff != nil { head.MixDigest = types.HotstuffDigest } if g.GasLimit == 0 { @@ -342,9 +342,10 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { // checkExtra validators should be sorted and do not allow dump validators. func (g *Genesis) checkExtra() { + if g.Config == nil || g.Config.HotStuff == nil { return } extra, err := types.ExtractHotstuffExtraPayload(g.ExtraData) if err != nil { - panic("extra invalid") + panic(err) } vs := extra.Validators @@ -357,41 +358,44 @@ func (g *Genesis) checkExtra() { // checkGovernance governance address and signer address can't be repeated func (g *Genesis) checkGovernance() { - data := make(map[common.Address]int) + validator := make(map[common.Address]int) + signer := make(map[common.Address]int) for _, v := range g.Governance { - data[v.Validator] += 1 - data[v.Signer] += 1 + validator[v.Validator] += 1 + signer[v.Signer] += 1 } - for addr, cnt := range data { + for addr, cnt := range validator { if cnt > 1 { - panic(fmt.Sprintf("address %s repeated %d", addr.String(), cnt)) + panic(fmt.Sprintf("validator address %s repeated %d", addr.String(), cnt)) + } + } + for addr, cnt := range signer { + if cnt > 1 { + panic(fmt.Sprintf("signer address %s repeated %d", addr.String(), cnt)) } } } func (g *Genesis) createNativeContract(db *state.StateDB, addr common.Address) { - if params.CheckZionChain(g.Config.ChainID.Uint64()) { - db.CreateAccount(addr) - db.SetCode(addr, addr[:]) - initBlockNumber := big.NewInt(0) - if g.Config.IsEIP158(initBlockNumber) { - db.SetNonce(addr, 1) - } + db.CreateAccount(addr) + db.SetCode(addr, addr[:]) + initBlockNumber := big.NewInt(0) + if g.Config.IsEIP158(initBlockNumber) { + db.SetNonce(addr, 1) } } +var CheckAllocWithTotalSupply bool = true func (g *Genesis) mintNativeToken(statedb *state.StateDB) { - if params.CheckZionChain(g.Config.ChainID.Uint64()) { - // check total balance - total := new(big.Int) - for _, account := range g.Alloc { - total = new(big.Int).Add(total, account.Balance) - } - if total.Cmp(params.GenesisSupply) != 0 { - panic("alloc amount should be equal to genesis supply") - } + // check total balance + total := new(big.Int) + for _, account := range g.Alloc { + total = new(big.Int).Add(total, account.Balance) + } + if CheckAllocWithTotalSupply && total.Cmp(params.GenesisSupply) != 0 { + panic(fmt.Sprintf("alloc amount %s should be equal to genesis supply %s", total, params.GenesisSupply)) } for addr, account := range g.Alloc { @@ -441,6 +445,7 @@ func (g *Genesis) MustCommit(db ethdb.Database) *types.Block { // GenesisBlockForTesting creates and writes a block in which addr has the given wei balance. func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block { + CheckAllocWithTotalSupply = false RegGenesis = func(db *state.StateDB, genesis *Genesis) error { return nil } @@ -452,6 +457,7 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big ChainID: new(big.Int).SetUint64(params.MainnetChainID), HotStuff: ¶ms.HotStuffConfig{}, } + g.ExtraData, _ = types.GenerateExtraWithSignature(0, 1, nil, []byte{}, [][]byte{}) return g.MustCommit(db) } @@ -464,6 +470,8 @@ func DefaultGenesisBlock() *Genesis { GasLimit: 5000, Difficulty: big.NewInt(17179869184), Alloc: decodePrealloc(mainnetAllocData), + CommunityRate: big.NewInt(2000), + CommunityAddress: common.HexToAddress("0x79ad3ca3faa0F30f4A0A2839D2DaEb4Eb6B6820D"), } } @@ -489,6 +497,7 @@ func DefaultRopstenGenesisBlock() *Genesis { GasLimit: 16777216, Difficulty: big.NewInt(1048576), Alloc: decodePrealloc(ropstenAllocData), + CommunityRate: big.NewInt(10), } } @@ -541,6 +550,8 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis { GasLimit: 11500000, BaseFee: big.NewInt(params.InitialBaseFee), Difficulty: big.NewInt(1), + CommunityRate: big.NewInt(20), + CommunityAddress: common.BytesToAddress([]byte{1}), Alloc: map[common.Address]GenesisAccount{ common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // Recover common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 diff --git a/core/genesis_test.go b/core/genesis_test.go index eec4c171..39504aa2 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -31,21 +31,19 @@ import ( ) func TestDefaultGenesisBlock(t *testing.T) { + CheckAllocWithTotalSupply = false block := DefaultGenesisBlock().ToBlock(nil) if block.Hash() != params.MainnetGenesisHash { t.Errorf("wrong mainnet genesis hash, got %v, want %v", block.Hash(), params.MainnetGenesisHash) } - block = DefaultRopstenGenesisBlock().ToBlock(nil) - if block.Hash() != params.RopstenGenesisHash { - t.Errorf("wrong ropsten genesis hash, got %v, want %v", block.Hash(), params.RopstenGenesisHash) - } } func TestSetupGenesis(t *testing.T) { + CheckAllocWithTotalSupply = false var ( customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50") customg = Genesis{ - Config: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3)}, + Config: ¶ms.ChainConfig{ChainID: params.TestChainConfig.ChainID, HomesteadBlock: big.NewInt(3)}, Alloc: GenesisAlloc{ {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, }, @@ -94,19 +92,10 @@ func TestSetupGenesis(t *testing.T) { wantHash: customghash, wantConfig: customg.Config, }, - { - name: "custom block in DB, genesis == ropsten", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - customg.MustCommit(db) - return SetupGenesisBlock(db, DefaultRopstenGenesisBlock()) - }, - wantErr: &GenesisMismatchError{Stored: customghash, New: params.RopstenGenesisHash}, - wantHash: params.RopstenGenesisHash, - wantConfig: params.RopstenChainConfig, - }, { name: "compatible config in DB", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + oldcustomg.Config.ChainID = params.TestChainConfig.ChainID oldcustomg.MustCommit(db) return SetupGenesisBlock(db, &customg) }, @@ -173,22 +162,6 @@ func TestGenesisHashes(t *testing.T) { genesis: DefaultGenesisBlock(), hash: params.MainnetGenesisHash, }, - { - genesis: DefaultGoerliGenesisBlock(), - hash: params.GoerliGenesisHash, - }, - { - genesis: DefaultRopstenGenesisBlock(), - hash: params.RopstenGenesisHash, - }, - { - genesis: DefaultRinkebyGenesisBlock(), - hash: params.RinkebyGenesisHash, - }, - { - genesis: DefaultBaikalGenesisBlock(), - hash: params.BaikalGenesisHash, - }, } for i, c := range cases { b := c.genesis.MustCommit(rawdb.NewMemoryDatabase()) diff --git a/core/headerchain_test.go b/core/headerchain_test.go index f3e40b62..1c8783a5 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -69,9 +69,14 @@ func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus // This test checks status reporting of InsertHeaderChain. func TestHeaderInsertion(t *testing.T) { + gspec := Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } var ( db = rawdb.NewMemoryDatabase() - genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis = gspec.MustCommit(db) ) hc, err := NewHeaderChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), func() bool { return false }) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 7cc9bfce..2adfd817 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -19,6 +19,7 @@ package rawdb import ( "bytes" "encoding/binary" + "errors" "math/big" "sort" @@ -766,6 +767,106 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block { return blocks } +// storedReceiptRLP is the storage encoding of a receipt. +// Re-definition in core/types/receipt.go. +type storedReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Logs []*types.LogForStorage +} + +// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps +// the list of logs. When decoding a stored receipt into this object we +// avoid creating the bloom filter. +type receiptLogs struct { + Logs []*types.Log +} + +// DecodeRLP implements rlp.Decoder. +func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error { + var stored storedReceiptRLP + if err := s.Decode(&stored); err != nil { + return err + } + r.Logs = make([]*types.Log, len(stored.Logs)) + for i, log := range stored.Logs { + r.Logs[i] = (*types.Log)(log) + } + return nil +} + +// DeriveLogFields fills the logs in receiptLogs with information such as block number, txhash, etc. +func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, txs types.Transactions) error { + logIndex := uint(0) + if len(txs) != len(receipts) { + return errors.New("transaction and receipt count mismatch") + } + for i := 0; i < len(receipts); i++ { + txHash := txs[i].Hash() + // The derived log fields can simply be set from the block and transaction + for j := 0; j < len(receipts[i].Logs); j++ { + receipts[i].Logs[j].BlockNumber = number + receipts[i].Logs[j].BlockHash = hash + receipts[i].Logs[j].TxHash = txHash + receipts[i].Logs[j].TxIndex = uint(i) + receipts[i].Logs[j].Index = logIndex + logIndex++ + } + } + return nil +} + +// ReadLogs retrieves the logs for all transactions in a block. The log fields +// are populated with metadata. In case the receipts or the block body +// are not found, a nil is returned. +func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.ChainConfig) [][]*types.Log { + // Retrieve the flattened receipt slice + data := ReadReceiptsRLP(db, hash, number) + if len(data) == 0 { + return nil + } + receipts := []*receiptLogs{} + if err := rlp.DecodeBytes(data, &receipts); err != nil { + // Receipts might be in the legacy format, try decoding that. + // TODO: to be removed after users migrated + if logs := readLegacyLogs(db, hash, number, config); logs != nil { + return logs + } + log.Error("Invalid receipt array RLP", "hash", hash, "err", err) + return nil + } + + body := ReadBody(db, hash, number) + if body == nil { + log.Error("Missing body but have receipt", "hash", hash, "number", number) + return nil + } + if err := deriveLogFields(receipts, hash, number, body.Transactions); err != nil { + log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err) + return nil + } + logs := make([][]*types.Log, len(receipts)) + for i, receipt := range receipts { + logs[i] = receipt.Logs + } + return logs +} + +// readLegacyLogs is a temporary workaround for when trying to read logs +// from a block which has its receipt stored in the legacy format. It'll +// be removed after users have migrated their freezer databases. +func readLegacyLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.ChainConfig) [][]*types.Log { + receipts := ReadReceipts(db, hash, number, config) + if receipts == nil { + return nil + } + logs := make([][]*types.Log, len(receipts)) + for i, receipt := range receipts { + logs[i] = receipt.Logs + } + return logs +} + // 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) { diff --git a/core/rlp_test.go b/core/rlp_test.go index 04daf2fc..a1655e5e 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -38,12 +38,10 @@ func getBlock(transactions int, uncles int, dataSize int) *types.Block { 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{ + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + gspec = &Genesis{ Config: params.TestChainConfig, - Alloc: GenesisAlloc{address: {Balance: funds}}, + Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, } genesis = gspec.MustCommit(db) ) @@ -54,8 +52,12 @@ func getBlock(transactions int, uncles int, dataSize int) *types.Block { if n == uncles { // Add transactions and stuff on the last block for i := 0; i < transactions; i++ { + gasPrice := big.NewInt(0) + if b.header.BaseFee != nil { + gasPrice = b.header.BaseFee + } tx, _ := types.SignTx(types.NewTransaction(uint64(i), aa, - big.NewInt(0), 50000, big.NewInt(1), make([]byte, dataSize)), types.HomesteadSigner{}, key) + big.NewInt(0), 50000, gasPrice, make([]byte, dataSize)), types.HomesteadSigner{}, key) b.AddTx(tx) } for i := 0; i < uncles; i++ { diff --git a/core/state_processor_test.go b/core/state_processor_test.go index e9623b62..fe6c6bd9 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" @@ -43,6 +44,7 @@ func TestStateProcessorErrors(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{ Config: params.TestChainConfig, + Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, } genesis = gspec.MustCommit(db) blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) @@ -58,49 +60,31 @@ func TestStateProcessorErrors(t *testing.T) { }{ { 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), + 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 [0x36bfa6d14f1cd35a1be8cc2322982a595fabc0e799f09c1de3bad7bd5b1f7626]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", + want: "could not apply tx 1 [0xf10328f0d8d80f42edac03c54540300eaf70bd555ec16b9ff4fcdbd703dd79b4]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", }, { txs: []*types.Transaction{ - makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), }, - want: "could not apply tx 0 [0x51cd272d41ef6011d8138e18bf4043797aca9b713c7d39a97563f9bbe6bdbe6f]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0", + want: "could not apply tx 0 [0xdc9395bdd9a34aa3e10cc781f9fcba36c47ab270519b571bb01ad9e99ae48d8d]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0", }, { txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), 21000000, nil, nil), + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + makeTx(1, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + makeTx(2, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + makeTx(3, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(875000000), 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", + want: "could not apply tx 3 [0xeab57cdda1267fc3b477ed95420885f8ad4fe895a034ace43f663aa0958cb3c3]: 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) + block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) _, err := blockchain.InsertChain(types.Blocks{block}) if err == nil { t.Fatal("block imported without errors") @@ -115,7 +99,7 @@ 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(), @@ -130,6 +114,13 @@ 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()) + if !config.IsLondon(parent.Number()) { + parentGasLimit := parent.GasLimit() * config.ElasticityMultiplier() + header.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) + } + } 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 diff --git a/core/types/block.go b/core/types/block.go index 7240577f..99cdc126 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -414,12 +414,12 @@ func (b *Block) Copy() *Block { func (b *Block) Hash() common.Hash { return b.SealHash() /* - if hash := b.hash.Load(); hash != nil { - return hash.(common.Hash) - } - v := b.header.Hash() - b.hash.Store(v) - return v + if hash := b.hash.Load(); hash != nil { + return hash.(common.Hash) + } + v := b.header.Hash() + b.hash.Store(v) + return v */ } diff --git a/core/types/extra_test.go b/core/types/extra_test.go index aff8a54c..d85f984b 100644 --- a/core/types/extra_test.go +++ b/core/types/extra_test.go @@ -49,6 +49,12 @@ func TestExtraMissingField(t *testing.T) { // go test -v github.com/ethereum/go-ethereum/core/types -run TestSimple func TestSimple(t *testing.T) { + extraData, err := GenerateExtraWithSignature(0, 1, nil, []byte{}, [][]byte{}) + if err != nil { + t.Fatal(err) + } + t.Logf("%x", extraData) + list := []string{ "0x0000000000000000000000000000000000000000000000000000000000000000f85e8083061a80f85494258af48e28e4a6846e931ddff8e1cdf8579821e5946a708455c8777630aac9d1e7702d13f7a865b27c948c09d936a1b408d6e0afaa537ba4e06c4504a0ae94ad3bf5ed640cc72f37bd21d64a65c3c756e9c88c80c080", "0x0000000000000000000000000000000000000000000000000000000000000000c68080c080c080", diff --git a/core/types/receipt.go b/core/types/receipt.go index 8f6e15ee..bdf48451 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -31,15 +31,14 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -//go:generate gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go +//go:generate go run github.com/fjl/gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go var ( receiptStatusFailedRLP = []byte{} 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") +var errShortTypedReceipt = errors.New("typed receipt too short") const ( // ReceiptStatusFailed is the status code of a transaction if execution failed. @@ -97,6 +96,27 @@ 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 { @@ -123,13 +143,29 @@ func (r *Receipt) EncodeRLP(w io.Writer) error { buf := encodeBufferPool.Get().(*bytes.Buffer) defer encodeBufferPool.Put(buf) buf.Reset() - buf.WriteByte(r.Type) - if err := rlp.Encode(buf, data); err != nil { + if err := r.encodeTyped(data, buf); err != nil { return err } return rlp.Encode(w, buf.Bytes()) } +// encodeTyped writes the canonical encoding of a typed receipt to w. +func (r *Receipt) encodeTyped(data *receiptRLP, w *bytes.Buffer) error { + w.WriteByte(r.Type) + return rlp.Encode(w, data) +} + +// MarshalBinary returns the consensus encoding of the receipt. +func (r *Receipt) MarshalBinary() ([]byte, error) { + if r.Type == LegacyTxType { + return rlp.EncodeToBytes(r) + } + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + var buf bytes.Buffer + err := r.encodeTyped(data, &buf) + return buf.Bytes(), err +} + // DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt // from an RLP stream. func (r *Receipt) DecodeRLP(s *rlp.Stream) error { @@ -145,26 +181,49 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { } r.Type = LegacyTxType return r.setFromRLP(dec) - case kind == rlp.String: + default: // It's an EIP-2718 typed tx receipt. b, err := s.Bytes() if err != nil { return err } - if len(b) == 0 { - return errEmptyTypedReceipt + return r.decodeTyped(b) + } +} + +// UnmarshalBinary decodes the consensus encoding of receipts. +// It supports legacy RLP receipts and EIP-2718 typed receipts. +func (r *Receipt) UnmarshalBinary(b []byte) error { + if len(b) > 0 && b[0] > 0x7f { + // It's a legacy receipt decode the RLP + var data receiptRLP + err := rlp.DecodeBytes(b, &data) + if err != nil { + return err } - r.Type = b[0] - if r.Type == AccessListTxType || r.Type == DynamicFeeTxType { - var dec receiptRLP - if err := rlp.DecodeBytes(b[1:], &dec); err != nil { - return err - } - return r.setFromRLP(dec) + r.Type = LegacyTxType + return r.setFromRLP(data) + } + // It's an EIP2718 typed transaction envelope. + return r.decodeTyped(b) +} + +// decodeTyped decodes a typed receipt from the canonical format. +func (r *Receipt) decodeTyped(b []byte) error { + if len(b) <= 1 { + return errShortTypedReceipt + } + switch b[0] { + case DynamicFeeTxType, AccessListTxType: + var data receiptRLP + err := rlp.DecodeBytes(b[1:], &data) + if err != nil { + return err } - return ErrTxTypeNotSupported + r.Type = b[0] + return r.setFromRLP(data) default: - return rlp.ErrExpectedList + return ErrTxTypeNotSupported } } @@ -208,40 +267,51 @@ func (r *Receipt) Size() common.StorageSize { return size } -func (r *Receipt) LogString() string { - str := "" - for _, v := range r.Logs { - topicstr := "" - for i, v := range v.Topics { - topicstr += fmt.Sprintf("topic index: %d, hash: %s", i, v.Hex()) +// ReceiptForStorage is a wrapper around a Receipt with RLP serialization +// that omits the Bloom field and deserialization that re-computes it. +type ReceiptForStorage Receipt + +// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt +// into an RLP stream. +func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + outerList := w.List() + w.WriteBytes((*Receipt)(r).statusEncoding()) + w.WriteUint64(r.CumulativeGasUsed) + logList := w.List() + for _, log := range r.Logs { + if err := rlp.Encode(w, log); err != nil { + return err } - str += fmt.Sprintf("blockHash:%s, blockNumber: %d, address:%s, data:%s, topic: %s, logIndex: %d, txHash: %s, txIndex: %d, removed: %v", - v.BlockHash.Hex(), v.BlockNumber, v.Address.Hex(), hexutil.Encode(v.Data), topicstr, v.Index, v.TxHash.Hex(), v.TxIndex, v.Removed) } - return str + w.ListEnd(logList) + w.ListEnd(outerList) + return w.Flush() } -// ReceiptForStorage is a wrapper around a Receipt that flattens and parses the -// entire content of a receipt, as opposed to only the consensus fields originally. -type ReceiptForStorage Receipt - -// EncodeRLP implements rlp.Encoder. -func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { - enc := &storedReceiptRLP{ - PostStateOrStatus: (*Receipt)(r).statusEncoding(), - CumulativeGasUsed: r.CumulativeGasUsed, - Logs: make([]*LogForStorage, len(r.Logs)), +// DecodeRLP implements rlp.Decoder, and loads both consensus and implementation +// fields of a receipt from an RLP stream. +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 } - for i, log := range r.Logs { - enc.Logs[i] = (*LogForStorage)(log) + if err := decodeV3StoredReceiptRLP(r, blob); err == nil { + return nil } - return rlp.Encode(w, enc) + return decodeV4StoredReceiptRLP(r, blob) } -// DecodeRLP implements rlp.Decoder. -func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { +func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { var stored storedReceiptRLP - if err := s.Decode(&stored); err != nil { + if err := rlp.DecodeBytes(blob, &stored); err != nil { return err } if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { @@ -253,6 +323,48 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { 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 { + 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 } @@ -284,42 +396,42 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { // DeriveFields fills the receipts with their computed fields based on consensus // data and contextual infos like containing block and transactions. -func (r Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, txs Transactions) error { +func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, txs Transactions) error { signer := MakeSigner(config, new(big.Int).SetUint64(number)) logIndex := uint(0) - if len(txs) != len(r) { + if len(txs) != len(rs) { return errors.New("transaction and receipt count mismatch") } - for i := 0; i < len(r); i++ { + for i := 0; i < len(rs); i++ { // The transaction type and hash can be retrieved from the transaction itself - r[i].Type = txs[i].Type() - r[i].TxHash = txs[i].Hash() + rs[i].Type = txs[i].Type() + rs[i].TxHash = txs[i].Hash() // block location fields - r[i].BlockHash = hash - r[i].BlockNumber = new(big.Int).SetUint64(number) - r[i].TransactionIndex = uint(i) + rs[i].BlockHash = hash + rs[i].BlockNumber = new(big.Int).SetUint64(number) + rs[i].TransactionIndex = uint(i) // The contract address can be derived from the transaction itself if txs[i].To() == nil { // Deriving the signer is expensive, only do if it's actually needed from, _ := Sender(signer, txs[i]) - r[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) + rs[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) } // The used gas can be calculated based on previous r if i == 0 { - r[i].GasUsed = r[i].CumulativeGasUsed + rs[i].GasUsed = rs[i].CumulativeGasUsed } else { - r[i].GasUsed = r[i].CumulativeGasUsed - r[i-1].CumulativeGasUsed + rs[i].GasUsed = rs[i].CumulativeGasUsed - rs[i-1].CumulativeGasUsed } // The derived log fields can simply be set from the block and transaction - for j := 0; j < len(r[i].Logs); j++ { - r[i].Logs[j].BlockNumber = number - r[i].Logs[j].BlockHash = hash - r[i].Logs[j].TxHash = r[i].TxHash - r[i].Logs[j].TxIndex = uint(i) - r[i].Logs[j].Index = logIndex + for j := 0; j < len(rs[i].Logs); j++ { + rs[i].Logs[j].BlockNumber = number + rs[i].Logs[j].BlockHash = hash + rs[i].Logs[j].TxHash = rs[i].TxHash + rs[i].Logs[j].TxIndex = uint(i) + rs[i].Logs[j].Index = logIndex logIndex++ } } diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 87fc16a5..6f0953bd 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -20,6 +20,7 @@ import ( "bytes" "math" "math/big" + "reflect" "testing" "github.com/ethereum/go-ethereum/common" @@ -28,15 +29,198 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +var ( + legacyReceipt = &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}, + }, + }, + } + accessListReceipt = &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}, + }, + }, + Type: AccessListTxType, + } + eip1559Receipt = &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}, + }, + }, + Type: DynamicFeeTxType, + } +) + func TestDecodeEmptyTypedReceipt(t *testing.T) { input := []byte{0x80} var r Receipt err := rlp.DecodeBytes(input, &r) - if err != errEmptyTypedReceipt { + if err != errShortTypedReceipt { t.Fatal("wrong error:", err) } } +func TestLegacyReceiptDecoding(t *testing.T) { + tests := []struct { + name string + encode func(*Receipt) ([]byte, error) + }{ + { + "ReceiptForStorage", + encodeAsReceiptForStorage, + }, + { + "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 encodeAsReceiptForStorage(want *Receipt) ([]byte, error) { + return rlp.EncodeToBytes((*ReceiptForStorage)(want)) +} + +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 @@ -150,9 +334,6 @@ func TestDeriveFields(t *testing.T) { if receipts[i].Logs[j].TxHash != txs[i].Hash() { t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String()) } - if receipts[i].Logs[j].TxHash != txs[i].Hash() { - t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String()) - } if receipts[i].Logs[j].TxIndex != uint(i) { t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i) } @@ -192,6 +373,105 @@ func TestTypedReceiptEncodingDecoding(t *testing.T) { } } +func TestReceiptMarshalBinary(t *testing.T) { + // Legacy Receipt + legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt}) + have, err := legacyReceipt.MarshalBinary() + if err != nil { + t.Fatalf("marshal binary error: %v", err) + } + legacyReceipts := Receipts{legacyReceipt} + buf := new(bytes.Buffer) + legacyReceipts.EncodeIndex(0, buf) + haveEncodeIndex := buf.Bytes() + if !bytes.Equal(have, haveEncodeIndex) { + t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) + } + buf.Reset() + if err := legacyReceipt.EncodeRLP(buf); err != nil { + t.Fatalf("encode rlp error: %v", err) + } + haveRLPEncode := buf.Bytes() + if !bytes.Equal(have, haveRLPEncode) { + t.Errorf("BinaryMarshal and EncodeRLP mismatch for legacy tx, got %x want %x", have, haveRLPEncode) + } + legacyWant := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + if !bytes.Equal(have, legacyWant) { + t.Errorf("encoded RLP mismatch, got %x want %x", have, legacyWant) + } + + // 2930 Receipt + buf.Reset() + accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt}) + have, err = accessListReceipt.MarshalBinary() + if err != nil { + t.Fatalf("marshal binary error: %v", err) + } + accessListReceipts := Receipts{accessListReceipt} + accessListReceipts.EncodeIndex(0, buf) + haveEncodeIndex = buf.Bytes() + if !bytes.Equal(have, haveEncodeIndex) { + t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) + } + accessListWant := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + if !bytes.Equal(have, accessListWant) { + t.Errorf("encoded RLP mismatch, got %x want %x", have, accessListWant) + } + + // 1559 Receipt + buf.Reset() + eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt}) + have, err = eip1559Receipt.MarshalBinary() + if err != nil { + t.Fatalf("marshal binary error: %v", err) + } + eip1559Receipts := Receipts{eip1559Receipt} + eip1559Receipts.EncodeIndex(0, buf) + haveEncodeIndex = buf.Bytes() + if !bytes.Equal(have, haveEncodeIndex) { + t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) + } + eip1559Want := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + if !bytes.Equal(have, eip1559Want) { + t.Errorf("encoded RLP mismatch, got %x want %x", have, eip1559Want) + } +} + +func TestReceiptUnmarshalBinary(t *testing.T) { + // Legacy Receipt + legacyBinary := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + gotLegacyReceipt := new(Receipt) + if err := gotLegacyReceipt.UnmarshalBinary(legacyBinary); err != nil { + t.Fatalf("unmarshal binary error: %v", err) + } + legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt}) + if !reflect.DeepEqual(gotLegacyReceipt, legacyReceipt) { + t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotLegacyReceipt, legacyReceipt) + } + + // 2930 Receipt + accessListBinary := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + gotAccessListReceipt := new(Receipt) + if err := gotAccessListReceipt.UnmarshalBinary(accessListBinary); err != nil { + t.Fatalf("unmarshal binary error: %v", err) + } + accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt}) + if !reflect.DeepEqual(gotAccessListReceipt, accessListReceipt) { + t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotAccessListReceipt, accessListReceipt) + } + + // 1559 Receipt + eip1559RctBinary := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + got1559Receipt := new(Receipt) + if err := got1559Receipt.UnmarshalBinary(eip1559RctBinary); err != nil { + t.Fatalf("unmarshal binary error: %v", err) + } + eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt}) + if !reflect.DeepEqual(got1559Receipt, eip1559Receipt) { + t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", got1559Receipt, eip1559Receipt) + } +} + func clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) { t.Helper() diff --git a/eth/api_backend.go b/eth/api_backend.go index be1df908..38b2ad3f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -181,16 +181,8 @@ func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (type return b.eth.blockchain.GetReceiptsByHash(hash), nil } -func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { - receipts := b.eth.blockchain.GetReceiptsByHash(hash) - if receipts == nil { - return nil, nil - } - logs := make([][]*types.Log, len(receipts)) - for i, receipt := range receipts { - logs[i] = receipt.Logs - } - return logs, nil +func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { + return rawdb.ReadLogs(b.eth.chainDb, hash, number, b.ChainConfig()), nil } func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { @@ -288,7 +280,7 @@ func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) return b.gpo.SuggestTipCap(ctx) } -func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock rpc.BlockNumber, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) { +func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) { return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) } diff --git a/eth/backend.go b/eth/backend.go index ce2827c5..cd84e7d3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -40,7 +40,6 @@ import ( "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" "github.com/ethereum/go-ethereum/eth/protocols/snap" @@ -327,11 +326,6 @@ func (s *Ethereum) APIs() []rpc.API { Version: "1.0", Service: NewPrivateMinerAPI(s), Public: false, - }, { - Namespace: "eth", - Version: "1.0", - Service: filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute), - Public: true, }, { Namespace: "admin", Version: "1.0", diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index ecb1abc7..2deb30b8 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -38,12 +38,14 @@ var ( // testAddr is the Ethereum address of the tester account. testAddr = crypto.PubkeyToAddress(testKey.PublicKey) - testBalance = big.NewInt(2e10) + testBalance = big.NewInt(2e18) ) func generateTestChain() (*core.Genesis, []*types.Block) { + core.RegGenesis = nil db := rawdb.NewMemoryDatabase() config := params.AllEthashProtocolChanges + params.GenesisSupply = testBalance genesis := &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, @@ -110,11 +112,13 @@ func TestEth2AssembleBlock(t *testing.T) { 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) + tx, err := types.SignTx(types.NewTransaction(0, blocks[8].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) if err != nil { t.Fatalf("error signing transaction, err=%v", err) } - ethservice.TxPool().AddLocal(tx) + if err := ethservice.TxPool().AddLocal(tx); err != nil { + t.Fatalf("failed to add local tx, err=%v", err) + } blockParams := assembleBlockParams{ ParentHash: blocks[8].ParentHash(), Timestamp: blocks[8].Time(), diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 79416099..fb27caa9 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -474,7 +474,11 @@ func (dlp *downloadTesterPeer) RequestNodeData(hashes []common.Hash) error { results := make([][]byte, 0, len(hashes)) for _, hash := range hashes { - if data, err := dlp.dl.peerDb.Get(hash.Bytes()); err == nil { + data, err := dlp.dl.peerDb.Get(hash.Bytes()) + if err != nil { + data, err = dlp.dl.peerDb.Get(append([]byte("c"), hash.Bytes()...)) + } + if err == nil { if !dlp.missingStates[hash] { results = append(results, data) } diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go index f43ad67a..487cebe3 100644 --- a/eth/downloader/queue_test.go +++ b/eth/downloader/queue_test.go @@ -35,7 +35,7 @@ import ( var ( testdb = rawdb.NewMemoryDatabase() - genesis = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) + genesis = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000000000000)) ) // makeChain creates a chain of n blocks starting at and including parent. @@ -48,7 +48,7 @@ func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Bloc // Add one tx to every secondblock if !empty && i%2 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) if err != nil { panic(err) } @@ -74,6 +74,7 @@ func init() { blocks, _ = makeChain(targetBlocks, 0, genesis, true) emptyChain = &chainData{blocks, 0} + core.CheckAllocWithTotalSupply = false } func (chain *chainData) headers() []*types.Header { diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index 2d7b4d1f..2468d7da 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -35,7 +35,7 @@ var ( testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testAddress = crypto.PubkeyToAddress(testKey.PublicKey) testDB = rawdb.NewMemoryDatabase() - testGenesis = core.GenesisBlockForTesting(testDB, testAddress, big.NewInt(1000000000)) + testGenesis = core.GenesisBlockForTesting(testDB, testAddress, big.NewInt(1000000000000000000)) ) // The common prefix of all test chains: @@ -127,7 +127,7 @@ func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) // Include transactions to the miner to make blocks more interesting. if parent == tc.genesis && i%22 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) if err != nil { panic(err) } diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index a6eef71d..80f7bd68 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -38,8 +38,8 @@ var ( testdb = rawdb.NewMemoryDatabase() 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, trie.NewStackTrie(nil)) + genesis = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000000000000)) + unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil)) ) // makeChain creates a chain of n blocks starting at and including parent. @@ -53,7 +53,7 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common // If the block number is multiple of 3, send a bonus transaction to the miner if parent == genesis && i%3 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) if err != nil { panic(err) } diff --git a/eth/filters/api.go b/eth/filters/api.go index e0b07e31..0b4e9a91 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -29,8 +29,7 @@ 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/ethdb" - "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/rpc" ) @@ -38,43 +37,40 @@ import ( // and associated subscription in the event system. type filter struct { typ Type - deadline *time.Timer // filter is inactiv when deadline triggers + deadline *time.Timer // filter is inactive when deadline triggers hashes []common.Hash + txs []*types.Transaction crit FilterCriteria logs []*types.Log s *Subscription // associated subscription in event system } -// PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various +// FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various // information related to the Ethereum protocol such als blocks, transactions and logs. -type PublicFilterAPI struct { - backend Backend - mux *event.TypeMux - quit chan struct{} - chainDb ethdb.Database +type FilterAPI struct { + sys *FilterSystem 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, timeout time.Duration) *PublicFilterAPI { - api := &PublicFilterAPI{ - backend: backend, - chainDb: backend.ChainDb(), - events: NewEventSystem(backend, lightMode), +// NewFilterAPI returns a new FilterAPI instance. +func NewFilterAPI(system *FilterSystem, lightMode bool) *FilterAPI { + api := &FilterAPI{ + sys: system, + events: NewEventSystem(system, lightMode), filters: make(map[rpc.ID]*filter), - timeout: timeout, + timeout: system.cfg.Timeout, } - go api.timeoutLoop(timeout) + go api.timeoutLoop(system.cfg.Timeout) return api } // 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) { +func (api *FilterAPI) timeoutLoop(timeout time.Duration) { var toUninstall []*Subscription ticker := time.NewTicker(timeout) defer ticker.Stop() @@ -102,30 +98,28 @@ func (api *PublicFilterAPI) timeoutLoop(timeout time.Duration) { } } -// NewPendingTransactionFilter creates a filter that fetches pending transaction hashes +// NewPendingTransactionFilter creates a filter that fetches pending transactions // as transactions enter the pending state. // // 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://eth.wiki/json-rpc/API#eth_newpendingtransactionfilter -func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { +func (api *FilterAPI) NewPendingTransactionFilter() rpc.ID { var ( - pendingTxs = make(chan []common.Hash) + pendingTxs = make(chan []*types.Transaction) pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) ) api.filtersMu.Lock() - api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: pendingTxSub} + api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(api.timeout), txs: make([]*types.Transaction, 0), s: pendingTxSub} api.filtersMu.Unlock() go func() { for { select { - case ph := <-pendingTxs: + case pTx := <-pendingTxs: api.filtersMu.Lock() if f, found := api.filters[pendingTxSub.ID]; found { - f.hashes = append(f.hashes, ph...) + f.txs = append(f.txs, pTx...) } api.filtersMu.Unlock() case <-pendingTxSub.Err(): @@ -140,9 +134,10 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { return pendingTxSub.ID } -// NewPendingTransactions creates a subscription that is triggered each time a transaction -// enters the transaction pool and was signed from one of the transactions this nodes manages. -func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { +// NewPendingTransactions creates a subscription that is triggered each time a +// transaction enters the transaction pool. If fullTx is true the full tx is +// sent to the client, otherwise the hash is sent. +func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) { notifier, supported := rpc.NotifierFromContext(ctx) if !supported { return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported @@ -151,16 +146,23 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su rpcSub := notifier.CreateSubscription() go func() { - txHashes := make(chan []common.Hash, 128) - pendingTxSub := api.events.SubscribePendingTxs(txHashes) + txs := make(chan []*types.Transaction, 128) + pendingTxSub := api.events.SubscribePendingTxs(txs) + chainConfig := api.sys.backend.ChainConfig() for { select { - case hashes := <-txHashes: + case txs := <-txs: // To keep the original behaviour, send a single tx hash in one notification. // TODO(rjl493456442) Send a batch of tx hashes in one notification - for _, h := range hashes { - notifier.Notify(rpcSub.ID, h) + latest := api.sys.backend.CurrentHeader() + for _, tx := range txs { + if fullTx != nil && *fullTx { + rpcTx := ethapi.NewRPCPendingTransaction(tx, latest, chainConfig) + notifier.Notify(rpcSub.ID, rpcTx) + } else { + notifier.Notify(rpcSub.ID, tx.Hash()) + } } case <-rpcSub.Err(): pendingTxSub.Unsubscribe() @@ -177,9 +179,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://eth.wiki/json-rpc/API#eth_newblockfilter -func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { +func (api *FilterAPI) NewBlockFilter() rpc.ID { var ( headers = make(chan *types.Header) headerSub = api.events.SubscribeNewHeads(headers) @@ -211,7 +211,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { } // NewHeads send a notification each time a new (header) block is appended to the chain. -func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { +func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { notifier, supported := rpc.NotifierFromContext(ctx) if !supported { return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported @@ -241,7 +241,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er } // Logs creates a subscription that fires for all new log that match the given filter criteria. -func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) { +func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) { notifier, supported := rpc.NotifierFromContext(ctx) if !supported { return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported @@ -258,11 +258,11 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc } go func() { - for { select { case logs := <-matchedLogs: for _, log := range logs { + log := log notifier.Notify(rpcSub.ID, &log) } case <-rpcSub.Err(): // client send an unsubscribe request @@ -293,9 +293,7 @@ type FilterCriteria ethereum.FilterQuery // again but with the removed property set to true. // // In case "fromBlock" > "toBlock" an error is returned. -// -// https://eth.wiki/json-rpc/API#eth_newfilter -func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { +func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { logs := make(chan []*types.Log) logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) if err != nil { @@ -328,13 +326,11 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { } // GetLogs returns logs matching the given argument that are stored within the state. -// -// https://eth.wiki/json-rpc/API#eth_getlogs -func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { +func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { var filter *Filter if crit.BlockHash != nil { // Block filter requested, construct a single-shot filter - filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) + filter = api.sys.NewBlockFilter(*crit.BlockHash, crit.Addresses, crit.Topics) } else { // Convert the RPC block numbers into internal representations begin := rpc.LatestBlockNumber.Int64() @@ -346,7 +342,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([ end = crit.ToBlock.Int64() } // Construct the range filter - filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) + filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics) } // Run the filter and return all the logs logs, err := filter.Logs(ctx) @@ -357,9 +353,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([ } // UninstallFilter removes the filter with the given filter id. -// -// https://eth.wiki/json-rpc/API#eth_uninstallfilter -func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { +func (api *FilterAPI) UninstallFilter(id rpc.ID) bool { api.filtersMu.Lock() f, found := api.filters[id] if found { @@ -375,9 +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://eth.wiki/json-rpc/API#eth_getfilterlogs -func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { +func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { api.filtersMu.Lock() f, found := api.filters[id] api.filtersMu.Unlock() @@ -389,7 +381,7 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty var filter *Filter if f.crit.BlockHash != nil { // Block filter requested, construct a single-shot filter - filter = NewBlockFilter(api.backend, *f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) + filter = api.sys.NewBlockFilter(*f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) } else { // Convert the RPC block numbers into internal representations begin := rpc.LatestBlockNumber.Int64() @@ -401,7 +393,7 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty end = f.crit.ToBlock.Int64() } // Construct the range filter - filter = NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics) + filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics) } // Run the filter and return all the logs logs, err := filter.Logs(ctx) @@ -416,9 +408,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://eth.wiki/json-rpc/API#eth_getfilterchanges -func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { +func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { api.filtersMu.Lock() defer api.filtersMu.Unlock() @@ -431,10 +421,14 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { f.deadline.Reset(api.timeout) switch f.typ { - case PendingTransactionsSubscription, BlocksSubscription: + case BlocksSubscription: hashes := f.hashes f.hashes = nil return returnHashes(hashes), nil + case PendingTransactionsSubscription: + txs := f.txs + f.txs = nil + return txs, nil case LogsSubscription, MinedAndPendingLogsSubscription: logs := f.logs f.logs = nil diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 02229a75..0a80d0f8 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -56,7 +56,7 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) { // from, to block number var test1 FilterCriteria - vector := fmt.Sprintf(`{"fromBlock":"0x%x","toBlock":"0x%x"}`, fromBlock, toBlock) + vector := fmt.Sprintf(`{"fromBlock":"%#x","toBlock":"%#x"}`, fromBlock, toBlock) if err := json.Unmarshal([]byte(vector), &test1); err != nil { t.Fatal(err) } diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go index 020db070..73b96b77 100644 --- a/eth/filters/bench_test.go +++ b/eth/filters/bench_test.go @@ -62,6 +62,7 @@ func BenchmarkBloomBits32k(b *testing.B) { const benchFilterCnt = 2000 func benchmarkBloomBits(b *testing.B, sectionSize uint64) { + b.Skip("test disabled: this tests presume (and modify) an existing datadir.") benchDataDir := node.DefaultDataDir() + "/geth/chaindata" b.Log("Running bloombits benchmark section size:", sectionSize) @@ -92,9 +93,9 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { var header *types.Header for i := sectionIdx * sectionSize; i < (sectionIdx+1)*sectionSize; i++ { hash := rawdb.ReadCanonicalHash(db, i) - header = rawdb.ReadHeader(db, hash, i) - if header == nil { + if header = rawdb.ReadHeader(db, hash, i); header == nil { b.Fatalf("Error creating bloomBits data") + return } bc.AddBloom(uint(i-sectionIdx*sectionSize), header.Bloom) } @@ -121,31 +122,36 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { b.Log("Running filter benchmarks...") start = time.Now() - var backend *testBackend + var ( + backend *testBackend + sys *FilterSystem + ) for i := 0; i < benchFilterCnt; i++ { if i%20 == 0 { db.Close() db, _ = rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) backend = &testBackend{db: db, sections: cnt} + sys = NewFilterSystem(backend, Config{}) } var addr common.Address addr[0] = byte(i) addr[1] = byte(i / 256) - filter := NewRangeFilter(backend, 0, int64(cnt*sectionSize-1), []common.Address{addr}, nil) + filter := sys.NewRangeFilter(0, int64(cnt*sectionSize-1), []common.Address{addr}, nil) if _, err := filter.Logs(context.Background()); err != nil { - b.Error("filter.Find error:", err) + b.Error("filter.Logs error:", err) } } + d = time.Since(start) b.Log("Finished running filter benchmarks") b.Log(" ", d, "total ", d/time.Duration(benchFilterCnt), "per address", d*time.Duration(1000000)/time.Duration(benchFilterCnt*cnt*sectionSize), "per million blocks") db.Close() } -var bloomBitsPrefix = []byte("bloomBits-") - +//nolint:unused func clearBloomBits(db ethdb.Database) { + var bloomBitsPrefix = []byte("bloomBits-") fmt.Println("Clearing bloombits data...") it := db.NewIterator(bloomBitsPrefix, nil) for it.Next() { @@ -155,6 +161,7 @@ func clearBloomBits(db ethdb.Database) { } func BenchmarkNoBloomBits(b *testing.B) { + b.Skip("test disabled: this tests presume (and modify) an existing datadir.") benchDataDir := node.DefaultDataDir() + "/geth/chaindata" b.Log("Running benchmark without bloombits") db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) @@ -169,10 +176,11 @@ func BenchmarkNoBloomBits(b *testing.B) { clearBloomBits(db) + _, sys := newTestFilterSystem(b, db, Config{}) + b.Log("Running filter benchmarks...") start := time.Now() - backend := &testBackend{db: db} - filter := NewRangeFilter(backend, 0, int64(*headNum), []common.Address{{}}, nil) + filter := sys.NewRangeFilter(0, int64(*headNum), []common.Address{{}}, nil) filter.Logs(context.Background()) d := time.Since(start) b.Log("Finished running filter benchmarks") diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 17635837..58617b57 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -22,48 +22,27 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rpc" ) -type Backend interface { - ChainDb() ethdb.Database - HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) - HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) - GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) - GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) - - SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription - SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription - SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription - SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription - SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription - - BloomStatus() (uint64, uint64) - ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) -} - // Filter can be used to retrieve and filter logs. type Filter struct { - backend Backend + sys *FilterSystem - db ethdb.Database addresses []common.Address topics [][]common.Hash - block common.Hash // Block hash if filtering a single block - begin, end int64 // Range interval if filtering multiple blocks + block *common.Hash // Block hash if filtering a single block + begin, end int64 // Range interval if filtering multiple blocks matcher *bloombits.Matcher } // NewRangeFilter creates a new filter which uses a bloom filter on blocks to // figure out whether a particular block is interesting or not. -func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { +func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { // Flatten the address and topic filter clauses into a single bloombits filter // system. Since the bloombits are not positional, nil topics are permitted, // which get flattened into a nil byte slice. @@ -82,10 +61,10 @@ func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Addres } filters = append(filters, filter) } - size, _ := backend.BloomStatus() + size, _ := sys.backend.BloomStatus() // Create a generic filter and convert it into a range filter - filter := newFilter(backend, addresses, topics) + filter := newFilter(sys, addresses, topics) filter.matcher = bloombits.NewMatcher(size, filters) filter.begin = begin @@ -96,21 +75,20 @@ func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Addres // NewBlockFilter creates a new filter which directly inspects the contents of // a block to figure out whether it is interesting or not. -func NewBlockFilter(backend Backend, block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { +func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { // Create a generic filter and convert it into a block filter - filter := newFilter(backend, addresses, topics) - filter.block = block + filter := newFilter(sys, addresses, topics) + filter.block = &block return filter } // newFilter creates a generic filter that can either filter based on a block hash, // or based on range queries. The search criteria needs to be explicitly set. -func newFilter(backend Backend, addresses []common.Address, topics [][]common.Hash) *Filter { +func newFilter(sys *FilterSystem, addresses []common.Address, topics [][]common.Hash) *Filter { return &Filter{ - backend: backend, + sys: sys, addresses: addresses, topics: topics, - db: backend.ChainDb(), } } @@ -118,36 +96,59 @@ func newFilter(backend Backend, addresses []common.Address, topics [][]common.Ha // first block that contains matches, updating the start of the filter accordingly. func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { // If we're doing singleton block filtering, execute and return - if f.block != (common.Hash{}) { - header, err := f.backend.HeaderByHash(ctx, f.block) + if f.block != nil { + header, err := f.sys.backend.HeaderByHash(ctx, *f.block) if err != nil { return nil, err } if header == nil { return nil, errors.New("unknown block") } - return f.blockLogs(ctx, header) + return f.blockLogs(ctx, header, false) + } + // Short-cut if all we care about is pending logs + if f.begin == rpc.PendingBlockNumber.Int64() { + if f.end != rpc.PendingBlockNumber.Int64() { + return nil, errors.New("invalid block range") + } + return f.pendingLogs() } // Figure out the limits of the filter range - header, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + header, _ := f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) if header == nil { return nil, nil } - head := header.Number.Uint64() - - if f.begin == -1 { - f.begin = int64(head) + var ( + err error + head = header.Number.Int64() + pending = f.end == rpc.PendingBlockNumber.Int64() + ) + resolveSpecial := func(number int64) (int64, error) { + var hdr *types.Header + switch number { + case rpc.LatestBlockNumber.Int64(): + return head, nil + case rpc.PendingBlockNumber.Int64(): + // we should return head here since we've already captured + // that we need to get the pending logs in the pending boolean above + return head, nil + default: + return number, nil + } + return hdr.Number.Int64(), nil } - end := uint64(f.end) - if f.end == -1 { - end = head + if f.begin, err = resolveSpecial(f.begin); err != nil { + return nil, err + } + if f.end, err = resolveSpecial(f.end); err != nil { + return nil, err } // Gather all indexed logs, and finish with non indexed ones var ( - logs []*types.Log - err error + logs []*types.Log + end = uint64(f.end) + size, sections = f.sys.backend.BloomStatus() ) - size, sections := f.backend.BloomStatus() if indexed := sections * size; indexed > uint64(f.begin) { if indexed > end { logs, err = f.indexedLogs(ctx, end) @@ -160,6 +161,13 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { } rest, err := f.unindexedLogs(ctx, end) logs = append(logs, rest...) + if pending { + pendingLogs, err := f.pendingLogs() + if err != nil { + return nil, err + } + logs = append(logs, pendingLogs...) + } return logs, err } @@ -175,7 +183,7 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err } defer session.Close() - f.backend.ServiceFilter(ctx, session) + f.sys.backend.ServiceFilter(ctx, session) // Iterate over the matches until exhausted or context closed var logs []*types.Log @@ -194,11 +202,11 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err f.begin = int64(number) + 1 // Retrieve the suggested block and pull any truly matching logs - header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) + header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) if header == nil || err != nil { return logs, err } - found, err := f.checkMatches(ctx, header) + found, err := f.blockLogs(ctx, header, true) if err != nil { return logs, err } @@ -216,11 +224,11 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, e var logs []*types.Log for ; f.begin <= int64(end); f.begin++ { - header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) + header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) if header == nil || err != nil { return logs, err } - found, err := f.blockLogs(ctx, header) + found, err := f.blockLogs(ctx, header, false) if err != nil { return logs, err } @@ -230,34 +238,34 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, e } // blockLogs returns the logs matching the filter criteria within a single block. -func (f *Filter) blockLogs(ctx context.Context, header *types.Header) (logs []*types.Log, err error) { - if bloomFilter(header.Bloom, f.addresses, f.topics) { - found, err := f.checkMatches(ctx, header) +func (f *Filter) blockLogs(ctx context.Context, header *types.Header, skipBloom bool) ([]*types.Log, error) { + // Fast track: no filtering criteria + if len(f.addresses) == 0 && len(f.topics) == 0 { + list, err := f.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64()) if err != nil { - return logs, err + return nil, err } - logs = append(logs, found...) + return flatten(list), nil + } else if skipBloom || bloomFilter(header.Bloom, f.addresses, f.topics) { + return f.checkMatches(ctx, header) } - return logs, nil + return nil, nil } // checkMatches checks if the receipts belonging to the given header contain any log events that // match the filter criteria. This function is called when the bloom filter signals a potential match. -func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs []*types.Log, err error) { - // Get the logs of the block - logsList, err := f.backend.GetLogs(ctx, header.Hash()) +func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) { + logsList, err := f.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64()) if err != nil { return nil, err } - var unfiltered []*types.Log - for _, logs := range logsList { - unfiltered = append(unfiltered, logs...) - } - logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics) + + unfiltered := flatten(logsList) + logs := filterLogs(unfiltered, nil, nil, f.addresses, f.topics) if len(logs) > 0 { // We have matching logs, check if we need to resolve full logs via the light client if logs[0].TxHash == (common.Hash{}) { - receipts, err := f.backend.GetReceipts(ctx, header.Hash()) + receipts, err := f.sys.backend.GetReceipts(ctx, header.Hash()) if err != nil { return nil, err } @@ -272,6 +280,19 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs [ return nil, nil } +// pendingLogs returns the logs matching the filter criteria within the pending block. +func (f *Filter) pendingLogs() ([]*types.Log, error) { + block, receipts := f.sys.backend.PendingBlockAndReceipts() + if bloomFilter(block.Bloom(), f.addresses, f.topics) { + var unfiltered []*types.Log + for _, r := range receipts { + unfiltered = append(unfiltered, r.Logs...) + } + return filterLogs(unfiltered, nil, nil, f.addresses, f.topics), nil + } + return nil, nil +} + func includes(addresses []common.Address, a common.Address) bool { for _, addr := range addresses { if addr == a { @@ -299,7 +320,7 @@ Logs: } // If the to filtered topics is greater than the amount of topics in logs, skip. if len(topics) > len(log.Topics) { - continue Logs + continue } for i, sub := range topics { match := len(sub) == 0 // empty rule set == wildcard @@ -346,3 +367,11 @@ func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]commo } return true } + +func flatten(list [][]*types.Log) []*types.Log { + var flat []*types.Log + for _, logs := range list { + flat = append(flat, logs...) + } + return flat +} diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 12f037d0..c424dd29 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -26,14 +26,89 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" "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" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) +// Config represents the configuration of the filter system. +type Config struct { + LogCacheSize int // maximum number of cached blocks (default: 32) + Timeout time.Duration // how long filters stay active (default: 5min) +} + +func (cfg Config) withDefaults() Config { + if cfg.Timeout == 0 { + cfg.Timeout = 5 * time.Minute + } + if cfg.LogCacheSize == 0 { + cfg.LogCacheSize = 32 + } + return cfg +} + +type Backend interface { + ChainDb() ethdb.Database + HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) + HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) + GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) + GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) + PendingBlockAndReceipts() (*types.Block, types.Receipts) + + CurrentHeader() *types.Header + ChainConfig() *params.ChainConfig + SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription + SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription + SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription + SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription + SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription + + BloomStatus() (uint64, uint64) + ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) +} + +// FilterSystem holds resources shared by all filters. +type FilterSystem struct { + backend Backend + logsCache *lru.Cache[common.Hash, [][]*types.Log] + cfg *Config +} + +// NewFilterSystem creates a filter system. +func NewFilterSystem(backend Backend, config Config) *FilterSystem { + config = config.withDefaults() + return &FilterSystem{ + backend: backend, + logsCache: lru.NewCache[common.Hash, [][]*types.Log](config.LogCacheSize), + cfg: &config, + } +} + +// cachedGetLogs loads block logs from the backend and caches the result. +func (sys *FilterSystem) cachedGetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { + cached, ok := sys.logsCache.Get(blockHash) + if ok { + return cached, nil + } + + logs, err := sys.backend.GetLogs(ctx, blockHash, number) + if err != nil { + return nil, err + } + if logs == nil { + return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString()) + } + sys.logsCache.Add(blockHash, logs) + return logs, nil +} + // Type determines the kind of filter and is used to put the filter in to // the correct bucket when added. type Type byte @@ -47,12 +122,12 @@ const ( PendingLogsSubscription // MinedAndPendingLogsSubscription queries for logs in mined and pending blocks. MinedAndPendingLogsSubscription - // PendingTransactionsSubscription queries tx hashes for pending - // transactions entering the pending state + // PendingTransactionsSubscription queries for pending transactions entering + // the pending state PendingTransactionsSubscription // BlocksSubscription queries hashes for blocks that are imported BlocksSubscription - // LastSubscription keeps track of the last index + // LastIndexSubscription keeps track of the last index LastIndexSubscription ) @@ -74,7 +149,7 @@ type subscription struct { created time.Time logsCrit ethereum.FilterQuery logs chan []*types.Log - hashes chan []common.Hash + txs chan []*types.Transaction headers chan *types.Header installed chan struct{} // closed when the filter is installed err chan error // closed when the filter is uninstalled @@ -84,6 +159,7 @@ type subscription struct { // subscription which match the subscription criteria. type EventSystem struct { backend Backend + sys *FilterSystem lightMode bool lastHead *types.Header @@ -110,9 +186,10 @@ type EventSystem struct { // // The returned manager has a loop that needs to be stopped with the Stop function // or by stopping the given mux. -func NewEventSystem(backend Backend, lightMode bool) *EventSystem { +func NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem { m := &EventSystem{ - backend: backend, + sys: sys, + backend: sys.backend, lightMode: lightMode, install: make(chan *subscription), uninstall: make(chan *subscription), @@ -165,7 +242,7 @@ func (sub *Subscription) Unsubscribe() { case sub.es.uninstall <- sub.f: break uninstallLoop case <-sub.f.logs: - case <-sub.f.hashes: + case <-sub.f.txs: case <-sub.f.headers: } } @@ -232,7 +309,7 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit ethereum.FilterQuery, logs logsCrit: crit, created: time.Now(), logs: logs, - hashes: make(chan []common.Hash), + txs: make(chan []*types.Transaction), headers: make(chan *types.Header), installed: make(chan struct{}), err: make(chan error), @@ -249,7 +326,7 @@ func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*typ logsCrit: crit, created: time.Now(), logs: logs, - hashes: make(chan []common.Hash), + txs: make(chan []*types.Transaction), headers: make(chan *types.Header), installed: make(chan struct{}), err: make(chan error), @@ -257,7 +334,7 @@ func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*typ return es.subscribe(sub) } -// subscribePendingLogs creates a subscription that writes transaction hashes for +// subscribePendingLogs creates a subscription that writes contract event logs for // transactions that enter the transaction pool. func (es *EventSystem) subscribePendingLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription { sub := &subscription{ @@ -266,7 +343,7 @@ func (es *EventSystem) subscribePendingLogs(crit ethereum.FilterQuery, logs chan logsCrit: crit, created: time.Now(), logs: logs, - hashes: make(chan []common.Hash), + txs: make(chan []*types.Transaction), headers: make(chan *types.Header), installed: make(chan struct{}), err: make(chan error), @@ -282,7 +359,7 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti typ: BlocksSubscription, created: time.Now(), logs: make(chan []*types.Log), - hashes: make(chan []common.Hash), + txs: make(chan []*types.Transaction), headers: headers, installed: make(chan struct{}), err: make(chan error), @@ -290,15 +367,15 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti return es.subscribe(sub) } -// SubscribePendingTxs creates a subscription that writes transaction hashes for +// SubscribePendingTxs creates a subscription that writes transactions for // transactions that enter the transaction pool. -func (es *EventSystem) SubscribePendingTxs(hashes chan []common.Hash) *Subscription { +func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subscription { sub := &subscription{ id: rpc.NewID(), typ: PendingTransactionsSubscription, created: time.Now(), logs: make(chan []*types.Log), - hashes: hashes, + txs: txs, headers: make(chan *types.Header), installed: make(chan struct{}), err: make(chan error), @@ -342,12 +419,8 @@ func (es *EventSystem) handleRemovedLogs(filters filterIndex, ev core.RemovedLog } func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { - hashes := make([]common.Hash, 0, len(ev.Txs)) - for _, tx := range ev.Txs { - hashes = append(hashes, tx.Hash()) - } for _, f := range filters[PendingTransactionsSubscription] { - f.hashes <- hashes + f.txs <- ev.Txs } } @@ -405,7 +478,7 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common. // Get the logs of the block ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - logsList, err := es.backend.GetLogs(ctx, header.Hash()) + logsList, err := es.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64()) if err != nil { return nil } diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 52150366..52a0f9b4 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -39,12 +39,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -var ( - deadline = 5 * time.Minute -) - type testBackend struct { - mux *event.TypeMux db ethdb.Database sections uint64 txFeed event.Feed @@ -54,6 +49,14 @@ type testBackend struct { chainFeed event.Feed } +func (b *testBackend) ChainConfig() *params.ChainConfig { + panic("implement me") +} + +func (b *testBackend) CurrentHeader() *types.Header { + panic("implement me") +} + func (b *testBackend) ChainDb() ethdb.Database { return b.db } @@ -63,14 +66,15 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe hash common.Hash num uint64 ) - if blockNr == rpc.LatestBlockNumber { + switch blockNr { + case rpc.LatestBlockNumber: hash = rawdb.ReadHeadBlockHash(b.db) number := rawdb.ReadHeaderNumber(b.db, hash) if number == nil { return nil, nil } num = *number - } else { + default: num = uint64(blockNr) hash = rawdb.ReadCanonicalHash(b.db, num) } @@ -92,20 +96,15 @@ func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types. return nil, nil } -func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { - number := rawdb.ReadHeaderNumber(b.db, hash) - if number == nil { - return nil, nil - } - receipts := rawdb.ReadReceipts(b.db, hash, *number, params.TestChainConfig) - - logs := make([][]*types.Log, len(receipts)) - for i, receipt := range receipts { - logs[i] = receipt.Logs - } +func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { + logs := rawdb.ReadLogs(b.db, hash, number, params.TestChainConfig) return logs, nil } +func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + return nil, nil +} + func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { return b.txFeed.Subscribe(ch) } @@ -157,6 +156,12 @@ func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.Matc }() } +func newTestFilterSystem(t testing.TB, db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) { + backend := &testBackend{db: db} + sys := NewFilterSystem(backend, cfg) + return backend, sys +} + // TestBlockSubscription tests if a block subscription returns block hashes for posted chain events. // It creates multiple subscriptions: // - one at the start and should receive all posted chain events and a second (blockHashes) @@ -166,11 +171,14 @@ func TestBlockSubscription(t *testing.T) { t.Parallel() var ( - db = rawdb.NewMemoryDatabase() - backend = &testBackend{db: db} - 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) {}) + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys, false) + genesis = &core.Genesis{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + _, chain, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {}) chainEvents = []core.ChainEvent{} ) @@ -218,9 +226,9 @@ func TestPendingTxFilter(t *testing.T) { t.Parallel() var ( - db = rawdb.NewMemoryDatabase() - backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false, deadline) + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys, false) transactions = []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), @@ -230,7 +238,7 @@ func TestPendingTxFilter(t *testing.T) { types.NewTransaction(4, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), } - hashes []common.Hash + txs []*types.Transaction ) fid0 := api.NewPendingTransactionFilter() @@ -245,9 +253,9 @@ func TestPendingTxFilter(t *testing.T) { t.Fatalf("Unable to retrieve logs: %v", err) } - h := results.([]common.Hash) - hashes = append(hashes, h...) - if len(hashes) >= len(transactions) { + tx := results.([]*types.Transaction) + txs = append(txs, tx...) + if len(txs) >= len(transactions) { break } // check timeout @@ -258,13 +266,13 @@ func TestPendingTxFilter(t *testing.T) { time.Sleep(100 * time.Millisecond) } - if len(hashes) != len(transactions) { - t.Errorf("invalid number of transactions, want %d transactions(s), got %d", len(transactions), len(hashes)) + if len(txs) != len(transactions) { + t.Errorf("invalid number of transactions, want %d transactions(s), got %d", len(transactions), len(txs)) return } - for i := range hashes { - if hashes[i] != transactions[i].Hash() { - t.Errorf("hashes[%d] invalid, want %x, got %x", i, transactions[i].Hash(), hashes[i]) + for i := range txs { + if txs[i].Hash() != transactions[i].Hash() { + t.Errorf("hashes[%d] invalid, want %x, got %x", i, transactions[i].Hash(), txs[i].Hash()) } } } @@ -273,9 +281,9 @@ func TestPendingTxFilter(t *testing.T) { // If not it must return an error. func TestLogFilterCreation(t *testing.T) { var ( - db = rawdb.NewMemoryDatabase() - backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false, deadline) + db = rawdb.NewMemoryDatabase() + _, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys, false) testCases = []struct { crit FilterCriteria @@ -301,12 +309,15 @@ func TestLogFilterCreation(t *testing.T) { ) for i, test := range testCases { - _, err := api.NewFilter(test.crit) - if test.success && err != nil { + id, err := api.NewFilter(test.crit) + if err != nil && test.success { t.Errorf("expected filter creation for case %d to success, got %v", i, err) } - if !test.success && err == nil { - t.Errorf("expected testcase %d to fail with an error", i) + if err == nil { + api.UninstallFilter(id) + if !test.success { + t.Errorf("expected testcase %d to fail with an error", i) + } } } } @@ -317,9 +328,9 @@ func TestInvalidLogFilterCreation(t *testing.T) { t.Parallel() var ( - db = rawdb.NewMemoryDatabase() - backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false, deadline) + db = rawdb.NewMemoryDatabase() + _, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys, false) ) // different situations where log filter creation should fail. @@ -340,8 +351,8 @@ func TestInvalidLogFilterCreation(t *testing.T) { func TestInvalidGetLogsRequest(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false, deadline) + _, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys, false) blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") ) @@ -364,9 +375,9 @@ func TestLogFilter(t *testing.T) { t.Parallel() var ( - db = rawdb.NewMemoryDatabase() - backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false, deadline) + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys, false) firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") @@ -478,9 +489,9 @@ func TestPendingLogsSubscription(t *testing.T) { t.Parallel() var ( - db = rawdb.NewMemoryDatabase() - backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false, deadline) + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys, false) firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") @@ -506,58 +517,80 @@ func TestPendingLogsSubscription(t *testing.T) { }, } + pendingBlockNumber = big.NewInt(rpc.PendingBlockNumber.Int64()) + testCases = []struct { crit ethereum.FilterQuery expected []*types.Log c chan []*types.Log sub *Subscription + err chan error }{ // match all { - ethereum.FilterQuery{}, flattenLogs(allLogs), - nil, nil, + ethereum.FilterQuery{FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber}, + flattenLogs(allLogs), + nil, nil, nil, }, // match none due to no matching addresses { - ethereum.FilterQuery{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, + ethereum.FilterQuery{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber}, nil, - nil, nil, + nil, nil, nil, }, // match logs based on addresses, ignore topics { - ethereum.FilterQuery{Addresses: []common.Address{firstAddr}}, + ethereum.FilterQuery{Addresses: []common.Address{firstAddr}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber}, append(flattenLogs(allLogs[:2]), allLogs[5][3]), - nil, nil, + nil, nil, nil, }, // match none due to no matching topics (match with address) { - ethereum.FilterQuery{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{{notUsedTopic}}}, + ethereum.FilterQuery{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{{notUsedTopic}}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber}, + nil, nil, nil, nil, }, // match logs based on addresses and topics { - ethereum.FilterQuery{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, + ethereum.FilterQuery{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber}, append(flattenLogs(allLogs[3:5]), allLogs[5][0]), - nil, nil, + nil, nil, nil, }, // match logs based on multiple addresses and "or" topics { - ethereum.FilterQuery{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, + ethereum.FilterQuery{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber}, append(flattenLogs(allLogs[2:5]), allLogs[5][0]), + nil, nil, nil, + }, + // multiple pending logs, should match only 2 topics from the logs in block 5 + { + ethereum.FilterQuery{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, fourthTopic}}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber}, + []*types.Log{allLogs[5][0], allLogs[5][2]}, + nil, nil, nil, + }, + // match none due to only matching new mined logs + { + ethereum.FilterQuery{}, nil, + nil, nil, nil, + }, + // match none due to only matching mined logs within a specific block range + { + ethereum.FilterQuery{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2)}, nil, + nil, nil, nil, }, - // block numbers are ignored for filters created with New***Filter, these return all logs that match the given criteria when the state changes + // match all due to matching mined and pending logs { - ethereum.FilterQuery{Addresses: []common.Address{firstAddr}, FromBlock: big.NewInt(2), ToBlock: big.NewInt(3)}, - append(flattenLogs(allLogs[:2]), allLogs[5][3]), - nil, nil, + ethereum.FilterQuery{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64())}, + flattenLogs(allLogs), + nil, nil, nil, }, - // multiple pending logs, should match only 2 topics from the logs in block 5 + // match none due to matching logs from a specific block number to new mined blocks { - ethereum.FilterQuery{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, fourthTopic}}}, - []*types.Log{allLogs[5][0], allLogs[5][2]}, - nil, nil, + ethereum.FilterQuery{FromBlock: big.NewInt(1), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, + nil, + nil, nil, nil, }, } ) @@ -567,43 +600,69 @@ func TestPendingLogsSubscription(t *testing.T) { // (some) events are posted. for i := range testCases { testCases[i].c = make(chan []*types.Log) - testCases[i].sub, _ = api.events.SubscribeLogs(testCases[i].crit, testCases[i].c) + testCases[i].err = make(chan error, 1) + + var err error + testCases[i].sub, err = api.events.SubscribeLogs(testCases[i].crit, testCases[i].c) + if err != nil { + t.Fatalf("SubscribeLogs %d failed: %v\n", i, err) + } } for n, test := range testCases { i := n tt := test go func() { + defer tt.sub.Unsubscribe() + var fetched []*types.Log + + timeout := time.After(1 * time.Second) fetchLoop: for { - logs := <-tt.c - fetched = append(fetched, logs...) - if len(fetched) >= len(tt.expected) { + select { + case logs := <-tt.c: + // Do not break early if we've fetched greater, or equal, + // to the number of logs expected. This ensures we do not + // deadlock the filter system because it will do a blocking + // send on this channel if another log arrives. + fetched = append(fetched, logs...) + case <-timeout: break fetchLoop } } if len(fetched) != len(tt.expected) { - panic(fmt.Sprintf("invalid number of logs for case %d, want %d log(s), got %d", i, len(tt.expected), len(fetched))) + tt.err <- fmt.Errorf("invalid number of logs for case %d, want %d log(s), got %d", i, len(tt.expected), len(fetched)) + return } for l := range fetched { if fetched[l].Removed { - panic(fmt.Sprintf("expected log not to be removed for log %d in case %d", l, i)) + tt.err <- fmt.Errorf("expected log not to be removed for log %d in case %d", l, i) + return } if !reflect.DeepEqual(fetched[l], tt.expected[l]) { - panic(fmt.Sprintf("invalid log on index %d for case %d", l, i)) + tt.err <- fmt.Errorf("invalid log on index %d for case %d\n", l, i) + return } } + tt.err <- nil }() } // raise events - time.Sleep(1 * time.Second) for _, ev := range allLogs { backend.pendingLogsFeed.Send(ev) } + + for i := range testCases { + err := <-testCases[i].err + if err != nil { + t.Fatalf("test %d failed: %v", i, err) + } + <-testCases[i].sub.Err() + } } // TestPendingTxFilterDeadlock tests if the event loop hangs when pending @@ -614,10 +673,10 @@ func TestPendingTxFilterDeadlock(t *testing.T) { timeout := 100 * time.Millisecond var ( - db = rawdb.NewMemoryDatabase() - backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false, timeout) - done = make(chan struct{}) + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{Timeout: timeout}) + api = NewFilterAPI(sys, false) + done = make(chan struct{}) ) go func() { @@ -644,11 +703,11 @@ func TestPendingTxFilterDeadlock(t *testing.T) { fids[i] = fid // Wait for at least one tx to arrive in filter for { - hashes, err := api.GetFilterChanges(fid) + txs, err := api.GetFilterChanges(fid) if err != nil { t.Fatalf("Filter should exist: %v\n", err) } - if len(hashes.([]common.Hash)) > 0 { + if len(txs.([]*types.Transaction)) > 0 { break } runtime.Gosched() diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index 3fc77bbc..f88f5a4f 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -18,9 +18,8 @@ package filters import ( "context" - "io/ioutil" "math/big" - "os" + "reflect" "testing" "github.com/ethereum/go-ethereum/common" @@ -42,41 +41,47 @@ func makeReceipt(addr common.Address) *types.Receipt { } func BenchmarkFilters(b *testing.B) { - dir, err := ioutil.TempDir("", "filtertest") - if err != nil { - b.Fatal(err) - } - defer os.RemoveAll(dir) - var ( - db, _ = rawdb.NewLevelDBDatabase(dir, 0, 0, "", false) - backend = &testBackend{db: db} + db, _ = rawdb.NewLevelDBDatabase(b.TempDir(), 0, 0, "", false) + _, sys = newTestFilterSystem(b, db, Config{}) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = common.BytesToAddress([]byte("jeff")) addr3 = common.BytesToAddress([]byte("ethereum")) addr4 = common.BytesToAddress([]byte("random addresses please")) + + gspec = &core.Genesis{ + Alloc: core.GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.TestChainConfig, + } ) defer db.Close() - - genesis := core.GenesisBlockForTesting(db, addr1, big.NewInt(1000000)) - chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 100010, func(i int, gen *core.BlockGen) { + _, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 100010, func(i int, gen *core.BlockGen) { switch i { case 2403: receipt := makeReceipt(addr1) gen.AddUncheckedReceipt(receipt) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) case 1034: receipt := makeReceipt(addr2) gen.AddUncheckedReceipt(receipt) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) case 34: receipt := makeReceipt(addr3) gen.AddUncheckedReceipt(receipt) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) case 99999: receipt := makeReceipt(addr4) gen.AddUncheckedReceipt(receipt) - + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) } }) + // The test txs are not properly signed, can't simply create a chain + // and then import blocks. TODO(rjl493456442) try to get rid of the + // manual database writes. + gspec.MustCommit(db) + for i, block := range chain { rawdb.WriteBlock(db, block) rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) @@ -85,7 +90,7 @@ func BenchmarkFilters(b *testing.B) { } b.ResetTimer() - filter := NewRangeFilter(backend, 0, -1, []common.Address{addr1, addr2, addr3, addr4}, nil) + filter := sys.NewRangeFilter(0, -1, []common.Address{addr1, addr2, addr3, addr4}, nil) for i := 0; i < b.N; i++ { logs, _ := filter.Logs(context.Background()) @@ -96,15 +101,10 @@ func BenchmarkFilters(b *testing.B) { } func TestFilters(t *testing.T) { - dir, err := ioutil.TempDir("", "filtertest") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - + core.CheckAllocWithTotalSupply = false var ( - db, _ = rawdb.NewLevelDBDatabase(dir, 0, 0, "", false) - backend = &testBackend{db: db} + db, _ = rawdb.NewLevelDBDatabase(t.TempDir(), 0, 0, "", false) + _, sys = newTestFilterSystem(t, db, Config{}) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr = crypto.PubkeyToAddress(key1.PublicKey) @@ -112,11 +112,17 @@ func TestFilters(t *testing.T) { hash2 = common.BytesToHash([]byte("topic2")) hash3 = common.BytesToHash([]byte("topic3")) hash4 = common.BytesToHash([]byte("topic4")) + + gspec = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{addr: {Balance: big.NewInt(1000000)}}, + BaseFee: big.NewInt(params.InitialBaseFee), + CommunityRate: big.NewInt(20), + } ) defer db.Close() - genesis := core.GenesisBlockForTesting(db, addr, big.NewInt(1000000)) - chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 1000, func(i int, gen *core.BlockGen) { + _, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 1000, func(i int, gen *core.BlockGen) { switch i { case 1: receipt := types.NewReceipt(nil, false, 0) @@ -127,7 +133,7 @@ func TestFilters(t *testing.T) { }, } gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)) + gen.AddUncheckedTx(types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, gen.BaseFee(), nil)) case 2: receipt := types.NewReceipt(nil, false, 0) receipt.Logs = []*types.Log{ @@ -137,7 +143,7 @@ func TestFilters(t *testing.T) { }, } gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)) + gen.AddUncheckedTx(types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, gen.BaseFee(), nil)) case 998: receipt := types.NewReceipt(nil, false, 0) @@ -148,7 +154,7 @@ func TestFilters(t *testing.T) { }, } gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(998, common.HexToAddress("0x998"), big.NewInt(998), 998, big.NewInt(998), nil)) + gen.AddUncheckedTx(types.NewTransaction(998, common.HexToAddress("0x998"), big.NewInt(998), 998, gen.BaseFee(), nil)) case 999: receipt := types.NewReceipt(nil, false, 0) receipt.Logs = []*types.Log{ @@ -158,9 +164,13 @@ func TestFilters(t *testing.T) { }, } gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, big.NewInt(999), nil)) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) } }) + // The test txs are not properly signed, can't simply create a chain + // and then import blocks. TODO(rjl493456442) try to get rid of the + // manual database writes. + gspec.MustCommit(db) for i, block := range chain { rawdb.WriteBlock(db, block) rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) @@ -168,58 +178,57 @@ func TestFilters(t *testing.T) { rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) } - filter := NewRangeFilter(backend, 0, -1, []common.Address{addr}, [][]common.Hash{{hash1, hash2, hash3, hash4}}) - - logs, _ := filter.Logs(context.Background()) + filter := sys.NewRangeFilter(0, -1, []common.Address{addr}, [][]common.Hash{{hash1, hash2, hash3, hash4}}) + logs, err := filter.Logs(context.Background()) + if err != nil { + t.Fatal(err) + } if len(logs) != 4 { t.Error("expected 4 log, got", len(logs)) } - filter = NewRangeFilter(backend, 900, 999, []common.Address{addr}, [][]common.Hash{{hash3}}) - logs, _ = filter.Logs(context.Background()) - if len(logs) != 1 { - t.Error("expected 1 log, got", len(logs)) - } - if len(logs) > 0 && logs[0].Topics[0] != hash3 { - t.Errorf("expected log[0].Topics[0] to be %x, got %x", hash3, logs[0].Topics[0]) - } - - filter = NewRangeFilter(backend, 990, -1, []common.Address{addr}, [][]common.Hash{{hash3}}) - logs, _ = filter.Logs(context.Background()) - if len(logs) != 1 { - t.Error("expected 1 log, got", len(logs)) - } - if len(logs) > 0 && logs[0].Topics[0] != hash3 { - t.Errorf("expected log[0].Topics[0] to be %x, got %x", hash3, logs[0].Topics[0]) - } - - filter = NewRangeFilter(backend, 1, 10, nil, [][]common.Hash{{hash1, hash2}}) - - logs, _ = filter.Logs(context.Background()) - if len(logs) != 2 { - t.Error("expected 2 log, got", len(logs)) - } - - failHash := common.BytesToHash([]byte("fail")) - filter = NewRangeFilter(backend, 0, -1, nil, [][]common.Hash{{failHash}}) - - logs, _ = filter.Logs(context.Background()) - if len(logs) != 0 { - t.Error("expected 0 log, got", len(logs)) - } - - failAddr := common.BytesToAddress([]byte("failmenow")) - filter = NewRangeFilter(backend, 0, -1, []common.Address{failAddr}, nil) - - logs, _ = filter.Logs(context.Background()) - if len(logs) != 0 { - t.Error("expected 0 log, got", len(logs)) - } - - filter = NewRangeFilter(backend, 0, -1, nil, [][]common.Hash{{failHash}, {hash1}}) - - logs, _ = filter.Logs(context.Background()) - if len(logs) != 0 { - t.Error("expected 0 log, got", len(logs)) + for i, tc := range []struct { + f *Filter + wantHashes []common.Hash + }{ + { + sys.NewRangeFilter(900, 999, []common.Address{addr}, [][]common.Hash{{hash3}}), + []common.Hash{hash3}, + }, { + sys.NewRangeFilter(990, -1, []common.Address{addr}, [][]common.Hash{{hash3}}), + []common.Hash{hash3}, + }, { + sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}), + []common.Hash{hash1, hash2}, + }, { + sys.NewRangeFilter(0, -1, nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}), + nil, + }, { + sys.NewRangeFilter(0, -1, []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil), + nil, + }, { + sys.NewRangeFilter(0, -1, nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}), + nil, + }, { + sys.NewRangeFilter(-1, -1, nil, nil), []common.Hash{hash4}, + }, + } { + logs, err := tc.f.Logs(context.Background()) + if err != nil { + t.Fatal(err) + } + var haveHashes []common.Hash + for _, l := range logs { + haveHashes = append(haveHashes, l.Topics[0]) + } + if have, want := len(haveHashes), len(tc.wantHashes); have != want { + t.Fatalf("test %d, have %d logs, want %d", i, have, want) + } + if len(haveHashes) == 0 { + continue + } + if !reflect.DeepEqual(tc.wantHashes, haveHashes) { + t.Fatalf("test %d, have %v want %v", i, haveHashes, tc.wantHashes) + } } } diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index a14dd594..c2e48df8 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -18,12 +18,15 @@ package gasprice import ( "context" + "encoding/binary" "errors" "fmt" + "math" "math/big" "sort" "sync/atomic" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -36,10 +39,6 @@ var ( ) const ( - // maxFeeHistory is the maximum number of blocks that can be retrieved for a - // fee history request. - maxFeeHistory = 1024 - // maxBlockFetchers is the max number of goroutines to spin up to pull blocks // for the fee history calculation (mostly relevant for LES). maxBlockFetchers = 4 @@ -48,15 +47,25 @@ const ( // blockFees represents a single block for processing type blockFees struct { // set by the caller - blockNumber rpc.BlockNumber + blockNumber uint64 header *types.Header block *types.Block // only set if reward percentiles are requested receipts types.Receipts // filled by processBlock + results processedFees + err error +} + +type cacheKey struct { + number uint64 + percentiles string +} + +// processedFees contains the results of a processed block. +type processedFees struct { reward []*big.Int baseFee, nextBaseFee *big.Int gasUsedRatio float64 - err error } // txGasAndReward is sorted in ascending order based on reward @@ -81,15 +90,15 @@ func (s sortGasAndReward) Less(i, j int) bool { // fills in the rest of the fields. func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { chainconfig := oracle.backend.ChainConfig() - if bf.baseFee = bf.header.BaseFee; bf.baseFee == nil { - bf.baseFee = new(big.Int) + if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil { + bf.results.baseFee = new(big.Int) } if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) { - bf.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header) + bf.results.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header) } else { - bf.nextBaseFee = new(big.Int) + bf.results.nextBaseFee = new(big.Int) } - bf.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit) + bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit) if len(percentiles) == 0 { // rewards were not requested, return null return @@ -99,11 +108,11 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { return } - bf.reward = make([]*big.Int, len(percentiles)) + bf.results.reward = make([]*big.Int, len(percentiles)) if len(bf.block.Transactions()) == 0 { // return an all zero row if there are no transactions to gather data from - for i := range bf.reward { - bf.reward[i] = new(big.Int) + for i := range bf.results.reward { + bf.results.reward[i] = new(big.Int) } return } @@ -113,7 +122,7 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} } - sort.Sort(sorter) + sort.Stable(sorter) var txIndex int sumGasUsed := sorter[0].gasUsed @@ -124,7 +133,7 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { txIndex++ sumGasUsed += sorter[txIndex].gasUsed } - bf.reward[i] = sorter[txIndex].reward + bf.results.reward[i] = sorter[txIndex].reward } } @@ -133,55 +142,64 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { // also returned if requested and available. // Note: an error is only returned if retrieving the head header has failed. If there are no // retrievable blocks in the specified range then zero block count is returned with no error. -func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks, maxHistory int) (*types.Block, []*types.Receipt, rpc.BlockNumber, int, error) { +func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNumber, blocks int) (*types.Block, []*types.Receipt, uint64, int, error) { var ( - headBlock rpc.BlockNumber + headBlock *types.Header pendingBlock *types.Block pendingReceipts types.Receipts + err error ) - // query either pending block or head header and set headBlock - if lastBlock == rpc.PendingBlockNumber { - if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil { - lastBlock = rpc.BlockNumber(pendingBlock.NumberU64()) - headBlock = lastBlock - 1 - } else { - // pending block not supported by backend, process until latest block - lastBlock = rpc.LatestBlockNumber - blocks-- - if blocks == 0 { - return nil, nil, 0, 0, nil - } - } - } - if pendingBlock == nil { - // if pending block is not fetched then we retrieve the head header to get the head block number - if latestHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err == nil { - headBlock = rpc.BlockNumber(latestHeader.Number.Uint64()) - } else { - return nil, nil, 0, 0, err - } + + // Get the chain's current head. + if headBlock, err = oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err != nil { + return nil, nil, 0, 0, err } - if lastBlock == rpc.LatestBlockNumber { - lastBlock = headBlock - } else if pendingBlock == nil && lastBlock > headBlock { - return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock) + head := rpc.BlockNumber(headBlock.Number.Uint64()) + + // Fail if request block is beyond the chain's current head. + if head < reqEnd { + return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, reqEnd, head) } - if maxHistory != 0 { - // limit retrieval to the given number of latest blocks - if tooOldCount := int64(headBlock) - int64(maxHistory) - int64(lastBlock) + int64(blocks); tooOldCount > 0 { - // tooOldCount is the number of requested blocks that are too old to be served - if int64(blocks) > tooOldCount { - blocks -= int(tooOldCount) + + // Resolve block tag. + if reqEnd < 0 { + var ( + resolved *types.Header + err error + ) + switch reqEnd { + case rpc.PendingBlockNumber: + if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil { + resolved = pendingBlock.Header() } else { - return nil, nil, 0, 0, nil + // Pending block not supported by backend, process only until latest block. + resolved = headBlock + + // Update total blocks to return to account for this. + blocks-- } + case rpc.LatestBlockNumber: + // Retrieved above. + resolved = headBlock + case rpc.EarliestBlockNumber: + resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.EarliestBlockNumber) + } + if resolved == nil || err != nil { + return nil, nil, 0, 0, err } + // Absolute number resolved. + reqEnd = rpc.BlockNumber(resolved.Number.Uint64()) } - // ensure not trying to retrieve before genesis - if rpc.BlockNumber(blocks) > lastBlock+1 { - blocks = int(lastBlock + 1) + + // If there are no blocks to return, short circuit. + if blocks == 0 { + return nil, nil, 0, 0, nil + } + // Ensure not trying to retrieve before genesis. + if int(reqEnd+1) < blocks { + blocks = int(reqEnd + 1) } - return pendingBlock, pendingReceipts, lastBlock, blocks, nil + return pendingBlock, pendingReceipts, uint64(reqEnd), blocks, nil } // FeeHistory returns data relevant for fee estimation based on the specified range of blocks. @@ -191,15 +209,20 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.Block // actually processed range is returned to avoid ambiguity when parts of the requested range // are not available or when the head has changed during processing this request. // Three arrays are returned based on the processed blocks: -// - reward: the requested percentiles of effective priority fees per gas of transactions in each -// block, sorted in ascending order and weighted by gas used. -// - baseFee: base fee per gas in the given block -// - gasUsedRatio: gasUsed/gasLimit in the given block +// - reward: the requested percentiles of effective priority fees per gas of transactions in each +// block, sorted in ascending order and weighted by gas used. +// - baseFee: base fee per gas in the given block +// - gasUsedRatio: gasUsed/gasLimit in the given block +// // Note: baseFee includes the next block after the newest of the returned range, because this // value can be derived from the newest block. -func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (rpc.BlockNumber, [][]*big.Int, []*big.Int, []float64, error) { +func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { if blocks < 1 { - return 0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks + return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks + } + maxFeeHistory := oracle.maxHeaderHistory + if len(rewardPercentiles) != 0 { + maxFeeHistory = oracle.maxBlockHistory } if blocks > maxFeeHistory { log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory) @@ -207,62 +230,72 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, lastBlock rpc. } for i, p := range rewardPercentiles { if p < 0 || p > 100 { - return 0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p) + return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p) } if i > 0 && p < rewardPercentiles[i-1] { - return 0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) + return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) } } - // Only process blocks if reward percentiles were requested - maxHistory := oracle.maxHeaderHistory - if len(rewardPercentiles) != 0 { - maxHistory = oracle.maxBlockHistory - } var ( pendingBlock *types.Block pendingReceipts []*types.Receipt err error ) - pendingBlock, pendingReceipts, lastBlock, blocks, err = oracle.resolveBlockRange(ctx, lastBlock, blocks, maxHistory) + pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks) if err != nil || blocks == 0 { - return 0, nil, nil, nil, err + return common.Big0, nil, nil, nil, err } - oldestBlock := lastBlock + 1 - rpc.BlockNumber(blocks) + oldestBlock := lastBlock + 1 - uint64(blocks) var ( - next = int64(oldestBlock) + next = oldestBlock results = make(chan *blockFees, blocks) ) + percentileKey := make([]byte, 8*len(rewardPercentiles)) + for i, p := range rewardPercentiles { + binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) + } for i := 0; i < maxBlockFetchers && i < blocks; i++ { go func() { for { // Retrieve the next block number to fetch with this goroutine - blockNumber := rpc.BlockNumber(atomic.AddInt64(&next, 1) - 1) + blockNumber := atomic.AddUint64(&next, 1) - 1 if blockNumber > lastBlock { return } fees := &blockFees{blockNumber: blockNumber} - if pendingBlock != nil && blockNumber >= rpc.BlockNumber(pendingBlock.NumberU64()) { + if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() { fees.block, fees.receipts = pendingBlock, pendingReceipts + fees.header = fees.block.Header() + oracle.processBlock(fees, rewardPercentiles) + results <- fees } else { - if len(rewardPercentiles) != 0 { - fees.block, fees.err = oracle.backend.BlockByNumber(ctx, blockNumber) - if fees.block != nil && fees.err == nil { - fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash()) - } + cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)} + + if p, ok := oracle.historyCache.Get(cacheKey); ok { + fees.results = p + results <- fees } else { - fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, blockNumber) + if len(rewardPercentiles) != 0 { + fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber)) + if fees.block != nil && fees.err == nil { + fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash()) + fees.header = fees.block.Header() + } + } else { + fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) + } + if fees.header != nil && fees.err == nil { + oracle.processBlock(fees, rewardPercentiles) + if fees.err == nil { + oracle.historyCache.Add(cacheKey, fees.results) + } + } + // send to results even if empty to guarantee that blocks items are sent in total + results <- fees } } - if fees.block != nil { - fees.header = fees.block.Header() - } - if fees.header != nil { - oracle.processBlock(fees, rewardPercentiles) - } - // send to results even if empty to guarantee that blocks items are sent in total - results <- fees } }() } @@ -275,11 +308,11 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, lastBlock rpc. for ; blocks > 0; blocks-- { fees := <-results if fees.err != nil { - return 0, nil, nil, nil, fees.err + return common.Big0, nil, nil, nil, fees.err } i := int(fees.blockNumber - oldestBlock) - if fees.header != nil { - reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.reward, fees.baseFee, fees.nextBaseFee, fees.gasUsedRatio + if fees.results.baseFee != nil { + reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio } else { // getting no block and no error means we are requesting into the future (might happen because of a reorg) if i < firstMissing { @@ -288,7 +321,7 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, lastBlock rpc. } } if firstMissing == 0 { - return 0, nil, nil, nil, nil + return common.Big0, nil, nil, nil, nil } if len(rewardPercentiles) != 0 { reward = reward[:firstMissing] @@ -296,5 +329,5 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, lastBlock rpc. reward = nil } baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing] - return oldestBlock, reward, baseFee, gasUsedRatio, nil + return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil } diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go index 57cfb260..982b2ed7 100644 --- a/eth/gasprice/feehistory_test.go +++ b/eth/gasprice/feehistory_test.go @@ -32,24 +32,24 @@ func TestFeeHistory(t *testing.T) { count int last rpc.BlockNumber percent []float64 - expFirst rpc.BlockNumber + expFirst uint64 expCount int expErr error }{ - {false, 0, 0, 10, 30, nil, 21, 10, nil}, - {false, 0, 0, 10, 30, []float64{0, 10}, 21, 10, nil}, - {false, 0, 0, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile}, - {false, 0, 0, 1000000000, 30, nil, 0, 31, nil}, - {false, 0, 0, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil}, - {false, 0, 0, 10, 40, nil, 0, 0, errRequestBeyondHead}, - {true, 0, 0, 10, 40, nil, 0, 0, errRequestBeyondHead}, + {false, 1000, 1000, 10, 30, nil, 21, 10, nil}, + {false, 1000, 1000, 10, 30, []float64{0, 10}, 21, 10, nil}, + {false, 1000, 1000, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile}, + {false, 1000, 1000, 1000000000, 30, nil, 0, 31, nil}, + {false, 1000, 1000, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil}, + {false, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead}, + {true, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead}, {false, 20, 2, 100, rpc.LatestBlockNumber, nil, 13, 20, nil}, {false, 20, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil}, {false, 20, 2, 100, 32, []float64{0, 10}, 31, 2, nil}, - {false, 0, 0, 1, rpc.PendingBlockNumber, nil, 0, 0, nil}, - {false, 0, 0, 2, rpc.PendingBlockNumber, nil, 32, 1, nil}, - {true, 0, 0, 2, rpc.PendingBlockNumber, nil, 32, 2, nil}, - {true, 0, 0, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 2, nil}, + {false, 1000, 1000, 1, rpc.PendingBlockNumber, nil, 0, 0, nil}, + {false, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil}, + {true, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 2, nil}, + {true, 1000, 1000, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 2, nil}, } for i, c := range cases { config := Config{ @@ -60,7 +60,7 @@ func TestFeeHistory(t *testing.T) { oracle := NewOracle(backend, config) first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent) - + backend.teardown() expReward := c.expCount if len(c.percent) == 0 { expReward = 0 @@ -70,7 +70,7 @@ func TestFeeHistory(t *testing.T) { expBaseFee++ } - if first != c.expFirst { + if first.Uint64() != c.expFirst { t.Fatalf("Test case %d: first block mismatch, want %d, got %d", i, c.expFirst, first) } if len(reward) != expReward { diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 407eeaa2..604ad5e1 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -23,7 +23,10 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -53,6 +56,7 @@ type OracleBackend interface { GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) PendingBlockAndReceipts() (*types.Block, types.Receipts) ChainConfig() *params.ChainConfig + SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription } // Oracle recommends gas prices based on the content of recent @@ -68,6 +72,8 @@ type Oracle struct { checkBlocks, percentile int maxHeaderHistory, maxBlockHistory int + + historyCache *lru.Cache[cacheKey, processedFees] } // NewOracle returns a new gasprice oracle which can recommend suitable @@ -82,8 +88,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { if percent < 0 { percent = 0 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) - } - if percent > 100 { + } else if percent > 100 { percent = 100 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) } @@ -99,6 +104,30 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { } else if ignorePrice.Int64() > 0 { log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice) } + maxHeaderHistory := params.MaxHeaderHistory + if maxHeaderHistory < 1 { + maxHeaderHistory = 1 + log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory) + } + maxBlockHistory := params.MaxBlockHistory + if maxBlockHistory < 1 { + maxBlockHistory = 1 + log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory) + } + + cache := lru.NewCache[cacheKey, processedFees](2048) + headEvent := make(chan core.ChainHeadEvent, 1) + backend.SubscribeChainHeadEvent(headEvent) + go func() { + var lastHead common.Hash + for ev := range headEvent { + if ev.Block.ParentHash() != lastHead { + cache.Purge() + } + lastHead = ev.Block.Hash() + } + }() + return &Oracle{ backend: backend, lastPrice: params.Default, @@ -106,8 +135,9 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { ignorePrice: ignorePrice, checkBlocks: blocks, percentile: percent, - maxHeaderHistory: params.MaxHeaderHistory, - maxBlockHistory: params.MaxBlockHistory, + maxHeaderHistory: maxHeaderHistory, + maxBlockHistory: maxBlockHistory, + historyCache: cache, } } diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index dea8fea9..1633682f 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -25,10 +25,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -44,6 +44,9 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber if number > testHead { return nil, nil } + if number == rpc.EarliestBlockNumber { + number = 0 + } if number == rpc.LatestBlockNumber { number = testHead } @@ -61,6 +64,9 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) if number > testHead { return nil, nil } + if number == rpc.EarliestBlockNumber { + number = 0 + } if number == rpc.LatestBlockNumber { number = testHead } @@ -90,33 +96,38 @@ func (b *testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } +func (b *testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return nil +} + +func (b *testBackend) teardown() { + b.chain.Stop() +} + +// newTestBackend creates a test backend. OBS: don't forget to invoke tearDown +// after use, otherwise the blockchain instance will mem-leak via goroutines. func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBackend { var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr = crypto.PubkeyToAddress(key.PublicKey) + config = *params.TestChainConfig // needs copy because it is modified below gspec = &core.Genesis{ - Config: params.TestChainConfig, + Config: &config, Alloc: core.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, } signer = types.LatestSigner(gspec.Config) ) - if londonBlock != nil { - gspec.Config.LondonBlock = londonBlock - signer = types.LatestSigner(gspec.Config) - } else { - gspec.Config.LondonBlock = nil - } + params.GenesisSupply = big.NewInt(math.MaxInt64) + config.LondonBlock = londonBlock engine := ethash.NewFaker() - db := rawdb.NewMemoryDatabase() - genesis, _ := gspec.Commit(db) // Generate testing blocks - blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, testHead+1, func(i int, b *core.BlockGen) { + db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, testHead+1, func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{1}) - var tx *types.Transaction + var txdata types.TxData if londonBlock != nil && b.Number().Cmp(londonBlock) >= 0 { - txdata := &types.DynamicFeeTx{ + txdata = &types.DynamicFeeTx{ ChainID: gspec.Config.ChainID, Nonce: b.TxNonce(addr), To: &common.Address{}, @@ -125,9 +136,8 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke GasTipCap: big.NewInt(int64(i+1) * params.GWei), Data: []byte{}, } - tx = types.NewTx(txdata) } else { - txdata := &types.LegacyTx{ + txdata = &types.LegacyTx{ Nonce: b.TxNonce(addr), To: &common.Address{}, Gas: 21000, @@ -135,18 +145,11 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke Value: big.NewInt(100), Data: []byte{}, } - tx = types.NewTx(txdata) - } - tx, err := types.SignTx(tx, signer, key) - if err != nil { - t.Fatalf("failed to create tx: %v", err) } - b.AddTx(tx) + b.AddTx(types.MustSignNewTx(key, signer, txdata)) }) // Construct testing chain - diskdb := rawdb.NewMemoryDatabase() - gspec.Commit(diskdb) - chain, err := core.NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec.Config, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create local chain, %v", err) } @@ -184,6 +187,7 @@ func TestSuggestTipCap(t *testing.T) { // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G got, err := oracle.SuggestTipCap(context.Background()) + backend.teardown() if err != nil { t.Fatalf("Failed to retrieve recommended gas price: %v", err) } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 4acaa811..29f04117 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -55,7 +55,7 @@ func (h *testEthHandler) TxPool() eth.TxPool { panic("no backi 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) Engine() consensus.Engine { panic("not used in tests") } +func (h *testEthHandler) Engine() consensus.Engine { return nil } func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { switch packet := packet.(type) { @@ -86,6 +86,8 @@ 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) { + core.RegGenesis = nil + core.CheckAllocWithTotalSupply = false t.Parallel() var ( @@ -456,6 +458,7 @@ func testTransactionPropagation(t *testing.T, protocol uint) { // 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) { + core.RegGenesis = nil tests := []struct { syncmode downloader.SyncMode checkpoint bool diff --git a/eth/handler_nodes.go b/eth/handler_nodes.go index 640fbca6..3b1ac44a 100644 --- a/eth/handler_nodes.go +++ b/eth/handler_nodes.go @@ -101,7 +101,10 @@ func newNodeBroadcaster(miner common.Address, manager staticNodeServer, handler // only used for hotstuff miner func (h *nodeFetcher) Start() { - handler := h.handler.engine.(consensus.Handler) + handler, ok := h.handler.engine.(consensus.Handler) + if !ok { + return + } h.notifySub = handler.SubscribeNodes(h.notifyCh) h.local = h.server.Self() for _, v := range h.server.SeedNodes() { @@ -116,6 +119,10 @@ func (h *nodeFetcher) Start() { } func (h *nodeFetcher) Stop() { + _, ok := h.handler.engine.(consensus.Handler) + if !ok { + return + } if h.isSeed() { atomic.StoreInt32(&h.seed, 0) } diff --git a/eth/handler_test.go b/eth/handler_test.go index 742435df..e9e006cb 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -132,6 +132,7 @@ func newTestHandler() *testHandler { func newTestHandlerWithBlocks(blocks int) *testHandler { // Create a database pre-initialize with a genesis block db := rawdb.NewMemoryDatabase() + params.GenesisSupply = big.NewInt(1000000) (&core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index ce3a0071..b9b83bb6 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -65,9 +65,10 @@ func newTestBackend(blocks int) *testBackend { func newTestBackendWithGenerator(blocks int, generator func(int, *core.BlockGen)) *testBackend { // Create a database pre-initialize with a genesis block db := rawdb.NewMemoryDatabase() + core.CheckAllocWithTotalSupply = false (&core.Genesis{ Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, + Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000000000000000)}}, }).MustCommit(db) chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) @@ -412,13 +413,13 @@ func testGetNodeData(t *testing.T, protocol uint) { 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) + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(21000000000000), params.TxGas, big.NewInt(params.InitialBaseFee), 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) + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, acc1Key) block.AddTx(tx1) block.AddTx(tx2) case 2: @@ -528,13 +529,13 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { 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) + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000000000000000), params.TxGas, big.NewInt(params.InitialBaseFee), 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) + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, acc1Key) block.AddTx(tx1) block.AddTx(tx2) case 2: diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 9ff01d66..72afef34 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -59,6 +59,7 @@ type testBackend struct { } func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + core.CheckAllocWithTotalSupply = false backend := &testBackend{ chainConfig: params.TestChainConfig, engine: ethash.NewFaker(), @@ -186,13 +187,14 @@ func TestTraceCall(t *testing.T) { accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)}, }} + params.GenesisSupply = big.NewInt(params.Ether * 3) 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) + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, accounts[0].key) b.AddTx(tx) })) @@ -322,7 +324,7 @@ func TestOverridenTraceCall(t *testing.T) { // 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) + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, accounts[0].key) b.AddTx(tx) })) randomAccounts, tracer := newAccounts(3), "callTracer" @@ -462,7 +464,7 @@ func TestTraceTransaction(t *testing.T) { // 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) + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, accounts[0].key) b.AddTx(tx) target = tx.Hash() })) @@ -496,7 +498,7 @@ func TestTraceBlock(t *testing.T) { // 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) + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, accounts[0].key) b.AddTx(tx) })) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 3e306063..f1809c33 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -488,6 +488,17 @@ func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockN return hex, nil } +// CallContractAtHash is almost the same as CallContract except that it selects +// the block by block hash instead of block height. +func (ec *Client) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { + var hex hexutil.Bytes + err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), rpc.BlockNumberOrHashWithHash(blockHash, false)) + if err != nil { + return nil, err + } + return hex, nil +} + // PendingCallContract executes a message call transaction using the EVM. // The state seen by the contract call is the pending state. func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { @@ -519,6 +530,38 @@ func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return (*big.Int)(&hex), nil } +type feeHistoryResultMarshaling struct { + OldestBlock *hexutil.Big `json:"oldestBlock"` + Reward [][]*hexutil.Big `json:"reward,omitempty"` + BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"` + GasUsedRatio []float64 `json:"gasUsedRatio"` +} + +// FeeHistory retrieves the fee market history. +func (ec *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + var res feeHistoryResultMarshaling + if err := ec.c.CallContext(ctx, &res, "eth_feeHistory", hexutil.Uint(blockCount), toBlockNumArg(lastBlock), rewardPercentiles); err != nil { + return nil, err + } + reward := make([][]*big.Int, len(res.Reward)) + for i, r := range res.Reward { + reward[i] = make([]*big.Int, len(r)) + for j, r := range r { + reward[i][j] = (*big.Int)(r) + } + } + baseFee := make([]*big.Int, len(res.BaseFee)) + for i, b := range res.BaseFee { + baseFee[i] = (*big.Int)(b) + } + return ðereum.FeeHistory{ + OldestBlock: (*big.Int)(res.OldestBlock), + Reward: reward, + BaseFee: baseFee, + GasUsedRatio: res.GasUsedRatio, + }, nil +} + // EstimateGas tries to estimate the gas needed to execute a specific transaction based on // the current pending state of the backend blockchain. There is no guarantee that this is // the true gas limit requirement as other transactions may be added or removed by miners, diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 56f287b6..fcd6db1b 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -19,13 +19,8 @@ package ethclient import ( "bytes" "context" - "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/contracts/native/governance/node_manager" - "github.com/ethereum/go-ethereum/contracts/native/utils" - "github.com/ethereum/go-ethereum/core/state" "math/big" "reflect" "testing" @@ -35,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -189,12 +183,37 @@ func TestToFilterArg(t *testing.T) { var ( testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testAddr = crypto.PubkeyToAddress(testKey.PublicKey) - testBalance = big.NewInt(2e10) + testBalance = big.NewInt(2e15) ) +var genesis = &core.Genesis{ + Config: params.AllEthashProtocolChanges, + Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + BaseFee: big.NewInt(params.InitialBaseFee), +} + +var testTx1 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{ + Nonce: 0, + Value: big.NewInt(12), + GasPrice: big.NewInt(params.InitialBaseFee), + Gas: params.TxGas, + To: &common.Address{2}, +}) + +var testTx2 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{ + Nonce: 1, + Value: big.NewInt(8), + GasPrice: big.NewInt(params.InitialBaseFee), + Gas: params.TxGas, + To: &common.Address{2}, +}) + func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { // Generate test chain. - genesis, blocks := generateTestChain() + blocks := generateTestChain() + // Create node n, err := node.New(&node.Config{}) if err != nil { @@ -217,30 +236,23 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { return n, blocks } -// todo(fuk): modify test chain generator -func generateTestChain() (*core.Genesis, []*types.Block) { - db := rawdb.NewMemoryDatabase() - config := params.AllEthashProtocolChanges - basicHotstuffExtra := []byte("0x0000000000000000000000000000000000000000000000000000000000000000f89bf85494258af48e28e4a6846e931ddff8e1cdf8579821e5948c09d936a1b408d6e0afaa537ba4e06c4504a0ae94c095448424a5ecd5ca7ccdadfaad127a9d7e88ec94d47a4e56e9262543db39d9203cf1a2e53735f834b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c080") - //normalExtra := []byte("test genesis") - genesis := &core.Genesis{ - Config: config, - Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, - ExtraData: basicHotstuffExtra, - Timestamp: 9000, - } +func generateTestChain() []*types.Block { generate := func(i int, g *core.BlockGen) { g.OffsetTime(5) g.SetExtra([]byte("test")) + if i == 1 { + // Test transactions are included in block #2. + g.AddTx(testTx1) + g.AddTx(testTx2) + } } - gblock := genesis.ToBlock(db) - engine := ethash.NewFaker() - blocks, _ := core.GenerateChain(config, gblock, engine, db, 1, generate) - blocks = append([]*types.Block{gblock}, blocks...) - return genesis, blocks + db, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 2, generate) + return append([]*types.Block{genesis.ToBlock(db)}, blocks...) } func TestEthClient(t *testing.T) { + core.RegGenesis = nil + core.CheckAllocWithTotalSupply = false backend, chain := newTestBackend(t) client, _ := backend.Attach() defer backend.Close() @@ -249,30 +261,36 @@ func TestEthClient(t *testing.T) { tests := map[string]struct { test func(t *testing.T) }{ - "TestHeader": { + "Header": { func(t *testing.T) { testHeader(t, chain, client) }, }, - "TestBalanceAt": { + "BalanceAt": { func(t *testing.T) { testBalanceAt(t, client) }, }, - "TestTxInBlockInterrupted": { + "TxInBlockInterrupted": { func(t *testing.T) { testTransactionInBlockInterrupted(t, client) }, }, - "TestChainID": { + "ChainID": { func(t *testing.T) { testChainID(t, client) }, }, - "TestGetBlock": { + "GetBlock": { func(t *testing.T) { testGetBlock(t, client) }, }, - "TestStatusFunctions": { + "StatusFunctions": { func(t *testing.T) { testStatusFunctions(t, client) }, }, - "TestCallContract": { + "CallContract": { func(t *testing.T) { testCallContract(t, client) }, }, - "TestAtFunctions": { + "CallContractAtHash": { + func(t *testing.T) { testCallContractAtHash(t, client) }, + }, + "AtFunctions": { func(t *testing.T) { testAtFunctions(t, client) }, }, + "TransactionSender": { + func(t *testing.T) { testTransactionSender(t, client) }, + }, } t.Parallel() @@ -328,6 +346,11 @@ func testBalanceAt(t *testing.T, client *rpc.Client) { want *big.Int wantErr error }{ + "valid_account_genesis": { + account: testAddr, + block: big.NewInt(0), + want: testBalance, + }, "valid_account": { account: testAddr, block: big.NewInt(1), @@ -365,23 +388,25 @@ func testBalanceAt(t *testing.T, client *rpc.Client) { func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) { ec := NewClient(client) - // Get current block by number + // 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 + + // Test tx in block interrupted. ctx, cancel := context.WithCancel(context.Background()) cancel() - tx, err := ec.TransactionInBlock(ctx, block.Hash(), 1) + tx, err := ec.TransactionInBlock(ctx, block.Hash(), 0) if tx != nil { t.Fatal("transaction should 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 { + + // Test tx in block not found. + if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 20); err != ethereum.NotFound { t.Fatal("error should be ethereum.NotFound") } } @@ -399,12 +424,13 @@ func testChainID(t *testing.T, client *rpc.Client) { 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) } - if blockNumber != 1 { + if blockNumber != 2 { t.Fatalf("BlockNumber returned wrong number: %d", blockNumber) } // Get current block by number @@ -452,6 +478,7 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if progress != nil { t.Fatalf("unexpected progress: %v", progress) } + // NetworkID networkID, err := ec.NetworkID(context.Background()) if err != nil { @@ -460,7 +487,8 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if networkID.Cmp(big.NewInt(0)) != 0 { t.Fatalf("unexpected networkID: %v", networkID) } - // SuggestGasPrice (should suggest 1 Gwei) + + // SuggestGasPrice gasPrice, err := ec.SuggestGasPrice(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -468,6 +496,65 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if gasPrice.Cmp(big.NewInt(1000000000)) != 0 { t.Fatalf("unexpected gas price: %v", gasPrice) } + + // SuggestGasTipCap + gasTipCap, err := ec.SuggestGasTipCap(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gasTipCap.Cmp(big.NewInt(234375000)) != 0 { + t.Fatalf("unexpected gas tip cap: %v", gasTipCap) + } + + // FeeHistory + history, err := ec.FeeHistory(context.Background(), 1, big.NewInt(2), []float64{95, 99}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := ðereum.FeeHistory{ + OldestBlock: big.NewInt(2), + Reward: [][]*big.Int{ + { + big.NewInt(234375000), + big.NewInt(234375000), + }, + }, + BaseFee: []*big.Int{ + big.NewInt(765625000), + big.NewInt(671627818), + }, + GasUsedRatio: []float64{0.008912678667376286}, + } + if !reflect.DeepEqual(history, want) { + t.Fatalf("FeeHistory result doesn't match expected: (got: %v, want: %v)", history, want) + } +} + +func testCallContractAtHash(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // EstimateGas + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + 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) + } + block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1)) + if err != nil { + t.Fatalf("BlockByNumber error: %v", err) + } + // CallContract + if _, err := ec.CallContractAtHash(context.Background(), msg, block.Hash()); err != nil { + t.Fatalf("unexpected error: %v", err) + } } func testCallContract(t *testing.T, client *rpc.Client) { @@ -475,11 +562,10 @@ func testCallContract(t *testing.T, client *rpc.Client) { // EstimateGas msg := ethereum.CallMsg{ - From: testAddr, - To: &common.Address{}, - Gas: 21000, - GasPrice: big.NewInt(1), - Value: big.NewInt(1), + From: testAddr, + To: &common.Address{}, + Gas: 21000, + Value: big.NewInt(1), } gas, err := ec.EstimateGas(context.Background(), msg) if err != nil { @@ -492,62 +578,19 @@ func testCallContract(t *testing.T, client *rpc.Client) { if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil { t.Fatalf("unexpected error: %v", err) } - // PendingCallCOntract + // PendingCallContract if _, err := ec.PendingCallContract(context.Background(), msg); err != nil { t.Fatalf("unexpected error: %v", err) } } -// -//func TestCallNativeContract(t *testing.T) { -// backend, _ := newTestBackend(t) -// client, _ := backend.Attach() -// ec := NewClient(client) -// -// contractAddr := native.NativeContractAddrMap[native.NativeGovernance] -// // estimate native contract gas -// ab := governance.GetABI() -// name := governance.MethodAddValidator -// -// expectValidator := common.HexToAddress("0x12345") -// payload, err := utils.PackMethod(ab, name, expectValidator) -// if err != nil { -// t.Fatalf("pack native contract method err: %v", err) -// } -// msg := ethereum.CallMsg{ -// From: testAddr, -// To: &contractAddr, -// Gas: 0, -// GasPrice: nil, -// Value: nil, -// Data: payload, -// AccessList: nil, -// } -// gas, err := ec.EstimateGas(context.Background(), msg) -// if err != nil { -// t.Fatalf("estimate native contract call gas used err: %v", err) -// } -// if gas != 100000 { -// t.Fatalf("unexpected gas usage %d", gas) -// } else { -// t.Logf("gas estimated as %d", 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 { @@ -606,96 +649,67 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { } } -func sendTransaction(ec *Client) error { - // Retrieve chainID - chainID, err := ec.ChainID(context.Background()) +func testTransactionSender(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + ctx := context.Background() + + // Retrieve testTx1 via RPC. + block2, err := ec.HeaderByNumber(ctx, big.NewInt(2)) if err != nil { - return err + t.Fatal("can't get block 1:", err) } - // Create transaction - tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) - signer := types.LatestSignerForChainID(chainID) - signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) + tx1, err := ec.TransactionInBlock(ctx, block2.Hash(), 0) if err != nil { - return err + t.Fatal("can't get tx:", err) } - signedTx, err := tx.WithSignature(signer, signature) - if err != nil { - return err + if tx1.Hash() != testTx1.Hash() { + t.Fatalf("wrong tx hash %v, want %v", tx1.Hash(), testTx1.Hash()) } - // Send transaction - return ec.SendTransaction(context.Background(), signedTx) -} - -//func SigHash(header *types.Header) (hash common.Hash) { -// hasher := sha3.NewLegacyKeccak256() -// -// // Clean seal is required for calculating proposer seal. -// rlp.Encode(hasher, HotstuffFilteredHeader(header, false)) -// hasher.Sum(hash[:0]) -// return hash -//} -// -func dialNode(url string) *rpc.Client { - client, err := rpc.Dial(url) + // The sender address is cached in tx1, so no additional RPC should be required in + // TransactionSender. Ensure the server is not asked by canceling the context here. + canceledCtx, cancel := context.WithCancel(context.Background()) + cancel() + sender1, err := ec.TransactionSender(canceledCtx, tx1, block2.Hash(), 0) if err != nil { - panic(fmt.Sprintf("failed to dial geth rpc [%v]", err)) + t.Fatal(err) + } + if sender1 != testAddr { + t.Fatal("wrong sender:", sender1) } - return client -} - -func TestUnmarshalHeader(t *testing.T) { - url := "http://101.32.99.70:22002" - cli := NewClient(dialNode(url)) - - var ( - blockNum uint64 = 1099 - epochID uint64 = 5 - ) - block, err := cli.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNum)) + // Now try to get the sender of testTx2, which was not fetched through RPC. + // TransactionSender should query the server here. + sender2, err := ec.TransactionSender(ctx, testTx2, block2.Hash(), 1) if err != nil { t.Fatal(err) } - txs := block.Transactions() - for _, tx := range txs { - if blob, err := tx.MarshalJSON(); err != nil { - t.Fatal(err) - } else { - t.Logf("tx json data: %s", blob) - } + if sender2 != testAddr { + t.Fatal("wrong sender:", sender2) } - originHeader := block.Header() - originHeaderEnc, err := originHeader.MarshalJSON() +} + +func sendTransaction(ec *Client) error { + chainID, err := ec.ChainID(context.Background()) if err != nil { - t.Fatal(err) + return err } - extra, err := types.ExtractHotstuffExtraPayload(originHeader.Extra) + nonce, err := ec.PendingNonceAt(context.Background(), testAddr) if err != nil { - t.Fatal(err) + return err } - t.Logf("header extra: %s", extra.Dump()) - t.Logf("origin header json string: %s", string(originHeaderEnc)) - // cache db slot - contractAddr := utils.NodeManagerContractAddress - cacheKey := utils.ConcatKey(contractAddr, []byte(node_manager.SKP_EPOCH_INFO), big.NewInt(int64(epochID)).Bytes()) - slot := state.Key2Slot(cacheKey[common.AddressLength:]) - t.Logf("slot hex before keccak: %s", slot.Hex()) - - // storage key - key := hexutil.Encode(slot[:]) - storageKeys := []string{key} - t.Logf("slot hex after keccak: %s", key) + signer := types.LatestSignerForChainID(chainID) + tx, err := types.SignNewTx(testKey, signer, &types.LegacyTx{ + Nonce: nonce, + To: &common.Address{2}, + Value: big.NewInt(1), + Gas: 22000, + GasPrice: big.NewInt(params.InitialBaseFee), + }) - proof, err := cli.ProofAt(context.Background(), utils.NodeManagerContractAddress, storageKeys, new(big.Int).SetUint64(blockNum)) if err != nil { - t.Fatal(err) - } - enc, err := json.Marshal(proof) - if err != nil { - t.Fatal(err) + return err } - t.Logf("proof result: %s", string(enc)) + return ec.SendTransaction(context.Background(), tx) } diff --git a/ethclient/signer.go b/ethclient/signer.go index 9de020b3..f827d4eb 100644 --- a/ethclient/signer.go +++ b/ethclient/signer.go @@ -45,7 +45,7 @@ func (s *senderFromServer) Equal(other types.Signer) bool { } func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) { - if s.blockhash == (common.Hash{}) { + if s.addr == (common.Address{}) { return common.Address{}, errNotCached } return s.addr, nil diff --git a/event/event_test.go b/event/event_test.go index 5464dd53..13cfeb5b 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -51,60 +51,6 @@ func TestSub(t *testing.T) { } } -func TestConcurrentSub(t *testing.T) { - mux := new(TypeMux) - defer mux.Stop() - - type State uint8 - const ( - StateAcceptRequest State = 1 - StatePrepare State = 2 - StatePreCommit State = 3 - ) - type wrapState struct { - s State - } - var w = &wrapState{s: StateAcceptRequest} - - setState := func(st State) { - w.s = st - } - - n := 0 - const N int = 1200 - - exec := func(num int) int { - if num > N-3 && w.s < StatePrepare { - time.Sleep(2 * time.Millisecond) - setState(StatePrepare) - t.Logf("state is %v", w.s) - } - return num - } - - sub := mux.Subscribe(testEvent(0)) - go func() { - for { - select { - case _, ok := <-sub.Chan(): - if !ok { - t.Log("data is not ok") - } - n += 1 - _ = exec(n) - } - } - }() - - for i := 0; i < N; i++ { - go mux.Post(testEvent(i)) - } - for n < N-1 { - continue - } - t.Log(n) -} - func TestClosure(t *testing.T) { closure := func() func() int { n := 0 diff --git a/go.mod b/go.mod index 37fce166..df47bd68 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,9 @@ module github.com/ethereum/go-ethereum -go 1.16 +go 1.18 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.6.0 github.com/aws/aws-sdk-go-v2 v1.2.0 github.com/aws/aws-sdk-go-v2/config v1.1.1 @@ -18,16 +15,12 @@ require ( github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set 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.13.0 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/go-kit/kit v0.10.0 // indirect - 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.5.0 github.com/golang/snappy v0.0.3 @@ -44,10 +37,8 @@ require ( github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e github.com/julienschmidt/httprouter v1.3.0 github.com/karalabe/usb v0.0.2 - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.9 github.com/mattn/go-isatty v0.0.14 - 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 @@ -60,17 +51,48 @@ require ( github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 - github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tyler-smith/go-bip39 v1.0.2 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210903071746-97244b99971b golang.org/x/text v0.3.6 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 - google.golang.org/protobuf v1.26.0 // indirect + golang.org/x/tools v0.1.0 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 +) + +require ( + github.com/Azure/azure-pipeline-go v0.2.2 // indirect + github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect + github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.1.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.1.1 // indirect + github.com/aws/smithy-go v1.1.0 // indirect + github.com/bits-and-blooms/bitset v1.2.1 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/dlclark/regexp2 v1.2.0 // indirect + github.com/go-kit/kit v0.10.0 // indirect + github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/naoina/go-stringutil v0.1.0 // indirect + github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tklauser/go-sysconf v0.3.5 // indirect + github.com/tklauser/numcpus v0.2.2 // indirect + golang.org/x/mod v0.4.2 // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index 1f2a1861..4137eae6 100644 --- a/go.sum +++ b/go.sum @@ -115,7 +115,6 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH 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= @@ -136,7 +135,6 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/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/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -208,7 +206,6 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP 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/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/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= @@ -327,33 +324,9 @@ 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/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= -github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= -github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18= -github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY= -github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g= -github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e h1:FdDd7bdI6cjq5vaoYlK1mfQYfF9sF2VZw8VEZMsl5t8= -github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208 h1:/WiCm+Vpj87e4QWuWwPD/bNE9kDrWCLvPBHOQNcG2+A= -github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208/go.mod h1:0OChplkvPTZ174D2FYZXg4IB9hbEwyHkD+zT+/eK+Fg= -github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY= -github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= -github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= -github.com/juju/testing v0.0.0-20180402130637-44801989f0f7/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= -github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/juju/testing v0.0.0-20210324180055-18c50b0c2098 h1:yrhek184cGp0IRyHg0uV1khLaorNg6GtDLkry4oNNjE= -github.com/juju/testing v0.0.0-20210324180055-18c50b0c2098/go.mod h1:7lxZW0B50+xdGFkvhAb8bwAGt6IU87JB1H9w4t8MNVM= -github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= -github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= -github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI= -github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= -github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= -github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= -github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= @@ -373,11 +346,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= @@ -385,20 +356,13 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/masterzen/azure-sdk-for-go v3.2.0-beta.0.20161014135628-ee4f0065d00c+incompatible/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE= -github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= -github.com/masterzen/winrm v0.0.0-20161014151040-7a535cd943fc/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E= -github.com/masterzen/xmlpath v0.0.0-20140218185901-13f4951698ad/go.mod h1:A0zPC53iKKKcXYxr4ROjpQRQ5FgJXtelNdSmHHuq/tY= -github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 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.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -436,7 +400,6 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= @@ -585,7 +548,6 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/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= @@ -597,7 +559,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -628,8 +589,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -653,7 +614,6 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/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-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -710,8 +670,6 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -761,6 +719,7 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/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.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= 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= @@ -823,18 +782,12 @@ google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/l google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg= 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/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0= -gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 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= @@ -842,11 +795,9 @@ gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJ 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/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= 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/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 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= @@ -855,7 +806,6 @@ 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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= @@ -867,8 +817,6 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= -launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/graphql/graphiql.go b/graphql/graphiql.go index 864ebf57..576a0cbe 100644 --- a/graphql/graphiql.go +++ b/graphql/graphiql.go @@ -48,7 +48,7 @@ func errorJSON(msg string) []byte { } func (h GraphiQL) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { + if r.Method != http.MethodGet { respond(w, errorJSON("only GET requests are supported"), http.StatusMethodNotAllowed) return } diff --git a/graphql/graphql.go b/graphql/graphql.go index 12966c63..4bfbbe50 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -21,24 +21,27 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/common/math" "math/big" + "sort" "strconv" "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "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" ) var ( errBlockInvariant = errors.New("block objects must be instantiated with at least one of num or hash") + RPCEVMTimeout = 5 * time.Second ) type Long int64 @@ -66,6 +69,8 @@ func (b *Long) UnmarshalGraphQL(input interface{}) error { *b = Long(input) case int64: *b = Long(input) + case float64: + *b = Long(input) default: err = fmt.Errorf("unexpected type %T for Long", input) } @@ -74,14 +79,14 @@ func (b *Long) UnmarshalGraphQL(input interface{}) error { // Account represents an Ethereum account at a particular block. type Account struct { - backend ethapi.Backend + r *Resolver address common.Address blockNrOrHash rpc.BlockNumberOrHash } // getState fetches the StateDB object for an account. func (a *Account) getState(ctx context.Context) (*state.StateDB, error) { - state, _, err := a.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash) + state, _, err := a.r.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash) return state, err } @@ -94,10 +99,22 @@ func (a *Account) Balance(ctx context.Context) (hexutil.Big, error) { if err != nil { return hexutil.Big{}, err } - return hexutil.Big(*state.GetBalance(a.address)), nil + balance := state.GetBalance(a.address) + if balance == nil { + return hexutil.Big{}, fmt.Errorf("failed to load balance %x", a.address) + } + return hexutil.Big(*balance), nil } func (a *Account) TransactionCount(ctx context.Context) (hexutil.Uint64, error) { + // Ask transaction pool for the nonce which includes pending transactions + if blockNr, ok := a.blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber { + nonce, err := a.r.backend.GetPoolNonce(ctx, a.address) + if err != nil { + return 0, err + } + return hexutil.Uint64(nonce), nil + } state, err := a.getState(ctx) if err != nil { return 0, err @@ -123,7 +140,7 @@ func (a *Account) Storage(ctx context.Context, args struct{ Slot common.Hash }) // Log represents an individual log message. All arguments are mandatory. type Log struct { - backend ethapi.Backend + r *Resolver transaction *Transaction log *types.Log } @@ -134,7 +151,7 @@ func (l *Log) Transaction(ctx context.Context) *Transaction { func (l *Log) Account(ctx context.Context, args BlockNumberArgs) *Account { return &Account{ - backend: l.backend, + r: l.r, address: l.log.Address, blockNrOrHash: args.NumberOrLatest(), } @@ -155,42 +172,44 @@ func (l *Log) Data(ctx context.Context) hexutil.Bytes { // AccessTuple represents EIP-2930 type AccessTuple struct { address common.Address - storageKeys *[]common.Hash + storageKeys []common.Hash } func (at *AccessTuple) Address(ctx context.Context) common.Address { return at.address } -func (at *AccessTuple) StorageKeys(ctx context.Context) *[]common.Hash { +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 { - backend ethapi.Backend - hash common.Hash - tx *types.Transaction - block *Block - index uint64 + r *Resolver + hash common.Hash + tx *types.Transaction + block *Block + index uint64 } // resolve returns the internal transaction object, fetching it if needed. func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) { if t.tx == nil { - tx, blockHash, _, index := rawdb.ReadTransaction(t.backend.ChainDb(), t.hash) - if tx != nil { + // Try to return an already finalized transaction + tx, blockHash, _, index, err := t.r.backend.GetTransaction(ctx, t.hash) + if err == nil && tx != nil { t.tx = tx blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false) t.block = &Block{ - backend: t.backend, + r: t.r, numberOrHash: &blockNrOrHash, } t.index = index - } else { - t.tx = t.backend.GetPoolTransaction(t.hash) + return t.tx, nil } + // No finalized transaction, try to retrieve it from the pool + t.tx = t.r.backend.GetPoolTransaction(t.hash) } return t.tx, nil } @@ -241,6 +260,10 @@ func (t *Transaction) EffectiveGasPrice(ctx context.Context) (*hexutil.Big, erro if err != nil || tx == nil { return nil, err } + // Pending tx + if t.block == nil { + return nil, nil + } header, err := t.block.resolveHeader(ctx) if err != nil || header == nil { return nil, err @@ -281,11 +304,38 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, e } } +func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return nil, err + } + // Pending tx + if t.block == nil { + return nil, nil + } + header, err := t.block.resolveHeader(ctx) + if err != nil || header == nil { + return nil, err + } + if header.BaseFee == nil { + return (*hexutil.Big)(tx.GasPrice()), nil + } + + tip, err := tx.EffectiveGasTip(header.BaseFee) + if err != nil { + return nil, err + } + return (*hexutil.Big)(tip), nil +} + func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) { tx, err := t.resolve(ctx) if err != nil || tx == nil { return hexutil.Big{}, err } + if tx.Value() == nil { + return hexutil.Big{}, fmt.Errorf("invalid transaction value %x", t.hash) + } return hexutil.Big(*tx.Value()), nil } @@ -307,7 +357,7 @@ func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, e return nil, nil } return &Account{ - backend: t.backend, + r: t.r, address: *to, blockNrOrHash: args.NumberOrLatest(), }, nil @@ -318,10 +368,10 @@ func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, if err != nil || tx == nil { return nil, err } - signer := types.LatestSigner(t.backend.ChainConfig()) + signer := types.LatestSigner(t.r.backend.ChainConfig()) from, _ := types.Sender(signer, tx) return &Account{ - backend: t.backend, + r: t.r, address: from, blockNrOrHash: args.NumberOrLatest(), }, nil @@ -365,6 +415,9 @@ func (t *Transaction) Status(ctx context.Context) (*Long, error) { if err != nil || receipt == nil { return nil, err } + if len(receipt.PostState) != 0 { + return nil, nil + } ret := Long(receipt.Status) return &ret, nil } @@ -393,24 +446,51 @@ func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs) return nil, err } return &Account{ - backend: t.backend, + r: t.r, address: receipt.ContractAddress, blockNrOrHash: args.NumberOrLatest(), }, nil } func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) { - receipt, err := t.getReceipt(ctx) - if err != nil || receipt == nil { + if _, err := t.resolve(ctx); err != nil { return nil, err } - ret := make([]*Log, 0, len(receipt.Logs)) - for _, log := range receipt.Logs { + if t.block == nil { + return nil, nil + } + if _, ok := t.block.numberOrHash.Hash(); !ok { + header, err := t.r.backend.HeaderByNumberOrHash(ctx, *t.block.numberOrHash) + if err != nil { + return nil, err + } + hash := header.Hash() + t.block.numberOrHash.BlockHash = &hash + } + return t.getLogs(ctx) +} + +// getLogs returns log objects for the given tx. +// Assumes block hash is resolved. +func (t *Transaction) getLogs(ctx context.Context) (*[]*Log, error) { + var ( + hash, _ = t.block.numberOrHash.Hash() + filter = t.r.filterSystem.NewBlockFilter(hash, nil, nil) + logs, err = filter.Logs(ctx) + ) + if err != nil { + return nil, err + } + var ret []*Log + // Select tx logs from all block logs + ix := sort.Search(len(logs), func(i int) bool { return uint64(logs[i].TxIndex) >= t.index }) + for ix < len(logs) && uint64(logs[ix].TxIndex) == t.index { ret = append(ret, &Log{ - backend: t.backend, + r: t.r, transaction: t, - log: log, + log: logs[ix], }) + ix++ } return &ret, nil } @@ -434,7 +514,7 @@ func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) { for _, al := range accessList { ret = append(ret, &AccessTuple{ address: al.Address, - storageKeys: &al.StorageKeys, + storageKeys: al.StorageKeys, }) } return &ret, nil @@ -467,13 +547,29 @@ func (t *Transaction) V(ctx context.Context) (hexutil.Big, error) { return hexutil.Big(*v), nil } +func (t *Transaction) Raw(ctx context.Context) (hexutil.Bytes, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return hexutil.Bytes{}, err + } + return tx.MarshalBinary() +} + +func (t *Transaction) RawReceipt(ctx context.Context) (hexutil.Bytes, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return hexutil.Bytes{}, err + } + return receipt.MarshalBinary() +} + type BlockType int // Block represents an Ethereum block. // backend, and numberOrHash are mandatory. All other fields are lazily fetched // when required. type Block struct { - backend ethapi.Backend + r *Resolver numberOrHash *rpc.BlockNumberOrHash hash common.Hash header *types.Header @@ -492,7 +588,7 @@ func (b *Block) resolve(ctx context.Context) (*types.Block, error) { b.numberOrHash = &latest } var err error - b.block, err = b.backend.BlockByNumberOrHash(ctx, *b.numberOrHash) + b.block, err = b.r.backend.BlockByNumberOrHash(ctx, *b.numberOrHash) if b.block != nil && b.header == nil { b.header = b.block.Header() if hash, ok := b.numberOrHash.Hash(); ok { @@ -512,9 +608,9 @@ func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) { var err error if b.header == nil { if b.hash != (common.Hash{}) { - b.header, err = b.backend.HeaderByHash(ctx, b.hash) + b.header, err = b.r.backend.HeaderByHash(ctx, b.hash) } else { - b.header, err = b.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash) + b.header, err = b.r.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash) } } return b.header, err @@ -532,7 +628,7 @@ func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) { } hash = header.Hash() } - receipts, err := b.backend.GetReceipts(ctx, hash) + receipts, err := b.r.backend.GetReceipts(ctx, hash) if err != nil { return nil, err } @@ -588,22 +684,35 @@ func (b *Block) BaseFeePerGas(ctx context.Context) (*hexutil.Big, error) { return (*hexutil.Big)(header.BaseFee), nil } -func (b *Block) Parent(ctx context.Context) (*Block, error) { - // If the block header hasn't been fetched, and we'll need it, fetch it. - if b.numberOrHash == nil && b.header == nil { - if _, err := b.resolveHeader(ctx); err != nil { - return nil, err +func (b *Block) NextBaseFeePerGas(ctx context.Context) (*hexutil.Big, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + chaincfg := b.r.backend.ChainConfig() + if header.BaseFee == nil { + // Make sure next block doesn't enable EIP-1559 + if !chaincfg.IsLondon(new(big.Int).Add(header.Number, common.Big1)) { + return nil, nil } } - if b.header != nil && b.header.Number.Uint64() > 0 { - num := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(b.header.Number.Uint64() - 1)) - return &Block{ - backend: b.backend, - numberOrHash: &num, - hash: b.header.ParentHash, - }, nil + nextBaseFee := misc.CalcBaseFee(chaincfg, header) + return (*hexutil.Big)(nextBaseFee), nil +} + +func (b *Block) Parent(ctx context.Context) (*Block, error) { + if _, err := b.resolveHeader(ctx); err != nil { + return nil, err + } + if b.header == nil || b.header.Number.Uint64() < 1 { + return nil, nil } - return nil, nil + num := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(b.header.Number.Uint64() - 1)) + return &Block{ + r: b.r, + numberOrHash: &num, + hash: b.header.ParentHash, + }, nil } func (b *Block) Difficulty(ctx context.Context) (hexutil.Big, error) { @@ -688,7 +797,7 @@ func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) { for _, uncle := range block.Uncles() { blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false) ret = append(ret, &Block{ - backend: b.backend, + r: b.r, numberOrHash: &blockNumberOrHash, header: uncle, }) @@ -721,7 +830,27 @@ func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) { } h = header.Hash() } - return hexutil.Big(*b.backend.GetTd(ctx, h)), nil + td := b.r.backend.GetTd(ctx, h) + if td == nil { + return hexutil.Big{}, fmt.Errorf("total difficulty not found %x", b.hash) + } + return hexutil.Big(*td), nil +} + +func (b *Block) RawHeader(ctx context.Context) (hexutil.Bytes, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return rlp.EncodeToBytes(header) +} + +func (b *Block) Raw(ctx context.Context) (hexutil.Bytes, error) { + block, err := b.resolve(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return rlp.EncodeToBytes(block) } // BlockNumberArgs encapsulates arguments to accessors that specify a block number. @@ -754,7 +883,7 @@ func (b *Block) Miner(ctx context.Context, args BlockNumberArgs) (*Account, erro return nil, err } return &Account{ - backend: b.backend, + r: b.r, address: header.Coinbase, blockNrOrHash: args.NumberOrLatest(), }, nil @@ -777,11 +906,11 @@ func (b *Block) Transactions(ctx context.Context) (*[]*Transaction, error) { ret := make([]*Transaction, 0, len(block.Transactions())) for i, tx := range block.Transactions() { ret = append(ret, &Transaction{ - backend: b.backend, - hash: tx.Hash(), - tx: tx, - block: b, - index: uint64(i), + r: b.r, + hash: tx.Hash(), + tx: tx, + block: b, + index: uint64(i), }) } return &ret, nil @@ -798,11 +927,11 @@ func (b *Block) TransactionAt(ctx context.Context, args struct{ Index int32 }) ( } tx := txs[args.Index] return &Transaction{ - backend: b.backend, - hash: tx.Hash(), - tx: tx, - block: b, - index: uint64(args.Index), + r: b.r, + hash: tx.Hash(), + tx: tx, + block: b, + index: uint64(args.Index), }, nil } @@ -818,7 +947,7 @@ func (b *Block) OmmerAt(ctx context.Context, args struct{ Index int32 }) (*Block uncle := uncles[args.Index] blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false) return &Block{ - backend: b.backend, + r: b.r, numberOrHash: &blockNumberOrHash, header: uncle, }, nil @@ -845,7 +974,7 @@ type BlockFilterCriteria struct { // runFilter accepts a filter and executes it, returning all its results as // `Log` objects. -func runFilter(ctx context.Context, be ethapi.Backend, filter *filters.Filter) ([]*Log, error) { +func runFilter(ctx context.Context, r *Resolver, filter *filters.Filter) ([]*Log, error) { logs, err := filter.Logs(ctx) if err != nil || logs == nil { return nil, err @@ -853,8 +982,8 @@ func runFilter(ctx context.Context, be ethapi.Backend, filter *filters.Filter) ( ret := make([]*Log, 0, len(logs)) for _, log := range logs { ret = append(ret, &Log{ - backend: be, - transaction: &Transaction{backend: be, hash: log.TxHash}, + r: r, + transaction: &Transaction{r: r, hash: log.TxHash}, log: log, }) } @@ -879,10 +1008,10 @@ func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteri hash = header.Hash() } // Construct the range filter - filter := filters.NewBlockFilter(b.backend, hash, addresses, topics) + filter := b.r.filterSystem.NewBlockFilter(hash, addresses, topics) // Run the filter and return all the logs - return runFilter(ctx, b.backend, filter) + return runFilter(ctx, b.r, filter) } func (b *Block) Account(ctx context.Context, args struct { @@ -895,7 +1024,7 @@ func (b *Block) Account(ctx context.Context, args struct { } } return &Account{ - backend: b.backend, + r: b.r, address: args.Address, blockNrOrHash: *b.numberOrHash, }, nil @@ -942,7 +1071,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, 5*time.Second, b.backend.RPCGasCap()) + result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, RPCEVMTimeout, b.r.backend.RPCGasCap()) if err != nil { return nil, err } @@ -967,31 +1096,31 @@ func (b *Block) EstimateGas(ctx context.Context, args struct { return 0, err } } - gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.numberOrHash, b.backend.RPCGasCap()) + gas, err := ethapi.DoEstimateGas(ctx, b.r.backend, args.Data, *b.numberOrHash, b.r.backend.RPCGasCap()) return Long(gas), err } type Pending struct { - backend ethapi.Backend + r *Resolver } func (p *Pending) TransactionCount(ctx context.Context) (int32, error) { - txs, err := p.backend.GetPoolTransactions() + txs, err := p.r.backend.GetPoolTransactions() return int32(len(txs)), err } func (p *Pending) Transactions(ctx context.Context) (*[]*Transaction, error) { - txs, err := p.backend.GetPoolTransactions() + txs, err := p.r.backend.GetPoolTransactions() if err != nil { return nil, err } ret := make([]*Transaction, 0, len(txs)) for i, tx := range txs { ret = append(ret, &Transaction{ - backend: p.backend, - hash: tx.Hash(), - tx: tx, - index: uint64(i), + r: p.r, + hash: tx.Hash(), + tx: tx, + index: uint64(i), }) } return &ret, nil @@ -1002,7 +1131,7 @@ func (p *Pending) Account(ctx context.Context, args struct { }) *Account { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) return &Account{ - backend: p.backend, + r: p.r, address: args.Address, blockNrOrHash: pendingBlockNr, } @@ -1012,7 +1141,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, 5*time.Second, p.backend.RPCGasCap()) + result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, RPCEVMTimeout, p.r.backend.RPCGasCap()) if err != nil { return nil, err } @@ -1031,14 +1160,15 @@ func (p *Pending) Call(ctx context.Context, args struct { func (p *Pending) EstimateGas(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (Long, error) { - latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) - gas, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, latestBlockNr, p.backend.RPCGasCap()) + pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + gas, err := ethapi.DoEstimateGas(ctx, p.r.backend, args.Data, pendingBlockNr, p.r.backend.RPCGasCap()) return Long(gas), err } // Resolver is the top-level object in the GraphQL hierarchy. type Resolver struct { - backend ethapi.Backend + backend ethapi.Backend + filterSystem *filters.FilterSystem } func (r *Resolver) Block(ctx context.Context, args struct { @@ -1053,19 +1183,19 @@ func (r *Resolver) Block(ctx context.Context, args struct { number := rpc.BlockNumber(*args.Number) numberOrHash := rpc.BlockNumberOrHashWithNumber(number) block = &Block{ - backend: r.backend, + r: r, numberOrHash: &numberOrHash, } } else if args.Hash != nil { numberOrHash := rpc.BlockNumberOrHashWithHash(*args.Hash, false) block = &Block{ - backend: r.backend, + r: r, numberOrHash: &numberOrHash, } } else { numberOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) block = &Block{ - backend: r.backend, + r: r, numberOrHash: &numberOrHash, } } @@ -1099,22 +1229,33 @@ func (r *Resolver) Blocks(ctx context.Context, args struct { ret := make([]*Block, 0, to-from+1) for i := from; i <= to; i++ { numberOrHash := rpc.BlockNumberOrHashWithNumber(i) - ret = append(ret, &Block{ - backend: r.backend, + block := &Block{ + r: r, numberOrHash: &numberOrHash, - }) + } + // Resolve the header to check for existence. + // Note we don't resolve block directly here since it will require an + // additional network request for light client. + h, err := block.resolveHeader(ctx) + if err != nil { + return nil, err + } else if h == nil { + // Blocks after must be non-existent too, break. + break + } + ret = append(ret, block) } return ret, nil } func (r *Resolver) Pending(ctx context.Context) *Pending { - return &Pending{r.backend} + return &Pending{r} } func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Hash }) (*Transaction, error) { tx := &Transaction{ - backend: r.backend, - hash: args.Hash, + r: r, + hash: args.Hash, } // Resolve the transaction; if it doesn't exist, return nil. t, err := tx.resolve(ctx) @@ -1174,8 +1315,8 @@ func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria topics = *args.Filter.Topics } // Construct the range filter - filter := filters.NewRangeFilter(filters.Backend(r.backend), begin, end, addresses, topics) - return runFilter(ctx, r.backend, filter) + filter := r.filterSystem.NewRangeFilter(begin, end, addresses, topics) + return runFilter(ctx, r, filter) } func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) { @@ -1209,32 +1350,30 @@ type SyncState struct { func (s *SyncState) StartingBlock() hexutil.Uint64 { return hexutil.Uint64(s.progress.StartingBlock) } - func (s *SyncState) CurrentBlock() hexutil.Uint64 { return hexutil.Uint64(s.progress.CurrentBlock) } - func (s *SyncState) HighestBlock() hexutil.Uint64 { return hexutil.Uint64(s.progress.HighestBlock) } -func (s *SyncState) PulledStates() *hexutil.Uint64 { - ret := hexutil.Uint64(s.progress.PulledStates) - return &ret -} - -func (s *SyncState) KnownStates() *hexutil.Uint64 { - ret := hexutil.Uint64(s.progress.KnownStates) - return &ret -} - // 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 -// - currentBlock: block number this node is currently importing -// - highestBlock: block number of the highest block header this node has received from peers -// - pulledStates: number of state entries processed until now -// - knownStates: number of known state entries that still need to be pulled +// - startingBlock: block number this node started to synchronise from +// - currentBlock: block number this node is currently importing +// - highestBlock: block number of the highest block header this node has received from peers +// - syncedAccounts: number of accounts downloaded +// - syncedAccountBytes: number of account trie bytes persisted to disk +// - syncedBytecodes: number of bytecodes downloaded +// - syncedBytecodeBytes: number of bytecode bytes downloaded +// - syncedStorage: number of storage slots downloaded +// - syncedStorageBytes: number of storage trie bytes persisted to disk +// - healedTrienodes: number of state trie nodes downloaded +// - healedTrienodeBytes: number of state trie bytes persisted to disk +// - healedBytecodes: number of bytecodes downloaded +// - healedBytecodeBytes: number of bytecodes persisted to disk +// - healingTrienodes: number of state trie nodes pending +// - healingBytecode: number of bytecodes pending func (r *Resolver) Syncing() (*SyncState, error) { progress := r.backend.Downloader().Progress() diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 2404ff45..8614cddd 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -17,8 +17,10 @@ package graphql import ( + "context" + "encoding/json" "fmt" - "io/ioutil" + "io" "math/big" "net/http" "strings" @@ -33,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -40,10 +43,7 @@ import ( ) func TestBuildSchema(t *testing.T) { - ddir, err := ioutil.TempDir("", "graphql-buildschema") - if err != nil { - t.Fatalf("failed to create temporary datadir: %v", err) - } + ddir := t.TempDir() // Copy config conf := node.DefaultConfig conf.DataDir = ddir @@ -51,16 +51,24 @@ func TestBuildSchema(t *testing.T) { if err != nil { t.Fatalf("could not create new node: %v", err) } + defer stack.Close() // Make sure the schema can be parsed and matched up to the object model. - if err := newHandler(stack, nil, []string{}, []string{}); err != nil { + if _, err := newHandler(stack, nil, nil, []string{}, []string{}); err != nil { t.Errorf("Could not construct GraphQL handler: %v", err) } } // 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, false) + stack := createNode(t) defer stack.Close() + genesis := &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: big.NewInt(1048576), + CommunityRate: big.NewInt(20), + } + newGQLService(t, stack, genesis, 10, func(i int, gen *core.BlockGen) {}) // start node if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) @@ -148,7 +156,7 @@ func TestGraphQLBlockSerialization(t *testing.T) { if err != nil { t.Fatalf("could not post: %v", err) } - bodyBytes, err := ioutil.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("could not read from response body: %v", err) } @@ -162,8 +170,57 @@ func TestGraphQLBlockSerialization(t *testing.T) { } func TestGraphQLBlockSerializationEIP2718(t *testing.T) { - stack := createNode(t, true, true) + // Account for signing txes + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + dad = common.HexToAddress("0x0000000000000000000000000000000000000dad") + ) + stack := createNode(t) defer stack.Close() + 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), + }, + }, + BaseFee: big.NewInt(params.InitialBaseFee), + CommunityRate: big.NewInt(20), + } + core.CheckAllocWithTotalSupply = false + signer := types.LatestSigner(genesis.Config) + newGQLService(t, stack, genesis, 1, func(i int, gen *core.BlockGen) { + gen.SetCoinbase(common.Address{1}) + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: uint64(0), + To: &dad, + Value: big.NewInt(100), + Gas: 50000, + GasPrice: big.NewInt(params.InitialBaseFee), + }) + gen.AddTx(tx) + tx, _ = types.SignNewTx(key, signer, &types.AccessListTx{ + ChainID: genesis.Config.ChainID, + Nonce: uint64(1), + To: &dad, + Gas: 30000, + GasPrice: big.NewInt(params.InitialBaseFee), + Value: big.NewInt(50), + AccessList: types.AccessList{{ + Address: dad, + StorageKeys: []common.Hash{{0}}, + }}, + }) + gen.AddTx(tx) + }) // start node if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) @@ -176,7 +233,7 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) { }{ { 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}]}}}`, + want: `{"data":{"block":{"number":1,"transactions":[{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x64","hash":"0xd864c9d7d37fade6b70164740540c06dd58bb9c3f6b46101908d6339db6a6a7b","type":0,"accessList":[],"index":0},{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x32","hash":"0x19b35f8187b4e15fb59a9af469dca5dfa3cd363c11d372058c12f6482477b474","type":1,"accessList":[{"address":"0x0000000000000000000000000000000000000dad","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000"]}],"index":1}]}}}`, code: 200, }, } { @@ -184,7 +241,7 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) { if err != nil { t.Fatalf("could not post: %v", err) } - bodyBytes, err := ioutil.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("could not read from response body: %v", err) } @@ -199,7 +256,7 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) { // 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, false) + stack := createNode(t) defer stack.Close() if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) @@ -213,91 +270,77 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { assert.Equal(t, http.StatusNotFound, resp.StatusCode) } -func createNode(t *testing.T, gqlEnabled bool, txEnabled bool) *node.Node { - stack, err := node.New(&node.Config{ - HTTPHost: "127.0.0.1", - HTTPPort: 0, - WSHost: "127.0.0.1", - WSPort: 0, - }) - if err != nil { - t.Fatalf("could not create node: %v", err) - } - if !gqlEnabled { - return stack - } - if !txEnabled { - createGQLService(t, stack) - } else { - createGQLServiceWithTransactions(t, stack) - } - return stack -} - -func createGQLService(t *testing.T, stack *node.Node) { - // create backend - ethConf := ðconfig.Config{ - Genesis: &core.Genesis{ +func TestGraphQLTransactionLogs(t *testing.T) { + var ( + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + dadStr = "0x0000000000000000000000000000000000000dad" + dad = common.HexToAddress(dadStr) + genesis = &core.Genesis{ Config: params.AllEthashProtocolChanges, GasLimit: 11500000, Difficulty: big.NewInt(1048576), - }, - Ethash: ethash.Config{ - PowMode: ethash.ModeFake, - }, - NetworkId: 1337, - TrieCleanCache: 5, - TrieCleanCacheJournal: "triecache", - TrieCleanCacheRejournal: 60 * time.Minute, - TrieDirtyCache: 5, - TrieTimeout: 60 * time.Minute, - SnapshotCache: 5, + CommunityRate: big.NewInt(20), + Alloc: core.GenesisAlloc{ + addr: {Balance: big.NewInt(params.Ether)}, + dad: { + // LOG0(0, 0), LOG0(0, 0), RETURN(0, 0) + Code: common.Hex2Bytes("60006000a060006000a060006000f3"), + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + signer = types.LatestSigner(genesis.Config) + stack = createNode(t) + ) + defer stack.Close() + + handler := newGQLService(t, stack, genesis, 1, func(i int, gen *core.BlockGen) { + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) + gen.AddTx(tx) + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Nonce: 1, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) + gen.AddTx(tx) + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Nonce: 2, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) + gen.AddTx(tx) + }) + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) } - ethBackend, err := eth.New(stack, ethConf) - if err != nil { - t.Fatalf("could not create eth backend: %v", err) + query := `{block { transactions { logs { account { address } } } } }` + res := handler.Schema.Exec(context.Background(), query, "", map[string]interface{}{}) + if res.Errors != nil { + t.Fatalf("graphql query failed: %v", res.Errors) } - // 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) + have, err := json.Marshal(res.Data) if err != nil { - t.Fatalf("could not create import blocks: %v", err) + t.Fatalf("failed to encode graphql response: %s", err) } - // create gql service - err = New(stack, ethBackend.APIBackend, []string{}, []string{}) - if err != nil { - t.Fatalf("could not create graphql service: %v", err) + want := fmt.Sprintf(`{"block":{"transactions":[{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]},{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]},{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]}]}}`, dadStr, dadStr, dadStr, dadStr, dadStr, dadStr) + if string(have) != want { + t.Errorf("response unmatch. expected %s, got %s", want, have) } } -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") +func createNode(t *testing.T) *node.Node { + core.CheckAllocWithTotalSupply = false + stack, err := node.New(&node.Config{ + HTTPHost: "127.0.0.1", + HTTPPort: 0, + WSHost: "127.0.0.1", + WSPort: 0, + HTTPTimeouts: node.DefaultConfig.HTTPTimeouts, + }) + if err != nil { + t.Fatalf("could not create node: %v", err) + } + return stack +} +func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlocks int, genfunc func(i int, gen *core.BlockGen)) *handler { 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), - }, - }, - }, + Genesis: gspec, Ethash: ethash.Config{ PowMode: ethash.ModeFake, }, @@ -309,48 +352,22 @@ func createGQLServiceWithTransactions(t *testing.T, stack *node.Node) { 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) - }) - + ethash.NewFaker(), ethBackend.ChainDb(), genBlocks, genfunc) _, 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{}) + // Set up handler + filterSystem := filters.NewFilterSystem(ethBackend.APIBackend, filters.Config{}) + handler, err := newHandler(stack, ethBackend.APIBackend, filterSystem, []string{}, []string{}) if err != nil { t.Fatalf("could not create graphql service: %v", err) } + return handler } diff --git a/graphql/schema.go b/graphql/schema.go index 4518c544..ff3919be 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -69,10 +69,10 @@ const schema string = ` transaction: Transaction! } - #EIP-2718 + #EIP-2718 type AccessTuple{ address: Address! - storageKeys : [Bytes32!] + storageKeys : [Bytes32!]! } # Transaction is an Ethereum transaction. @@ -94,10 +94,12 @@ const schema string = ` value: BigInt! # GasPrice is the price offered to miners for gas, in wei per unit. gasPrice: BigInt! - # MaxFeePerGas is the maximum fee per gas offered to include a transaction, in wei. - maxFeePerGas: BigInt - # MaxPriorityFeePerGas is the maximum miner tip per gas offered to include a transaction, in wei. - maxPriorityFeePerGas: BigInt + # MaxFeePerGas is the maximum fee per gas offered to include a transaction, in wei. + maxFeePerGas: BigInt + # MaxPriorityFeePerGas is the maximum miner tip per gas offered to include a transaction, in wei. + maxPriorityFeePerGas: BigInt + # EffectiveTip is the actual amount of reward going to miner after considering the max fee cap. + effectiveTip: BigInt # Gas is the maximum amount of gas this transaction can consume. gas: Long! # InputData is the data supplied to the target of the transaction. @@ -114,13 +116,11 @@ const schema string = ` # GasUsed is the amount of gas that was used processing this transaction. # If the transaction has not yet been mined, this field will be null. gasUsed: Long - # BaseFeePerGas is the fee perunit of gas burned by the protocol in this block. - baseFeePerGas: BigInt # CumulativeGasUsed is the total gas used in the block up to and including # this transaction. If the transaction has not yet been mined, this field # will be null. cumulativeGasUsed: Long - # EffectiveGasPrice is actual value per gas deducted from the sender's + # EffectiveGasPrice is actual value per gas deducted from the sender's # account. Before EIP-1559, this is equal to the transaction's gas price. # After EIP-1559, it is baseFeePerGas + min(maxFeePerGas - baseFeePerGas, # maxPriorityFeePerGas). Legacy transactions and EIP-2930 transactions are @@ -137,9 +137,16 @@ const schema string = ` r: BigInt! s: BigInt! v: BigInt! - #Envelope transaction support + # Envelope transaction support type: Int accessList: [AccessTuple!] + # Raw is 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. + raw: Bytes! + # RawReceipt is the canonical encoding of the receipt. For post EIP-2718 typed transactions + # this is equivalent to TxType || ReceiptEncoding. + rawReceipt: Bytes! } # BlockFilterCriteria encapsulates log filter criteria for a filter applied @@ -189,8 +196,10 @@ const schema string = ` gasLimit: Long! # GasUsed is the amount of gas that was used executing transactions in this block. gasUsed: Long! - # BaseFeePerGas is the fee perunit of gas burned by the protocol in this block. - baseFeePerGas: BigInt + # BaseFeePerGas is the fee per unit of gas burned by the protocol in this block. + baseFeePerGas: BigInt + # NextBaseFeePerGas is the fee per unit of gas which needs to be burned in the next block. + nextBaseFeePerGas: BigInt # Timestamp is the unix timestamp at which this block was mined. timestamp: Long! # LogsBloom is a bloom filter that can be used to check if a block may @@ -233,6 +242,10 @@ const schema string = ` # EstimateGas estimates the amount of gas that will be required for # successful execution of a transaction at the current block's state. estimateGas(data: CallData!): Long! + # RawHeader is the RLP encoding of the block's header. + rawHeader: Bytes! + # Raw is the RLP encoding of the block. + raw: Bytes! } # CallData represents the data associated with a local contract call. @@ -246,10 +259,10 @@ const schema string = ` gas: Long # GasPrice is the price, in wei, offered for each unit of gas. gasPrice: BigInt - # MaxFeePerGas is the maximum fee per gas offered, in wei. - maxFeePerGas: BigInt - # MaxPriorityFeePerGas is the maximum miner tip per gas offered, in wei. - maxPriorityFeePerGas: BigInt + # MaxFeePerGas is the maximum fee per gas offered, in wei. + maxFeePerGas: BigInt + # MaxPriorityFeePerGas is the maximum miner tip per gas offered, in wei. + maxPriorityFeePerGas: BigInt # Value is the value, in wei, sent along with the call. value: BigInt # Data is the data sent to the callee. @@ -299,12 +312,6 @@ const schema string = ` currentBlock: Long! # HighestBlock is the latest known block number. highestBlock: Long! - # PulledStates is the number of state entries fetched so far, or null - # if this is not known or not relevant. - pulledStates: Long - # KnownStates is the number of states the node knows of so far, or null - # if this is not known or not relevant. - knownStates: Long } # Pending represents the current pending state. @@ -338,7 +345,7 @@ 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! - # MaxPriorityFeePerGas returns the node's estimate of a gas tip sufficient + # MaxPriorityFeePerGas returns the node's estimate of a gas tip sufficient # to ensure a transaction is mined in a timely fashion. maxPriorityFeePerGas: BigInt! # Syncing returns information on the current synchronisation state. diff --git a/graphql/service.go b/graphql/service.go index bcb0a499..e2214083 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,9 +17,12 @@ package graphql import ( + "context" "encoding/json" "net/http" + "time" + "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/node" "github.com/graph-gophers/graphql-go" @@ -40,7 +43,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - response := h.Schema.Exec(r.Context(), params.Query, params.OperationName, params.Variables) + ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second) + defer cancel() + + response := h.Schema.Exec(ctx, params.Query, params.OperationName, params.Variables) responseJSON, err := json.Marshal(response) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -52,26 +58,22 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 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 { - panic("missing backend") - } - // check if http server with given endpoint exists and enable graphQL on it - return newHandler(stack, backend, cors, vhosts) +func New(stack *node.Node, backend ethapi.Backend, filterSystem *filters.FilterSystem, cors, vhosts []string) error { + _, err := newHandler(stack, backend, filterSystem, cors, vhosts) + return err } // newHandler returns a new `http.Handler` that will answer GraphQL queries. // It additionally exports an interactive query browser on the / endpoint. -func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error { - q := Resolver{backend} +func newHandler(stack *node.Node, backend ethapi.Backend, filterSystem *filters.FilterSystem, cors, vhosts []string) (*handler, error) { + q := Resolver{backend, filterSystem} s, err := graphql.ParseSchema(schema, &q) if err != nil { - return err + return nil, err } h := handler{Schema: s} handler := node.NewHTTPHandlerStack(h, cors, vhosts) @@ -80,5 +82,5 @@ func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) stack.RegisterHandler("GraphQL", "/graphql", handler) stack.RegisterHandler("GraphQL", "/graphql/", handler) - return nil + return &h, nil } diff --git a/interfaces.go b/interfaces.go index b9d0bb88..2c806ff7 100644 --- a/interfaces.go +++ b/interfaces.go @@ -182,6 +182,15 @@ type GasPricer interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) } +// FeeHistory provides recent fee market data that consumers can use to determine +// a reasonable maxPriorityFeePerGas value. +type FeeHistory struct { + OldestBlock *big.Int // block corresponding to first response value + Reward [][]*big.Int // list every txs priority fee per block + BaseFee []*big.Int // list of each block's base fee + GasUsedRatio []float64 // ratio of gas used out of the total available limit +} + // A PendingStateReader provides access to the pending state, which is the result of all // known executable transactions which have not yet been included in the blockchain. It is // commonly used to display the result of ’unconfirmed’ actions (e.g. wallet value diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index dfb84921..1005d941 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -82,19 +82,20 @@ func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil. } type feeHistoryResult struct { - OldestBlock rpc.BlockNumber `json:"oldestBlock"` + OldestBlock *hexutil.Big `json:"oldestBlock"` Reward [][]*hexutil.Big `json:"reward,omitempty"` BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"` GasUsedRatio []float64 `json:"gasUsedRatio"` } -func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) { - oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) +// FeeHistory returns the fee market history. +func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) { + oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, int(blockCount), lastBlock, rewardPercentiles) if err != nil { return nil, err } results := &feeHistoryResult{ - OldestBlock: oldest, + OldestBlock: (*hexutil.Big)(oldest), GasUsedRatio: gasUsed, } if reward != nil { @@ -1365,6 +1366,17 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber return result } +// NewRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation +func NewRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction { + var baseFee *big.Int + blockNumber := uint64(0) + if current != nil { + baseFee = misc.CalcBaseFee(config, current) + blockNumber = current.Number.Uint64() + } + return newRPCTransaction(tx, common.Hash{}, blockNumber, 0, baseFee) +} + // newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation func newRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction { var baseFee *big.Int diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index fe55ec59..95224158 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -42,7 +42,7 @@ type Backend interface { // General Ethereum API Downloader() *downloader.Downloader SuggestGasTipCap(ctx context.Context) (*big.Int, error) - FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (rpc.BlockNumber, [][]*big.Int, []*big.Int, []float64, error) + FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool @@ -62,6 +62,7 @@ type Backend interface { BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) + PendingBlockAndReceipts() (*types.Block, types.Receipts) 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, vmConfig *vm.Config) (*vm.EVM, func() error, error) @@ -82,7 +83,10 @@ type Backend interface { // Filter API BloomStatus() (uint64, uint64) - GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) + // This is copied from filters.Backend + // eth/filters needs to be initialized from this backend type, so methods needed by + // it must also be included here. + GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription diff --git a/les/api_backend.go b/les/api_backend.go index 6da08259..d3615d78 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -161,11 +161,8 @@ func (b *LesApiBackend) GetReceipts(ctx context.Context, hash common.Hash) (type return nil, nil } -func (b *LesApiBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { - if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { - return light.GetBlockLogs(ctx, b.eth.odr, hash, *number) - } - return nil, nil +func (b *LesApiBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { + return light.GetBlockLogs(ctx, b.eth.odr, hash, number) } func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { @@ -263,7 +260,7 @@ func (b *LesApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) return b.gpo.SuggestTipCap(ctx) } -func (b *LesApiBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock rpc.BlockNumber, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) { +func (b *LesApiBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) { return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) } diff --git a/les/client.go b/les/client.go index 1d8a2c6f..9b6cebee 100644 --- a/les/client.go +++ b/les/client.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "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" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -298,11 +297,6 @@ func (s *LightEthereum) APIs() []rpc.API { Version: "1.0", Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux), Public: true, - }, { - Namespace: "eth", - Version: "1.0", - Service: filters.NewPublicFilterAPI(s.ApiBackend, true, 5*time.Minute), - Public: true, }, { Namespace: "net", Version: "1.0", diff --git a/les/fetcher_test.go b/les/fetcher_test.go index d3a74d25..442901ee 100644 --- a/les/fetcher_test.go +++ b/les/fetcher_test.go @@ -66,6 +66,8 @@ func TestSequentialAnnouncementsLes2(t *testing.T) { testSequentialAnnouncements func TestSequentialAnnouncementsLes3(t *testing.T) { testSequentialAnnouncements(t, 3) } func testSequentialAnnouncements(t *testing.T, protocol int) { + core.CheckAllocWithTotalSupply = false + netconfig := testnetConfig{ blocks: 4, protocol: protocol, diff --git a/les/handler_test.go b/les/handler_test.go index d1dbee6b..bb8ad338 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -370,7 +370,7 @@ func testGetReceipt(t *testing.T, protocol int) { block := bc.GetBlockByNumber(i) hashes = append(hashes, block.Hash()) - receipts = append(receipts, rawdb.ReadRawReceipts(server.db, block.Hash(), block.NumberU64())) + receipts = append(receipts, rawdb.ReadReceipts(server.db, block.Hash(), block.NumberU64(), bc.Config())) } // Send the hash request and verify the response sendRequest(rawPeer.app, GetReceiptsMsg, 42, hashes) diff --git a/les/peer_test.go b/les/peer_test.go index d6551ce6..7ae2cd9f 100644 --- a/les/peer_test.go +++ b/les/peer_test.go @@ -105,6 +105,7 @@ func (f *fakeChain) Genesis() *types.Block { func (f *fakeChain) CurrentHeader() *types.Header { return &types.Header{Number: big.NewInt(10000000)} } func TestHandshake(t *testing.T) { + core.CheckAllocWithTotalSupply = false // Create a message pipe to communicate through app, net := p2p.MsgPipe() diff --git a/les/test_helper.go b/les/test_helper.go index fc85ed95..90ef9dc3 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -123,7 +123,7 @@ func prepare(n int, backend *backends.SimulatedBackend) { // 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) + tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(100000000000000000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey) backend.SendTransaction(ctx, tx) case 1: // Builtin-block @@ -134,20 +134,20 @@ func prepare(n int, backend *backends.SimulatedBackend) { userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1) // bankUser transfers more ether to user1 - tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1000), params.TxGas, nil, nil), signer, bankKey) + tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey) backend.SendTransaction(ctx, tx1) // user1 relays ether to user2 - tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1000), params.TxGas, nil, nil), signer, userKey1) + tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, userKey1) backend.SendTransaction(ctx, tx2) // user1 deploys a test contract - tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, userKey1) + tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(params.InitialBaseFee), testContractCode), signer, userKey1) backend.SendTransaction(ctx, tx3) testContractAddr = crypto.CreateAddress(userAddr1, userNonce1+1) // user1 deploys a event contract - tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, userKey1) + tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(params.InitialBaseFee), testEventEmitterCode), signer, userKey1) backend.SendTransaction(ctx, tx4) case 2: // Builtin-block @@ -156,12 +156,12 @@ func prepare(n int, backend *backends.SimulatedBackend) { // 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) + tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey) backend.SendTransaction(ctx, tx1) // invoke test contract data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") - tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey) + tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, big.NewInt(params.InitialBaseFee), data), signer, bankKey) backend.SendTransaction(ctx, tx2) case 3: // Builtin-block @@ -171,7 +171,7 @@ func prepare(n int, backend *backends.SimulatedBackend) { // invoke test contract bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") - tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey) + tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, big.NewInt(params.InitialBaseFee), data), signer, bankKey) backend.SendTransaction(ctx, tx) } backend.Commit() @@ -196,6 +196,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index gspec = core.Genesis{ Config: params.AllEthashProtocolChanges, Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, + CommunityRate: big.NewInt(20), GasLimit: 100000000, } oracle *checkpointoracle.CheckpointOracle @@ -256,9 +257,11 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da Config: params.AllEthashProtocolChanges, Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, GasLimit: 100000000, + CommunityRate: big.NewInt(20), } oracle *checkpointoracle.CheckpointOracle ) + core.CheckAllocWithTotalSupply = false genesis := gspec.MustCommit(db) // create a simulation backend and pre-commit several customized block to the database. diff --git a/light/lightchain_test.go b/light/lightchain_test.go index 2aed08d7..58fefe7d 100644 --- a/light/lightchain_test.go +++ b/light/lightchain_test.go @@ -53,6 +53,7 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [ // chain. Depending on the full flag, if creates either a full block chain or a // header only chain. func newCanonical(n int) (ethdb.Database, *LightChain, error) { + core.CheckAllocWithTotalSupply = false db := rawdb.NewMemoryDatabase() gspec := core.Genesis{Config: params.TestChainConfig} genesis := gspec.MustCommit(db) diff --git a/light/trie_test.go b/light/trie_test.go index 052194b4..8deed76a 100644 --- a/light/trie_test.go +++ b/light/trie_test.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "fmt" + "math/big" "testing" "github.com/davecgh/go-spew/spew" @@ -33,10 +34,11 @@ import ( ) func TestNodeIterator(t *testing.T) { + core.CheckAllocWithTotalSupply = false var ( fulldb = rawdb.NewMemoryDatabase() lightdb = rawdb.NewMemoryDatabase() - gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} + gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, CommunityRate: big.NewInt(20), BaseFee: big.NewInt(params.InitialBaseFee)} genesis = gspec.MustCommit(fulldb) ) gspec.MustCommit(lightdb) diff --git a/light/txpool_test.go b/light/txpool_test.go index 39d5afe5..6a31ff0b 100644 --- a/light/txpool_test.go +++ b/light/txpool_test.go @@ -76,14 +76,15 @@ func txPoolTestChainGen(i int, block *core.BlockGen) { } func TestTxPool(t *testing.T) { + core.CheckAllocWithTotalSupply = false for i := range testTx { - testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey) + testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) } var ( sdb = rawdb.NewMemoryDatabase() ldb = rawdb.NewMemoryDatabase() - gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} + gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, BaseFee: big.NewInt(params.InitialBaseFee), CommunityRate: big.NewInt(20)} genesis = gspec.MustCommit(sdb) ) gspec.MustCommit(ldb) diff --git a/miner/miner_test.go b/miner/miner_test.go index fc724b88..6e2f3843 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -83,6 +83,7 @@ func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) } func TestMiner(t *testing.T) { + core.CheckAllocWithTotalSupply = false miner, mux := createMiner(t) miner.Start(common.HexToAddress("0x12345")) waitForMiningState(t, miner, true) diff --git a/miner/worker.go b/miner/worker.go index 71832512..5c1270cc 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -875,18 +875,16 @@ func (w *worker) commitNewWork(interrupt *int32, timestamp int64) { header := &types.Header{ ParentHash: parent.Hash(), Number: num.Add(num, common.Big1), - GasLimit: core.CalcGasLimit(parent.GasUsed(), parent.GasLimit(), w.config.GasFloor, w.config.GasCeil), + GasLimit: parent.GasLimit(), 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 + parentGasLimit := parent.GasLimit() * w.chainConfig.ElasticityMultiplier() + header.GasLimit = core.CalcGasLimit(parentGasLimit, parentGasLimit) } - header.GasLimit = core.CalcGasLimit1559(parentGasLimit, w.config.GasCeil) } // Could potentially happen if starting to mine in an odd state. diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go index f2a4bed4..47951cb0 100644 --- a/p2p/dnsdisc/client.go +++ b/p2p/dnsdisc/client.go @@ -19,6 +19,7 @@ package dnsdisc import ( "bytes" "context" + "errors" "fmt" "math/rand" "net" @@ -26,12 +27,12 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common/lru" "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/enode" "github.com/ethereum/go-ethereum/p2p/enr" - lru "github.com/hashicorp/golang-lru" "golang.org/x/sync/singleflight" "golang.org/x/time/rate" ) @@ -40,7 +41,7 @@ import ( type Client struct { cfg Config clock mclock.Clock - entries *lru.Cache + entries *lru.Cache[string, entry] ratelimit *rate.Limiter singleflight singleflight.Group } @@ -95,14 +96,10 @@ func (cfg Config) withDefaults() Config { // NewClient creates a client. func NewClient(cfg Config) *Client { cfg = cfg.withDefaults() - cache, err := lru.New(cfg.CacheLimit) - if err != nil { - panic(err) - } rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10) return &Client{ cfg: cfg, - entries: cache, + entries: lru.NewCache[string, entry](cfg.CacheLimit), clock: mclock.System{}, ratelimit: rlimit, } @@ -175,7 +172,7 @@ func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, } cacheKey := truncateHash(hash) if e, ok := c.entries.Get(cacheKey); ok { - return e.(entry), nil + return e, nil } ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) { @@ -204,7 +201,7 @@ func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry } for _, txt := range txts { e, err := parseEntry(txt, c.cfg.ValidSchemes) - if err == errUnknownEntry { + if errors.Is(err, errUnknownEntry) { continue } if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) { @@ -281,7 +278,7 @@ func (it *randomIterator) nextNode() *enode.Node { } n, err := ct.syncRandom(it.ctx) if err != nil { - if err == it.ctx.Err() { + if errors.Is(err, it.ctx.Err()) { return nil // context canceled. } it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err) @@ -298,6 +295,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 741bee42..6de7fead 100644 --- a/p2p/dnsdisc/client_test.go +++ b/p2p/dnsdisc/client_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The go-ethereum Authors +// 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 @@ -20,12 +20,12 @@ import ( "context" "crypto/ecdsa" "errors" - "math/rand" "reflect" "testing" "time" "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/internal/testlog" @@ -34,23 +34,25 @@ import ( "github.com/ethereum/go-ethereum/p2p/enr" ) -const ( - signingKeySeed = 0x111111 - nodesSeed1 = 0x2945237 - nodesSeed2 = 0x4567299 -) +var signingKeyForTesting, _ = crypto.ToECDSA(hexutil.MustDecode("0xdc599867fc513f8f5e2c2c9c489cde5e71362d1d9ec6e693e0de063236ed1240")) func TestClientSyncTree(t *testing.T) { + nodes := []string{ + "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", + "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI", + "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o", + } + r := mapResolver{ "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA", "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", "JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24", - "2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", - "H4FHT4B454P6UXFD7JCYQ5PWDY.n": "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI", - "MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o", + "2XS2367YHAXJFGLZHVAWLQD4ZY.n": nodes[0], + "H4FHT4B454P6UXFD7JCYQ5PWDY.n": nodes[1], + "MHTDO6TMUBRIA2XWG5LUDACK24.n": nodes[2], } var ( - wantNodes = testNodes(0x29452, 3) + wantNodes = sortByID(parseNodes(nodes)) wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"} wantSeq = uint(1) ) @@ -60,7 +62,7 @@ func TestClientSyncTree(t *testing.T) { if err != nil { t.Fatal("sync error:", err) } - if !reflect.DeepEqual(sortByID(stree.Nodes()), sortByID(wantNodes)) { + if !reflect.DeepEqual(sortByID(stree.Nodes()), wantNodes) { t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes)) } if !reflect.DeepEqual(stree.Links(), wantLinks) { @@ -80,7 +82,7 @@ func TestClientSyncTreeBadNode(t *testing.T) { // tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}) // tree.entries[badHash] = &b // tree.root.eroot = badHash - // url, _ := tree.Sign(testKey(signingKeySeed), "n") + // url, _ := tree.Sign(signingKeyForTesting, "n") // fmt.Println(url) // fmt.Printf("%#v\n", tree.ToTXT("n")) @@ -99,9 +101,13 @@ func TestClientSyncTreeBadNode(t *testing.T) { // This test checks that randomIterator finds all entries. func TestIterator(t *testing.T) { - nodes := testNodes(nodesSeed1, 30) - tree, url := makeTestTree("n", nodes, nil) - r := mapResolver(tree.ToTXT("n")) + var ( + keys = testKeys(30) + nodes = testNodes(keys) + tree, url = makeTestTree("n", nodes, nil) + r = mapResolver(tree.ToTXT("n")) + ) + c := NewClient(Config{ Resolver: r, Logger: testlog.Logger(t, log.LvlTrace), @@ -115,10 +121,29 @@ 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) - tree1, url1 := makeTestTree("t1", nodes, nil) + var ( + keys = testKeys(500) + nodes = testNodes(keys) + tree1, url1 = makeTestTree("t1", nodes, nil) + ) + c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))}) it, err := c.NewIterator(url1) if err != nil { @@ -140,9 +165,13 @@ func TestIteratorClose(t *testing.T) { // This test checks that randomIterator traverses linked trees as well as explicitly added trees. func TestIteratorLinks(t *testing.T) { - nodes := testNodes(nodesSeed1, 40) - tree1, url1 := makeTestTree("t1", nodes[:10], nil) - tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1}) + var ( + keys = testKeys(40) + nodes = testNodes(keys) + tree1, url1 = makeTestTree("t1", nodes[:10], nil) + tree2, url2 = makeTestTree("t2", nodes[10:], []string{url1}) + ) + c := NewClient(Config{ Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")), Logger: testlog.Logger(t, log.LvlTrace), @@ -161,7 +190,8 @@ func TestIteratorLinks(t *testing.T) { func TestIteratorNodeUpdates(t *testing.T) { var ( clock = new(mclock.Simulated) - nodes = testNodes(nodesSeed1, 30) + keys = testKeys(30) + nodes = testNodes(keys) resolver = newMapResolver() c = NewClient(Config{ Resolver: resolver, @@ -182,7 +212,7 @@ func TestIteratorNodeUpdates(t *testing.T) { checkIterator(t, it, nodes[:25]) // Ensure RandomNode returns the new nodes after the tree is updated. - updateSomeNodes(nodesSeed1, nodes) + updateSomeNodes(keys, nodes) tree2, _ := makeTestTree("n", nodes, nil) resolver.clear() resolver.add(tree2.ToTXT("n")) @@ -198,7 +228,8 @@ func TestIteratorNodeUpdates(t *testing.T) { func TestIteratorRootRecheckOnFail(t *testing.T) { var ( clock = new(mclock.Simulated) - nodes = testNodes(nodesSeed1, 30) + keys = testKeys(30) + nodes = testNodes(keys) resolver = newMapResolver() c = NewClient(Config{ Resolver: resolver, @@ -222,7 +253,7 @@ func TestIteratorRootRecheckOnFail(t *testing.T) { checkIterator(t, it, nodes[:25]) // Ensure RandomNode returns the new nodes after the tree is updated. - updateSomeNodes(nodesSeed1, nodes) + updateSomeNodes(keys, nodes) tree2, _ := makeTestTree("n", nodes, nil) resolver.clear() resolver.add(tree2.ToTXT("n")) @@ -235,7 +266,8 @@ func TestIteratorRootRecheckOnFail(t *testing.T) { func TestIteratorEmptyTree(t *testing.T) { var ( clock = new(mclock.Simulated) - nodes = testNodes(nodesSeed1, 1) + keys = testKeys(1) + nodes = testNodes(keys) resolver = newMapResolver() c = NewClient(Config{ Resolver: resolver, @@ -250,7 +282,7 @@ func TestIteratorEmptyTree(t *testing.T) { resolver.add(tree1.ToTXT("n")) // Start the iterator. - node := make(chan *enode.Node) + node := make(chan *enode.Node, 1) it, err := c.NewIterator(url) if err != nil { t.Fatal(err) @@ -279,8 +311,7 @@ func TestIteratorEmptyTree(t *testing.T) { } // updateSomeNodes applies ENR updates to some of the given nodes. -func updateSomeNodes(keySeed int64, nodes []*enode.Node) { - keys := testKeys(nodesSeed1, len(nodes)) +func updateSomeNodes(keys []*ecdsa.PrivateKey, nodes []*enode.Node) { for i, n := range nodes[:len(nodes)/2] { r := n.Record() r.Set(enr.IP{127, 0, 0, 1}) @@ -296,7 +327,8 @@ func updateSomeNodes(keySeed int64, nodes []*enode.Node) { func TestIteratorLinkUpdates(t *testing.T) { var ( clock = new(mclock.Simulated) - nodes = testNodes(nodesSeed1, 30) + keys = testKeys(30) + nodes = testNodes(keys) resolver = newMapResolver() c = NewClient(Config{ Resolver: resolver, @@ -369,7 +401,7 @@ func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, st if err != nil { panic(err) } - url, err := tree.Sign(testKey(signingKeySeed), domain) + url, err := tree.Sign(signingKeyForTesting, domain) if err != nil { panic(err) } @@ -377,11 +409,10 @@ func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, st } // testKeys creates deterministic private keys for testing. -func testKeys(seed int64, n int) []*ecdsa.PrivateKey { - rand := rand.New(rand.NewSource(seed)) +func testKeys(n int) []*ecdsa.PrivateKey { keys := make([]*ecdsa.PrivateKey, n) for i := 0; i < n; i++ { - key, err := ecdsa.GenerateKey(crypto.S256(), rand) + key, err := crypto.GenerateKey() if err != nil { panic("can't generate key: " + err.Error()) } @@ -390,13 +421,8 @@ func testKeys(seed int64, n int) []*ecdsa.PrivateKey { return keys } -func testKey(seed int64) *ecdsa.PrivateKey { - return testKeys(seed, 1)[0] -} - -func testNodes(seed int64, n int) []*enode.Node { - keys := testKeys(seed, n) - nodes := make([]*enode.Node, n) +func testNodes(keys []*ecdsa.PrivateKey) []*enode.Node { + nodes := make([]*enode.Node, len(keys)) for i, key := range keys { record := new(enr.Record) record.SetSeq(uint64(i)) @@ -410,10 +436,6 @@ func testNodes(seed int64, n int) []*enode.Node { return nodes } -func testNode(seed int64) *enode.Node { - return testNodes(seed, 1)[0] -} - type mapResolver map[string]string func newMapResolver(maps ...map[string]string) mapResolver { @@ -442,3 +464,15 @@ func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, err } return nil, errors.New("not found") } + +func parseNodes(rec []string) []*enode.Node { + var ns []*enode.Node + for _, r := range rec { + var n enode.Node + if err := n.UnmarshalText([]byte(r)); err != nil { + panic(err) + } + ns = append(ns, &n) + } + return ns +} diff --git a/p2p/dnsdisc/tree_test.go b/p2p/dnsdisc/tree_test.go index 4048c35d..9ed17aa4 100644 --- a/p2p/dnsdisc/tree_test.go +++ b/p2p/dnsdisc/tree_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The go-ethereum Authors +// 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 @@ -61,7 +61,9 @@ func TestParseRoot(t *testing.T) { } func TestParseEntry(t *testing.T) { - testkey := testKey(signingKeySeed) + testENRs := []string{"enr:-HW4QES8QIeXTYlDzbfr1WEzE-XKY4f8gJFJzjJL-9D7TC9lJb4Z3JPRRz1lP4pL_N_QpT6rGQjAU9Apnc-C1iMP36OAgmlkgnY0iXNlY3AyNTZrMaED5IdwfMxdmR8W37HqSFdQLjDkIwBd4Q_MjxgZifgKSdM"} + testNodes := parseNodes(testENRs) + tests := []struct { input string e entry @@ -91,7 +93,11 @@ func TestParseEntry(t *testing.T) { // Links { input: "enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@nodes.example.org", - e: &linkEntry{"AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@nodes.example.org", "nodes.example.org", &testkey.PublicKey}, + e: &linkEntry{ + str: "AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@nodes.example.org", + domain: "nodes.example.org", + pubkey: &signingKeyForTesting.PublicKey, + }, }, { input: "enrtree://nodes.example.org", @@ -107,8 +113,8 @@ func TestParseEntry(t *testing.T) { }, // ENRs { - input: "enr:-HW4QES8QIeXTYlDzbfr1WEzE-XKY4f8gJFJzjJL-9D7TC9lJb4Z3JPRRz1lP4pL_N_QpT6rGQjAU9Apnc-C1iMP36OAgmlkgnY0iXNlY3AyNTZrMaED5IdwfMxdmR8W37HqSFdQLjDkIwBd4Q_MjxgZifgKSdM", - e: &enrEntry{node: testNode(nodesSeed1)}, + input: testENRs[0], + e: &enrEntry{node: testNodes[0]}, }, { input: "enr:-HW4QLZHjM4vZXkbp-5xJoHsKSbE7W39FPC8283X-y8oHcHPTnDDlIlzL5ArvDUlHZVDPgmFASrh7cWgLOLxj4wprRkHgmlkgnY0iXNlY3AyNTZrMaEC3t2jLMhDpCDX5mbSEwDn4L3iUfyXzoO8G28XvjGRkrAg=", @@ -132,7 +138,8 @@ func TestParseEntry(t *testing.T) { } func TestMakeTree(t *testing.T) { - nodes := testNodes(nodesSeed2, 50) + keys := testKeys(50) + nodes := testNodes(keys) tree, err := MakeTree(2, nodes, nil) if err != nil { t.Fatal(err) diff --git a/params/config.go b/params/config.go index 97cf6fc9..927d9a6f 100644 --- a/params/config.go +++ b/params/config.go @@ -60,21 +60,13 @@ var ( DevnetChainID uint64 = 60803 // zion token alias, decimal is 18 - ZNT1, _ = new(big.Int).SetString("1000000000000000000", 10) + ZNT1, _ = new(big.Int).SetString("1000000000000000000", 10) uGenesisSupply = new(big.Int).SetUint64(1e8) GenesisSupply = new(big.Int).Mul(ZNT1, uGenesisSupply) + RewardPerBlock = new(big.Int).Set(ZNT1) ) -func CheckZionChain(chainID uint64) bool { - switch chainID { - case MainnetChainID, TestnetChainID, DevnetChainID: - return true - default: - return false - } -} - var ( // MainnetChainConfig is the chain parameters to run a node on the main network. MainnetChainConfig = &ChainConfig{ @@ -615,6 +607,16 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi return nil } +// BaseFeeChangeDenominator bounds the amount the base fee can change between blocks. +func (c *ChainConfig) BaseFeeChangeDenominator() uint64 { + return DefaultBaseFeeChangeDenominator +} + +// ElasticityMultiplier bounds the maximum gas limit an EIP-1559 block may have. +func (c *ChainConfig) ElasticityMultiplier() uint64 { + return DefaultElasticityMultiplier +} + // isForkIncompatible returns true if a fork scheduled at s1 cannot be rescheduled to // block s2 because head is already past the fork. func isForkIncompatible(s1, s2, head *big.Int) bool { diff --git a/params/protocol_params.go b/params/protocol_params.go index 5b7c9686..ed1b79c6 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -19,10 +19,10 @@ package params import "math/big" const ( - GasLimitBoundDivisor uint64 = 16 //1024 // The bound divisor of the gas limit, used in update calculations. + GasLimitBoundDivisor uint64 = 1024 // The bound divisor of the gas limit, used in update calculations. // TODO(fuk): `MinGasLimit` settled as 40000000 for high throughput MinGasLimit uint64 = 5000 // Minimum the gas limit may ever be. - GenesisGasLimit uint64 = 40000000 //4712388 // Gas limit of the Genesis block. + GenesisGasLimit uint64 = 4712388 //4712388 // Gas limit of the Genesis block. MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis. ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction. @@ -119,9 +119,9 @@ 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. + DefaultBaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks. + DefaultElasticityMultiplier = 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 diff --git a/rlp/decode.go b/rlp/decode.go index ac04d5d5..c9b26524 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -27,6 +27,8 @@ import ( "reflect" "strings" "sync" + + "github.com/ethereum/go-ethereum/rlp/internal/rlpstruct" ) //lint:ignore ST1012 EOL is not an error. @@ -74,7 +76,7 @@ type Decoder interface { // Note that Decode does not set an input limit for all readers and may be vulnerable to // panics cause by huge value sizes. If you need an input limit, use // -// NewStream(r, limit).Decode(val) +// NewStream(r, limit).Decode(val) func Decode(r io.Reader, val interface{}) error { stream := streamPool.Get().(*Stream) defer streamPool.Put(stream) @@ -148,7 +150,7 @@ var ( bigInt = reflect.TypeOf(big.Int{}) ) -func makeDecoder(typ reflect.Type, tags tags) (dec decoder, err error) { +func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error) { kind := typ.Kind() switch { case typ == rawValueType: @@ -220,55 +222,20 @@ func decodeBigIntNoPtr(s *Stream, val reflect.Value) error { } func decodeBigInt(s *Stream, val reflect.Value) error { - 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)) } - i.SetBytes(buffer) + + err := s.decodeBigInt(i) + if err != nil { + return wrapStreamError(err, val.Type()) + } return nil } -func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) { +func makeListDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) { etype := typ.Elem() if etype.Kind() == reflect.Uint8 && !reflect.PtrTo(etype).Implements(decoderInterface) { if typ.Kind() == reflect.Array { @@ -276,7 +243,7 @@ func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) { } return decodeByteSlice, nil } - etypeinfo := theTC.infoWhileGenerating(etype, tags{}) + etypeinfo := theTC.infoWhileGenerating(etype, rlpstruct.Tags{}) if etypeinfo.decoderErr != nil { return nil, etypeinfo.decoderErr } @@ -286,7 +253,7 @@ func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) { dec = func(s *Stream, val reflect.Value) error { return decodeListArray(s, val, etypeinfo.decoder) } - case tag.tail: + case tag.Tail: // A slice with "tail" tag can occur as the last field // of a struct and is supposed to swallow all remaining // list elements. The struct decoder already called s.List, @@ -379,7 +346,7 @@ func decodeByteArray(s *Stream, val reflect.Value) error { if err != nil { return err } - slice := byteArrayBytes(val) + slice := byteArrayBytes(val, val.Len()) switch kind { case Byte: if len(slice) == 0 { @@ -451,16 +418,16 @@ 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) { +func makePtrDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) { etype := typ.Elem() - etypeinfo := theTC.infoWhileGenerating(etype, tags{}) + etypeinfo := theTC.infoWhileGenerating(etype, rlpstruct.Tags{}) switch { case etypeinfo.decoderErr != nil: return nil, etypeinfo.decoderErr - case !tag.nilOK: + case !tag.NilOK: return makeSimplePtrDecoder(etype, etypeinfo), nil default: - return makeNilPtrDecoder(etype, etypeinfo, tag.nilKind), nil + return makeNilPtrDecoder(etype, etypeinfo, tag), nil } } @@ -481,9 +448,13 @@ func makeSimplePtrDecoder(etype reflect.Type, etypeinfo *typeinfo) decoder { // values are decoded into a value of the element type, just like makePtrDecoder does. // // This decoder is used for pointer-typed struct fields with struct tag "nil". -func makeNilPtrDecoder(etype reflect.Type, etypeinfo *typeinfo, nilKind Kind) decoder { +func makeNilPtrDecoder(etype reflect.Type, etypeinfo *typeinfo, ts rlpstruct.Tags) decoder { typ := reflect.PtrTo(etype) nilPtr := reflect.Zero(typ) + + // Determine the value kind that results in nil pointer. + nilKind := typeNilKind(etype, ts) + return func(s *Stream, val reflect.Value) (err error) { kind, size, err := s.Kind() if err != nil { @@ -659,6 +630,37 @@ func (s *Stream) Bytes() ([]byte, error) { } } +// ReadBytes decodes the next RLP value and stores the result in b. +// The value size must match len(b) exactly. +func (s *Stream) ReadBytes(b []byte) error { + kind, size, err := s.Kind() + if err != nil { + return err + } + switch kind { + case Byte: + if len(b) != 1 { + return fmt.Errorf("input value has wrong size 1, want %d", len(b)) + } + b[0] = s.byteval + s.kind = -1 // rearm Kind + return nil + case String: + if uint64(len(b)) != size { + return fmt.Errorf("input value has wrong size %d, want %d", size, len(b)) + } + if err = s.readFull(b); err != nil { + return err + } + if size == 1 && b[0] < 128 { + return ErrCanonSize + } + return nil + default: + return ErrExpectedString + } +} + // Raw reads a raw encoded value including RLP type information. func (s *Stream) Raw() ([]byte, error) { kind, size, err := s.Kind() @@ -687,10 +689,31 @@ func (s *Stream) Raw() ([]byte, error) { // Uint reads an RLP string of up to 8 bytes and returns its contents // as an unsigned integer. If the input does not contain an RLP string, the // returned error will be ErrExpectedString. +// +// Deprecated: use s.Uint64 instead. func (s *Stream) Uint() (uint64, error) { return s.uint(64) } +func (s *Stream) Uint64() (uint64, error) { + return s.uint(64) +} + +func (s *Stream) Uint32() (uint32, error) { + i, err := s.uint(32) + return uint32(i), err +} + +func (s *Stream) Uint16() (uint16, error) { + i, err := s.uint(16) + return uint16(i), err +} + +func (s *Stream) Uint8() (uint8, error) { + i, err := s.uint(8) + return uint8(i), err +} + func (s *Stream) uint(maxbits int) (uint64, error) { kind, size, err := s.Kind() if err != nil { @@ -781,6 +804,65 @@ func (s *Stream) ListEnd() error { return nil } +// MoreDataInList reports whether the current list context contains +// more data to be read. +func (s *Stream) MoreDataInList() bool { + _, listLimit := s.listLimit() + return listLimit > 0 +} + +// BigInt decodes an arbitrary-size integer value. +func (s *Stream) BigInt() (*big.Int, error) { + i := new(big.Int) + if err := s.decodeBigInt(i); err != nil { + return nil, err + } + return i, nil +} + +func (s *Stream) decodeBigInt(dst *big.Int) error { + var buffer []byte + kind, size, err := s.Kind() + switch { + case err != nil: + return err + case kind == List: + return ErrExpectedString + 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 err + } + // Reject inputs where single byte encoding should have been used. + if size == 1 && buffer[0] < 128 { + return ErrCanonSize + } + default: + // For large integers, a temporary buffer is needed. + buffer = make([]byte, size) + if err := s.readFull(buffer); err != nil { + return err + } + } + + // Reject leading zero bytes. + if len(buffer) > 0 && buffer[0] == 0 { + return ErrCanonInt + } + // Set the integer bytes. + dst.SetBytes(buffer) + return nil +} + // Decode decodes a value and stores the result in the value pointed // to by val. Please see the documentation for the Decode function // to learn about the decoding rules. diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 7c3dafea..dbcfcffe 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -286,6 +286,47 @@ func TestStreamRaw(t *testing.T) { } } +func TestStreamReadBytes(t *testing.T) { + tests := []struct { + input string + size int + err string + }{ + // kind List + {input: "C0", size: 1, err: "rlp: expected String or Byte"}, + // kind Byte + {input: "04", size: 0, err: "input value has wrong size 1, want 0"}, + {input: "04", size: 1}, + {input: "04", size: 2, err: "input value has wrong size 1, want 2"}, + // kind String + {input: "820102", size: 0, err: "input value has wrong size 2, want 0"}, + {input: "820102", size: 1, err: "input value has wrong size 2, want 1"}, + {input: "820102", size: 2}, + {input: "820102", size: 3, err: "input value has wrong size 2, want 3"}, + } + + for _, test := range tests { + test := test + name := fmt.Sprintf("input_%s/size_%d", test.input, test.size) + t.Run(name, func(t *testing.T) { + s := NewStream(bytes.NewReader(unhex(test.input)), 0) + b := make([]byte, test.size) + err := s.ReadBytes(b) + if test.err == "" { + if err != nil { + t.Errorf("unexpected error %q", err) + } + } else { + if err == nil { + t.Errorf("expected error, got nil") + } else if err.Error() != test.err { + t.Errorf("wrong error %q", err) + } + } + }) + } +} + func TestDecodeErrors(t *testing.T) { r := bytes.NewReader(nil) @@ -398,6 +439,16 @@ type optionalPtrField struct { B *[3]byte `rlp:"optional"` } +type nonOptionalPtrField struct { + A uint + B *[3]byte +} + +type multipleOptionalFields struct { + A *[3]byte `rlp:"optional"` + B *[3]byte `rlp:"optional"` +} + type optionalPtrFieldNil struct { A uint B *[3]byte `rlp:"optional,nil"` @@ -411,7 +462,7 @@ type ignoredField struct { var ( veryBigInt = new(big.Int).Add( - big.NewInt(0).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16), + new(big.Int).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16), big.NewInt(0xFFFF), ) veryVeryBigInt = new(big.Int).Exp(veryBigInt, big.NewInt(8), nil) @@ -703,6 +754,30 @@ var decodeTests = []decodeTest{ ptr: new(optionalPtrField), value: optionalPtrField{A: 1, B: &[3]byte{1, 2, 3}}, }, + { + // all optional fields nil + input: "C0", + ptr: new(multipleOptionalFields), + value: multipleOptionalFields{A: nil, B: nil}, + }, + { + // all optional fields set + input: "C88301020383010203", + ptr: new(multipleOptionalFields), + value: multipleOptionalFields{A: &[3]byte{1, 2, 3}, B: &[3]byte{1, 2, 3}}, + }, + { + // nil optional field appears before a non-nil one + input: "C58083010203", + ptr: new(multipleOptionalFields), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.multipleOptionalFields).A", + }, + { + // decode a nil ptr into a ptr that is not nil or not optional + input: "C20180", + ptr: new(nonOptionalPtrField), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.nonOptionalPtrField).B", + }, { input: "C101", ptr: new(optionalPtrFieldNil), @@ -990,7 +1065,7 @@ func TestInvalidOptionalField(t *testing.T) { v interface{} err string }{ - {v: new(invalid1), err: `rlp: struct field rlp.invalid1.B needs "optional" tag`}, + {v: new(invalid1), err: `rlp: invalid struct tag "" for rlp.invalid1.B (must be optional because preceding field "A" is optional)`}, {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)`}, } @@ -1002,7 +1077,6 @@ func TestInvalidOptionalField(t *testing.T) { t.Errorf("wrong error for %T: %v", test.v, err.Error()) } } - } func ExampleDecode() { @@ -1162,7 +1236,7 @@ func encodeTestSlice(n uint) []byte { } func unhex(str string) []byte { - b, err := hex.DecodeString(strings.Replace(str, " ", "", -1)) + b, err := hex.DecodeString(strings.ReplaceAll(str, " ", "")) if err != nil { panic(fmt.Sprintf("invalid hex string: %q", str)) } diff --git a/rlp/doc.go b/rlp/doc.go index 113828e3..eeeee9a4 100644 --- a/rlp/doc.go +++ b/rlp/doc.go @@ -27,8 +27,7 @@ value zero equivalent to the empty string). RLP values are distinguished by a type tag. The type tag precedes the value in the input stream and defines the size and kind of the bytes that follow. - -Encoding Rules +# Encoding Rules Package rlp uses reflection and encodes RLP based on the Go type of the value. @@ -37,7 +36,7 @@ call EncodeRLP on nil pointer values. To encode a pointer, the value being pointed to is encoded. A nil pointer to a struct type, slice or array always encodes as an empty RLP list unless the slice or array has -elememt type byte. A nil pointer to any other value encodes as the empty string. +element type byte. A nil pointer to any other value encodes as the empty string. Struct values are encoded as an RLP list of all their encoded public fields. Recursive struct types are supported. @@ -58,8 +57,7 @@ An interface value encodes as the value contained in the interface. Floating point numbers, maps, channels and functions are not supported. - -Decoding Rules +# Decoding Rules Decoding uses the following type-dependent rules: @@ -93,30 +91,29 @@ or one (true). To decode into an interface value, one of these types is stored in the value: - []interface{}, for RLP lists - []byte, for RLP strings + []interface{}, for RLP lists + []byte, for RLP strings Non-empty interface types are not supported when decoding. Signed integers, floating point numbers, maps, channels and functions cannot be decoded into. - -Struct Tags +# Struct Tags As with other encoding packages, the "-" tag ignores fields. - type StructWithIgnoredField struct{ - Ignored uint `rlp:"-"` - Field uint - } + 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"` - } + type StructWithTail struct{ + Field uint + Tail []string `rlp:"tail"` + } 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. @@ -128,11 +125,11 @@ When decoding into a struct, optional fields may be omitted from the end of the 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"` - } + 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, @@ -140,9 +137,9 @@ input values must always match the required input length exactly and the decoder 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"` - } + 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 diff --git a/rlp/encbuffer.go b/rlp/encbuffer.go new file mode 100644 index 00000000..d2c6d93b --- /dev/null +++ b/rlp/encbuffer.go @@ -0,0 +1,398 @@ +// Copyright 2022 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 rlp + +import ( + "io" + "math/big" + "reflect" + "sync" +) + +type encBuffer 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 +} + +// The global encBuffer pool. +var encBufferPool = sync.Pool{ + New: func() interface{} { return new(encBuffer) }, +} + +func getEncBuffer() *encBuffer { + buf := encBufferPool.Get().(*encBuffer) + buf.reset() + return buf +} + +func (buf *encBuffer) reset() { + buf.lhsize = 0 + buf.str = buf.str[:0] + buf.lheads = buf.lheads[:0] +} + +// size returns the length of the encoded data. +func (buf *encBuffer) size() int { + return len(buf.str) + buf.lhsize +} + +// makeBytes creates the encoder output. +func (w *encBuffer) makeBytes() []byte { + out := make([]byte, w.size()) + w.copyTo(out) + return out +} + +func (w *encBuffer) copyTo(dst []byte) { + strpos := 0 + pos := 0 + for _, head := range w.lheads { + // write string data before header + n := copy(dst[pos:], w.str[strpos:head.offset]) + pos += n + strpos += n + // write the header + enc := head.encode(dst[pos:]) + pos += len(enc) + } + // copy string data after the last list header + copy(dst[pos:], w.str[strpos:]) +} + +// writeTo writes the encoder output to w. +func (buf *encBuffer) writeTo(w io.Writer) (err error) { + strpos := 0 + for _, head := range buf.lheads { + // write string data before header + if head.offset-strpos > 0 { + n, err := w.Write(buf.str[strpos:head.offset]) + strpos += n + if err != nil { + return err + } + } + // write the header + enc := head.encode(buf.sizebuf[:]) + if _, err = w.Write(enc); err != nil { + return err + } + } + if strpos < len(buf.str) { + // write string data after the last list header + _, err = w.Write(buf.str[strpos:]) + } + return err +} + +// Write implements io.Writer and appends b directly to the output. +func (buf *encBuffer) Write(b []byte) (int, error) { + buf.str = append(buf.str, b...) + return len(b), nil +} + +// writeBool writes b as the integer 0 (false) or 1 (true). +func (buf *encBuffer) writeBool(b bool) { + if b { + buf.str = append(buf.str, 0x01) + } else { + buf.str = append(buf.str, 0x80) + } +} + +func (buf *encBuffer) writeUint64(i uint64) { + if i == 0 { + buf.str = append(buf.str, 0x80) + } else if i < 128 { + // fits single byte + buf.str = append(buf.str, byte(i)) + } else { + s := putint(buf.sizebuf[1:], i) + buf.sizebuf[0] = 0x80 + byte(s) + buf.str = append(buf.str, buf.sizebuf[:s+1]...) + } +} + +func (buf *encBuffer) writeBytes(b []byte) { + if len(b) == 1 && b[0] <= 0x7F { + // fits single byte, no string header + buf.str = append(buf.str, b[0]) + } else { + buf.encodeStringHeader(len(b)) + buf.str = append(buf.str, b...) + } +} + +func (buf *encBuffer) writeString(s string) { + buf.writeBytes([]byte(s)) +} + +// wordBytes is the number of bytes in a big.Word +const wordBytes = (32 << (uint64(^big.Word(0)) >> 63)) / 8 + +// writeBigInt writes i as an integer. +func (w *encBuffer) writeBigInt(i *big.Int) { + bitlen := i.BitLen() + if bitlen <= 64 { + w.writeUint64(i.Uint64()) + return + } + // Integer is larger than 64 bits, encode from i.Bits(). + // The minimal byte length is bitlen rounded up to the next + // multiple of 8, divided by 8. + length := ((bitlen + 7) & -8) >> 3 + w.encodeStringHeader(length) + w.str = append(w.str, make([]byte, length)...) + index := length + buf := w.str[len(w.str)-length:] + for _, d := range i.Bits() { + for j := 0; j < wordBytes && index > 0; j++ { + index-- + buf[index] = byte(d) + d >>= 8 + } + } +} + +// list adds a new list header to the header stack. It returns the index of the header. +// Call listEnd with this index after encoding the content of the list. +func (buf *encBuffer) list() int { + buf.lheads = append(buf.lheads, listhead{offset: len(buf.str), size: buf.lhsize}) + return len(buf.lheads) - 1 +} + +func (buf *encBuffer) listEnd(index int) { + lh := &buf.lheads[index] + lh.size = buf.size() - lh.offset - lh.size + if lh.size < 56 { + buf.lhsize++ // length encoded into kind tag + } else { + buf.lhsize += 1 + intsize(uint64(lh.size)) + } +} + +func (buf *encBuffer) encode(val interface{}) error { + rval := reflect.ValueOf(val) + writer, err := cachedWriter(rval.Type()) + if err != nil { + return err + } + return writer(rval, buf) +} + +func (buf *encBuffer) encodeStringHeader(size int) { + if size < 56 { + buf.str = append(buf.str, 0x80+byte(size)) + } else { + sizesize := putint(buf.sizebuf[1:], uint64(size)) + buf.sizebuf[0] = 0xB7 + byte(sizesize) + buf.str = append(buf.str, buf.sizebuf[:sizesize+1]...) + } +} + +// encReader is the io.Reader returned by EncodeToReader. +// It releases its encbuf at EOF. +type encReader struct { + buf *encBuffer // the buffer we're reading from. this is nil when we're at EOF. + lhpos int // index of list header that we're reading + strpos int // current position in string buffer + piece []byte // next piece to be read +} + +func (r *encReader) Read(b []byte) (n int, err error) { + for { + if r.piece = r.next(); r.piece == nil { + // Put the encode buffer back into the pool at EOF when it + // is first encountered. Subsequent calls still return EOF + // as the error but the buffer is no longer valid. + if r.buf != nil { + encBufferPool.Put(r.buf) + r.buf = nil + } + return n, io.EOF + } + nn := copy(b[n:], r.piece) + n += nn + if nn < len(r.piece) { + // piece didn't fit, see you next time. + r.piece = r.piece[nn:] + return n, nil + } + r.piece = nil + } +} + +// next returns the next piece of data to be read. +// it returns nil at EOF. +func (r *encReader) next() []byte { + switch { + case r.buf == nil: + return nil + + case r.piece != nil: + // There is still data available for reading. + return r.piece + + case r.lhpos < len(r.buf.lheads): + // We're before the last list header. + head := r.buf.lheads[r.lhpos] + sizebefore := head.offset - r.strpos + if sizebefore > 0 { + // String data before header. + p := r.buf.str[r.strpos:head.offset] + r.strpos += sizebefore + return p + } + r.lhpos++ + return head.encode(r.buf.sizebuf[:]) + + case r.strpos < len(r.buf.str): + // String data at the end, after all list headers. + p := r.buf.str[r.strpos:] + r.strpos = len(r.buf.str) + return p + + default: + return nil + } +} + +func encBufferFromWriter(w io.Writer) *encBuffer { + switch w := w.(type) { + case EncoderBuffer: + return w.buf + case *EncoderBuffer: + return w.buf + case *encBuffer: + return w + default: + return nil + } +} + +// EncoderBuffer is a buffer for incremental encoding. +// +// The zero value is NOT ready for use. To get a usable buffer, +// create it using NewEncoderBuffer or call Reset. +type EncoderBuffer struct { + buf *encBuffer + dst io.Writer + + ownBuffer bool +} + +// NewEncoderBuffer creates an encoder buffer. +func NewEncoderBuffer(dst io.Writer) EncoderBuffer { + var w EncoderBuffer + w.Reset(dst) + return w +} + +// Reset truncates the buffer and sets the output destination. +func (w *EncoderBuffer) Reset(dst io.Writer) { + if w.buf != nil && !w.ownBuffer { + panic("can't Reset derived EncoderBuffer") + } + + // If the destination writer has an *encBuffer, use it. + // Note that w.ownBuffer is left false here. + if dst != nil { + if outer := encBufferFromWriter(dst); outer != nil { + *w = EncoderBuffer{outer, nil, false} + return + } + } + + // Get a fresh buffer. + if w.buf == nil { + w.buf = encBufferPool.Get().(*encBuffer) + w.ownBuffer = true + } + w.buf.reset() + w.dst = dst +} + +// Flush writes encoded RLP data to the output writer. This can only be called once. +// If you want to re-use the buffer after Flush, you must call Reset. +func (w *EncoderBuffer) Flush() error { + var err error + if w.dst != nil { + err = w.buf.writeTo(w.dst) + } + // Release the internal buffer. + if w.ownBuffer { + encBufferPool.Put(w.buf) + } + *w = EncoderBuffer{} + return err +} + +// ToBytes returns the encoded bytes. +func (w *EncoderBuffer) ToBytes() []byte { + return w.buf.makeBytes() +} + +// AppendToBytes appends the encoded bytes to dst. +func (w *EncoderBuffer) AppendToBytes(dst []byte) []byte { + size := w.buf.size() + out := append(dst, make([]byte, size)...) + w.buf.copyTo(out[len(dst):]) + return out +} + +// Write appends b directly to the encoder output. +func (w EncoderBuffer) Write(b []byte) (int, error) { + return w.buf.Write(b) +} + +// WriteBool writes b as the integer 0 (false) or 1 (true). +func (w EncoderBuffer) WriteBool(b bool) { + w.buf.writeBool(b) +} + +// WriteUint64 encodes an unsigned integer. +func (w EncoderBuffer) WriteUint64(i uint64) { + w.buf.writeUint64(i) +} + +// WriteBigInt encodes a big.Int as an RLP string. +// Note: Unlike with Encode, the sign of i is ignored. +func (w EncoderBuffer) WriteBigInt(i *big.Int) { + w.buf.writeBigInt(i) +} + +// WriteBytes encodes b as an RLP string. +func (w EncoderBuffer) WriteBytes(b []byte) { + w.buf.writeBytes(b) +} + +// WriteString encodes s as an RLP string. +func (w EncoderBuffer) WriteString(s string) { + w.buf.writeString(s) +} + +// List starts a list. It returns an internal index. Call EndList with +// this index after encoding the content to finish the list. +func (w EncoderBuffer) List() int { + return w.buf.list() +} + +// ListEnd finishes the given list. +func (w EncoderBuffer) ListEnd(index int) { + w.buf.listEnd(index) +} diff --git a/rlp/encbuffer_example_test.go b/rlp/encbuffer_example_test.go new file mode 100644 index 00000000..ee15d82a --- /dev/null +++ b/rlp/encbuffer_example_test.go @@ -0,0 +1,45 @@ +// Copyright 2022 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 rlp_test + +import ( + "bytes" + "fmt" + + "github.com/ethereum/go-ethereum/rlp" +) + +func ExampleEncoderBuffer() { + var w bytes.Buffer + + // Encode [4, [5, 6]] to w. + buf := rlp.NewEncoderBuffer(&w) + l1 := buf.List() + buf.WriteUint64(4) + l2 := buf.List() + buf.WriteUint64(5) + buf.WriteUint64(6) + buf.ListEnd(l2) + buf.ListEnd(l1) + + if err := buf.Flush(); err != nil { + panic(err) + } + fmt.Printf("%X\n", w.Bytes()) + // Output: + // C404C20506 +} diff --git a/rlp/encode.go b/rlp/encode.go index 33486443..a377a1ef 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -17,20 +17,27 @@ package rlp import ( + "errors" "fmt" "io" "math/big" "reflect" - "sync" + + "github.com/ethereum/go-ethereum/rlp/internal/rlpstruct" ) var ( // Common encoded values. // These are useful when implementing EncodeRLP. + + // EmptyString is the encoding of an empty string. EmptyString = []byte{0x80} - EmptyList = []byte{0xC0} + // EmptyList is the encoding of an empty list. + EmptyList = []byte{0xC0} ) +var ErrNegativeBigInt = errors.New("rlp: cannot encode negative big.Int") + // Encoder is implemented by types that require custom // encoding rules or want to encode private fields. type Encoder interface { @@ -51,30 +58,29 @@ type Encoder interface { // // Please see package-level documentation of encoding rules. func Encode(w io.Writer, val interface{}) error { - if outer, ok := w.(*encbuf); ok { - // Encode was called by some type's EncodeRLP. - // Avoid copying by writing to the outer encbuf directly. - return outer.encode(val) + // Optimization: reuse *encBuffer when called by EncodeRLP. + if buf := encBufferFromWriter(w); buf != nil { + return buf.encode(val) } - eb := encbufPool.Get().(*encbuf) - defer encbufPool.Put(eb) - eb.reset() - if err := eb.encode(val); err != nil { + + buf := getEncBuffer() + defer encBufferPool.Put(buf) + if err := buf.encode(val); err != nil { return err } - return eb.toWriter(w) + return buf.writeTo(w) } // EncodeToBytes returns the RLP encoding of val. // Please see package-level documentation for the encoding rules. func EncodeToBytes(val interface{}) ([]byte, error) { - eb := encbufPool.Get().(*encbuf) - defer encbufPool.Put(eb) - eb.reset() - if err := eb.encode(val); err != nil { + buf := getEncBuffer() + defer encBufferPool.Put(buf) + + if err := buf.encode(val); err != nil { return nil, err } - return eb.toBytes(), nil + return buf.makeBytes(), nil } // EncodeToReader returns a reader from which the RLP encoding of val @@ -83,12 +89,15 @@ func EncodeToBytes(val interface{}) ([]byte, error) { // // Please see the documentation of Encode for the encoding rules. func EncodeToReader(val interface{}) (size int, r io.Reader, err error) { - eb := encbufPool.Get().(*encbuf) - eb.reset() - if err := eb.encode(val); err != nil { + buf := getEncBuffer() + if err := buf.encode(val); err != nil { + encBufferPool.Put(buf) return 0, nil, err } - return eb.size(), &encReader{buf: eb}, nil + // Note: can't put the reader back into the pool here + // because it is held by encReader. The reader puts it + // back when it has been fully consumed. + return buf.size(), &encReader{buf: buf}, nil } type listhead struct { @@ -123,207 +132,10 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int { return sizesize + 1 } -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 -} - -// encbufs are pooled. -var encbufPool = sync.Pool{ - New: func() interface{} { return new(encbuf) }, -} - -func (w *encbuf) reset() { - w.lhsize = 0 - w.str = w.str[:0] - w.lheads = w.lheads[:0] -} - -// encbuf implements io.Writer so it can be passed it into EncodeRLP. -func (w *encbuf) Write(b []byte) (int, error) { - w.str = append(w.str, b...) - return len(b), nil -} - -func (w *encbuf) encode(val interface{}) error { - rval := reflect.ValueOf(val) - writer, err := cachedWriter(rval.Type()) - if err != nil { - return err - } - return writer(rval, w) -} - -func (w *encbuf) encodeStringHeader(size int) { - if size < 56 { - w.str = append(w.str, 0x80+byte(size)) - } else { - sizesize := putint(w.sizebuf[1:], uint64(size)) - w.sizebuf[0] = 0xB7 + byte(sizesize) - w.str = append(w.str, w.sizebuf[:sizesize+1]...) - } -} - -func (w *encbuf) encodeString(b []byte) { - if len(b) == 1 && b[0] <= 0x7F { - // fits single byte, no string header - w.str = append(w.str, b[0]) - } else { - w.encodeStringHeader(len(b)) - w.str = append(w.str, b...) - } -} - -func (w *encbuf) encodeUint(i uint64) { - if i == 0 { - w.str = append(w.str, 0x80) - } else if i < 128 { - // fits single byte - w.str = append(w.str, byte(i)) - } else { - s := putint(w.sizebuf[1:], i) - w.sizebuf[0] = 0x80 + byte(s) - w.str = append(w.str, w.sizebuf[:s+1]...) - } -} - -// list adds a new list header to the header stack. It returns the index -// of the header. The caller must call listEnd with this index after encoding -// the content of the list. -func (w *encbuf) list() int { - w.lheads = append(w.lheads, listhead{offset: len(w.str), size: w.lhsize}) - return len(w.lheads) - 1 -} - -func (w *encbuf) listEnd(index int) { - lh := &w.lheads[index] - lh.size = w.size() - lh.offset - lh.size - if lh.size < 56 { - w.lhsize++ // length encoded into kind tag - } else { - w.lhsize += 1 + intsize(uint64(lh.size)) - } -} - -func (w *encbuf) size() int { - return len(w.str) + w.lhsize -} - -func (w *encbuf) toBytes() []byte { - out := make([]byte, w.size()) - strpos := 0 - pos := 0 - for _, head := range w.lheads { - // write string data before header - n := copy(out[pos:], w.str[strpos:head.offset]) - pos += n - strpos += n - // write the header - enc := head.encode(out[pos:]) - pos += len(enc) - } - // copy string data after the last list header - copy(out[pos:], w.str[strpos:]) - return out -} - -func (w *encbuf) toWriter(out io.Writer) (err error) { - strpos := 0 - for _, head := range w.lheads { - // write string data before header - if head.offset-strpos > 0 { - n, err := out.Write(w.str[strpos:head.offset]) - strpos += n - if err != nil { - return err - } - } - // write the header - enc := head.encode(w.sizebuf[:]) - if _, err = out.Write(enc); err != nil { - return err - } - } - if strpos < len(w.str) { - // write string data after the last list header - _, err = out.Write(w.str[strpos:]) - } - return err -} - -// encReader is the io.Reader returned by EncodeToReader. -// It releases its encbuf at EOF. -type encReader struct { - buf *encbuf // the buffer we're reading from. this is nil when we're at EOF. - lhpos int // index of list header that we're reading - strpos int // current position in string buffer - piece []byte // next piece to be read -} - -func (r *encReader) Read(b []byte) (n int, err error) { - for { - if r.piece = r.next(); r.piece == nil { - // Put the encode buffer back into the pool at EOF when it - // is first encountered. Subsequent calls still return EOF - // as the error but the buffer is no longer valid. - if r.buf != nil { - encbufPool.Put(r.buf) - r.buf = nil - } - return n, io.EOF - } - nn := copy(b[n:], r.piece) - n += nn - if nn < len(r.piece) { - // piece didn't fit, see you next time. - r.piece = r.piece[nn:] - return n, nil - } - r.piece = nil - } -} - -// next returns the next piece of data to be read. -// it returns nil at EOF. -func (r *encReader) next() []byte { - switch { - case r.buf == nil: - return nil - - case r.piece != nil: - // There is still data available for reading. - return r.piece - - case r.lhpos < len(r.buf.lheads): - // We're before the last list header. - head := r.buf.lheads[r.lhpos] - sizebefore := head.offset - r.strpos - if sizebefore > 0 { - // String data before header. - p := r.buf.str[r.strpos:head.offset] - r.strpos += sizebefore - return p - } - r.lhpos++ - return head.encode(r.buf.sizebuf[:]) - - case r.strpos < len(r.buf.str): - // String data at the end, after all list headers. - p := r.buf.str[r.strpos:] - r.strpos = len(r.buf.str) - return p - - default: - return nil - } -} - var encoderInterface = reflect.TypeOf(new(Encoder)).Elem() // makeWriter creates a writer function for the given type. -func makeWriter(typ reflect.Type, ts tags) (writer, error) { +func makeWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { kind := typ.Kind() switch { case typ == rawValueType: @@ -357,71 +169,45 @@ func makeWriter(typ reflect.Type, ts tags) (writer, error) { } } -func writeRawValue(val reflect.Value, w *encbuf) error { +func writeRawValue(val reflect.Value, w *encBuffer) error { w.str = append(w.str, val.Bytes()...) return nil } -func writeUint(val reflect.Value, w *encbuf) error { - w.encodeUint(val.Uint()) +func writeUint(val reflect.Value, w *encBuffer) error { + w.writeUint64(val.Uint()) return nil } -func writeBool(val reflect.Value, w *encbuf) error { - if val.Bool() { - w.str = append(w.str, 0x01) - } else { - w.str = append(w.str, 0x80) - } +func writeBool(val reflect.Value, w *encBuffer) error { + w.writeBool(val.Bool()) return nil } -func writeBigIntPtr(val reflect.Value, w *encbuf) error { +func writeBigIntPtr(val reflect.Value, w *encBuffer) error { ptr := val.Interface().(*big.Int) if ptr == nil { w.str = append(w.str, 0x80) return nil } - return writeBigInt(ptr, w) + if ptr.Sign() == -1 { + return ErrNegativeBigInt + } + w.writeBigInt(ptr) + return nil } -func writeBigIntNoPtr(val reflect.Value, w *encbuf) error { +func writeBigIntNoPtr(val reflect.Value, w *encBuffer) error { i := val.Interface().(big.Int) - return writeBigInt(&i, w) -} - -// wordBytes is the number of bytes in a big.Word -const wordBytes = (32 << (uint64(^big.Word(0)) >> 63)) / 8 - -func writeBigInt(i *big.Int, w *encbuf) error { if i.Sign() == -1 { - return fmt.Errorf("rlp: cannot encode negative *big.Int") - } - bitlen := i.BitLen() - if bitlen <= 64 { - w.encodeUint(i.Uint64()) - return nil - } - // Integer is larger than 64 bits, encode from i.Bits(). - // The minimal byte length is bitlen rounded up to the next - // multiple of 8, divided by 8. - length := ((bitlen + 7) & -8) >> 3 - w.encodeStringHeader(length) - w.str = append(w.str, make([]byte, length)...) - index := length - buf := w.str[len(w.str)-length:] - for _, d := range i.Bits() { - for j := 0; j < wordBytes && index > 0; j++ { - index-- - buf[index] = byte(d) - d >>= 8 - } + return ErrNegativeBigInt } + w.writeBigInt(&i) return nil } -func writeBytes(val reflect.Value, w *encbuf) error { - w.encodeString(val.Bytes()) +func writeBytes(val reflect.Value, w *encBuffer) error { + w.writeBytes(val.Bytes()) return nil } @@ -432,16 +218,29 @@ func makeByteArrayWriter(typ reflect.Type) writer { case 1: return writeLengthOneByteArray default: - return writeByteArray + length := typ.Len() + return func(val reflect.Value, w *encBuffer) error { + if !val.CanAddr() { + // 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 + } + slice := byteArrayBytes(val, length) + w.encodeStringHeader(len(slice)) + w.str = append(w.str, slice...) + return nil + } } } -func writeLengthZeroByteArray(val reflect.Value, w *encbuf) error { +func writeLengthZeroByteArray(val reflect.Value, w *encBuffer) error { w.str = append(w.str, 0x80) return nil } -func writeLengthOneByteArray(val reflect.Value, w *encbuf) error { +func writeLengthOneByteArray(val reflect.Value, w *encBuffer) error { b := byte(val.Index(0).Uint()) if b <= 0x7f { w.str = append(w.str, b) @@ -451,22 +250,7 @@ func writeLengthOneByteArray(val reflect.Value, w *encbuf) error { return nil } -func writeByteArray(val reflect.Value, w *encbuf) error { - if !val.CanAddr() { - // 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 - } - - slice := byteArrayBytes(val) - w.encodeStringHeader(len(slice)) - w.str = append(w.str, slice...) - return nil -} - -func writeString(val reflect.Value, w *encbuf) error { +func writeString(val reflect.Value, w *encBuffer) error { s := val.String() if len(s) == 1 && s[0] <= 0x7f { // fits single byte, no string header @@ -478,7 +262,7 @@ func writeString(val reflect.Value, w *encbuf) error { return nil } -func writeInterface(val reflect.Value, w *encbuf) error { +func writeInterface(val reflect.Value, w *encBuffer) error { if val.IsNil() { // Write empty list. This is consistent with the previous RLP // encoder that we had and should therefore avoid any @@ -494,24 +278,44 @@ func writeInterface(val reflect.Value, w *encbuf) error { return writer(eval, w) } -func makeSliceWriter(typ reflect.Type, ts tags) (writer, error) { - etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{}) +func makeSliceWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { + etypeinfo := theTC.infoWhileGenerating(typ.Elem(), rlpstruct.Tags{}) if etypeinfo.writerErr != nil { return nil, etypeinfo.writerErr } - writer := func(val reflect.Value, w *encbuf) error { - if !ts.tail { - defer w.listEnd(w.list()) + + var wfn writer + if ts.Tail { + // This is for struct tail slices. + // w.list is not called for them. + wfn = func(val reflect.Value, w *encBuffer) error { + vlen := val.Len() + for i := 0; i < vlen; i++ { + if err := etypeinfo.writer(val.Index(i), w); err != nil { + return err + } + } + return nil } - vlen := val.Len() - for i := 0; i < vlen; i++ { - if err := etypeinfo.writer(val.Index(i), w); err != nil { - return err + } else { + // This is for regular slices and arrays. + wfn = func(val reflect.Value, w *encBuffer) error { + vlen := val.Len() + if vlen == 0 { + w.str = append(w.str, 0xC0) + return nil } + listOffset := w.list() + for i := 0; i < vlen; i++ { + if err := etypeinfo.writer(val.Index(i), w); err != nil { + return err + } + } + w.listEnd(listOffset) + return nil } - return nil } - return writer, nil + return wfn, nil } func makeStructWriter(typ reflect.Type) (writer, error) { @@ -529,7 +333,7 @@ func makeStructWriter(typ reflect.Type) (writer, error) { 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 { + writer = func(val reflect.Value, w *encBuffer) error { lh := w.list() for _, f := range fields { if err := f.info.writer(val.Field(f.index), w); err != nil { @@ -542,7 +346,7 @@ func makeStructWriter(typ reflect.Type) (writer, error) { } 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 { + writer = func(val reflect.Value, w *encBuffer) error { lastField := len(fields) - 1 for ; lastField >= firstOptionalField; lastField-- { if !val.Field(fields[lastField].index).IsZero() { @@ -562,40 +366,34 @@ func makeStructWriter(typ reflect.Type) (writer, error) { return writer, nil } -func makePtrWriter(typ reflect.Type, ts tags) (writer, error) { - etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{}) +func makePtrWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { + nilEncoding := byte(0xC0) + if typeNilKind(typ.Elem(), ts) == String { + nilEncoding = 0x80 + } + + etypeinfo := theTC.infoWhileGenerating(typ.Elem(), rlpstruct.Tags{}) if etypeinfo.writerErr != nil { return nil, etypeinfo.writerErr } - // Determine how to encode nil pointers. - var nilKind Kind - if ts.nilOK { - nilKind = ts.nilKind // use struct tag if provided - } else { - nilKind = defaultNilKind(typ.Elem()) - } - writer := func(val reflect.Value, w *encbuf) error { - if val.IsNil() { - if nilKind == String { - w.str = append(w.str, 0x80) - } else { - w.listEnd(w.list()) - } - return nil + writer := func(val reflect.Value, w *encBuffer) error { + if ev := val.Elem(); ev.IsValid() { + return etypeinfo.writer(ev, w) } - return etypeinfo.writer(val.Elem(), w) + w.str = append(w.str, nilEncoding) + return nil } return writer, nil } func makeEncoderWriter(typ reflect.Type) writer { if typ.Implements(encoderInterface) { - return func(val reflect.Value, w *encbuf) error { + return func(val reflect.Value, w *encBuffer) error { return val.Interface().(Encoder).EncodeRLP(w) } } - w := func(val reflect.Value, w *encbuf) error { + w := func(val reflect.Value, w *encBuffer) error { if !val.CanAddr() { // package json simply doesn't call MarshalJSON for this case, but encodes the // value as if it didn't implement the interface. We don't want to handle it that diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 25d4aac2..82c490a8 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "math/big" "runtime" "sync" @@ -120,15 +119,15 @@ var encTests = []encTest{ {val: big.NewInt(0xFFFFFFFFFFFF), output: "86FFFFFFFFFFFF"}, {val: big.NewInt(0xFFFFFFFFFFFFFF), output: "87FFFFFFFFFFFFFF"}, { - val: big.NewInt(0).SetBytes(unhex("102030405060708090A0B0C0D0E0F2")), + val: new(big.Int).SetBytes(unhex("102030405060708090A0B0C0D0E0F2")), output: "8F102030405060708090A0B0C0D0E0F2", }, { - val: big.NewInt(0).SetBytes(unhex("0100020003000400050006000700080009000A000B000C000D000E01")), + val: new(big.Int).SetBytes(unhex("0100020003000400050006000700080009000A000B000C000D000E01")), output: "9C0100020003000400050006000700080009000A000B000C000D000E01", }, { - val: big.NewInt(0).SetBytes(unhex("010000000000000000000000000000000000000000000000000000000000000000")), + val: new(big.Int).SetBytes(unhex("010000000000000000000000000000000000000000000000000000000000000000")), output: "A1010000000000000000000000000000000000000000000000000000000000000000", }, { @@ -145,7 +144,8 @@ var encTests = []encTest{ {val: *big.NewInt(0xFFFFFF), output: "83FFFFFF"}, // negative ints are not supported - {val: big.NewInt(-1), error: "rlp: cannot encode negative *big.Int"}, + {val: big.NewInt(-1), error: "rlp: cannot encode negative big.Int"}, + {val: *big.NewInt(-1), error: "rlp: cannot encode negative big.Int"}, // byte arrays {val: [0]byte{}, output: "80"}, @@ -290,6 +290,10 @@ var encTests = []encTest{ {val: &optionalBigIntField{A: 1}, output: "C101"}, {val: &optionalPtrField{A: 1}, output: "C101"}, {val: &optionalPtrFieldNil{A: 1}, output: "C101"}, + {val: &multipleOptionalFields{A: nil, B: nil}, output: "C0"}, + {val: &multipleOptionalFields{A: &[3]byte{1, 2, 3}, B: &[3]byte{1, 2, 3}}, output: "C88301020383010203"}, + {val: &multipleOptionalFields{A: nil, B: &[3]byte{1, 2, 3}}, output: "C58083010203"}, // encodes without error but decode will fail + {val: &nonOptionalPtrField{A: 1}, output: "C20180"}, // encodes without error but decode will fail // nil {val: (*uint)(nil), output: "80"}, @@ -398,13 +402,28 @@ func TestEncodeToBytes(t *testing.T) { runEncTests(t, EncodeToBytes) } +func TestEncodeAppendToBytes(t *testing.T) { + buffer := make([]byte, 20) + runEncTests(t, func(val interface{}) ([]byte, error) { + w := NewEncoderBuffer(nil) + defer w.Flush() + + err := Encode(w, val) + if err != nil { + return nil, err + } + output := w.AppendToBytes(buffer[:0]) + return output, nil + }) +} + func TestEncodeToReader(t *testing.T) { runEncTests(t, func(val interface{}) ([]byte, error) { _, r, err := EncodeToReader(val) if err != nil { return nil, err } - return ioutil.ReadAll(r) + return io.ReadAll(r) }) } @@ -445,7 +464,7 @@ func TestEncodeToReaderReturnToPool(t *testing.T) { go func() { for i := 0; i < 1000; i++ { _, r, _ := EncodeToReader("foo") - ioutil.ReadAll(r) + io.ReadAll(r) r.Read(buf) r.Read(buf) r.Read(buf) @@ -540,3 +559,31 @@ func BenchmarkEncodeByteArrayStruct(b *testing.B) { } } } + +type structSliceElem struct { + X uint64 + Y uint64 + Z uint64 +} + +type structPtrSlice []*structSliceElem + +func BenchmarkEncodeStructPtrSlice(b *testing.B) { + var out bytes.Buffer + var value = structPtrSlice{ + &structSliceElem{1, 1, 1}, + &structSliceElem{2, 2, 2}, + &structSliceElem{3, 3, 3}, + &structSliceElem{5, 5, 5}, + &structSliceElem{6, 6, 6}, + &structSliceElem{7, 7, 7}, + } + + 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/encoder_example_test.go b/rlp/encoder_example_test.go index 42c1c5c8..4cd3cb86 100644 --- a/rlp/encoder_example_test.go +++ b/rlp/encoder_example_test.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 . -package rlp +package rlp_test import ( "fmt" "io" + + "github.com/ethereum/go-ethereum/rlp" ) type MyCoolType struct { @@ -28,16 +30,16 @@ type MyCoolType struct { // EncodeRLP writes x as RLP list [a, b] that omits the Name field. func (x *MyCoolType) EncodeRLP(w io.Writer) (err error) { - return Encode(w, []uint{x.a, x.b}) + return rlp.Encode(w, []uint{x.a, x.b}) } func ExampleEncoder() { var t *MyCoolType // t is nil pointer to MyCoolType - bytes, _ := EncodeToBytes(t) + bytes, _ := rlp.EncodeToBytes(t) fmt.Printf("%v → %X\n", t, bytes) t = &MyCoolType{Name: "foobar", a: 5, b: 6} - bytes, _ = EncodeToBytes(t) + bytes, _ = rlp.EncodeToBytes(t) fmt.Printf("%v → %X\n", t, bytes) // Output: diff --git a/rlp/internal/rlpstruct/rlpstruct.go b/rlp/internal/rlpstruct/rlpstruct.go new file mode 100644 index 00000000..2e3eeb68 --- /dev/null +++ b/rlp/internal/rlpstruct/rlpstruct.go @@ -0,0 +1,213 @@ +// Copyright 2022 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 rlpstruct implements struct processing for RLP encoding/decoding. +// +// In particular, this package handles all rules around field filtering, +// struct tags and nil value determination. +package rlpstruct + +import ( + "fmt" + "reflect" + "strings" +) + +// Field represents a struct field. +type Field struct { + Name string + Index int + Exported bool + Type Type + Tag string +} + +// Type represents the attributes of a Go type. +type Type struct { + Name string + Kind reflect.Kind + IsEncoder bool // whether type implements rlp.Encoder + IsDecoder bool // whether type implements rlp.Decoder + Elem *Type // non-nil for Kind values of Ptr, Slice, Array +} + +// DefaultNilValue determines whether a nil pointer to t encodes/decodes +// as an empty string or empty list. +func (t Type) DefaultNilValue() NilKind { + k := t.Kind + if isUint(k) || k == reflect.String || k == reflect.Bool || isByteArray(t) { + return NilKindString + } + return NilKindList +} + +// NilKind is the RLP value encoded in place of nil pointers. +type NilKind uint8 + +const ( + NilKindString NilKind = 0x80 + NilKindList NilKind = 0xC0 +) + +// Tags represents struct tags. +type Tags struct { + // rlp:"nil" controls whether empty input results in a nil pointer. + // nilKind is the kind of empty value allowed for the field. + NilKind NilKind + 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. + Tail bool + + // rlp:"-" ignores fields. + Ignored bool +} + +// TagError is raised for invalid struct tags. +type TagError struct { + StructType string + + // These are set by this package. + Field string + Tag string + Err string +} + +func (e TagError) Error() string { + field := "field " + e.Field + if e.StructType != "" { + field = e.StructType + "." + e.Field + } + return fmt.Sprintf("rlp: invalid struct tag %q for %s (%s)", e.Tag, field, e.Err) +} + +// ProcessFields filters the given struct fields, returning only fields +// that should be considered for encoding/decoding. +func ProcessFields(allFields []Field) ([]Field, []Tags, error) { + lastPublic := lastPublicField(allFields) + + // Gather all exported fields and their tags. + var fields []Field + var tags []Tags + for _, field := range allFields { + if !field.Exported { + continue + } + ts, err := parseTag(field, lastPublic) + if err != nil { + return nil, nil, err + } + if ts.Ignored { + continue + } + fields = append(fields, field) + tags = append(tags, ts) + } + + // Verify optional field consistency. If any optional field exists, + // all fields after it must also be optional. Note: optional + tail + // is supported. + var anyOptional bool + var firstOptionalName string + for i, ts := range tags { + name := fields[i].Name + if ts.Optional || ts.Tail { + if !anyOptional { + firstOptionalName = name + } + anyOptional = true + } else { + if anyOptional { + msg := fmt.Sprintf("must be optional because preceding field %q is optional", firstOptionalName) + return nil, nil, TagError{Field: name, Err: msg} + } + } + } + return fields, tags, nil +} + +func parseTag(field Field, lastPublic int) (Tags, error) { + name := field.Name + tag := reflect.StructTag(field.Tag) + var ts Tags + for _, t := range strings.Split(tag.Get("rlp"), ",") { + switch t = strings.TrimSpace(t); t { + case "": + // empty tag is allowed for some reason + case "-": + ts.Ignored = true + case "nil", "nilString", "nilList": + ts.NilOK = true + if field.Type.Kind != reflect.Ptr { + return ts, TagError{Field: name, Tag: t, Err: "field is not a pointer"} + } + switch t { + case "nil": + ts.NilKind = field.Type.Elem.DefaultNilValue() + case "nilString": + ts.NilKind = NilKindString + case "nilList": + ts.NilKind = NilKindList + } + case "optional": + ts.Optional = true + if ts.Tail { + return ts, TagError{Field: name, Tag: t, Err: `also has "tail" tag`} + } + case "tail": + ts.Tail = true + if field.Index != lastPublic { + return ts, TagError{Field: name, Tag: t, Err: "must be on last field"} + } + if ts.Optional { + return ts, TagError{Field: name, Tag: t, Err: `also has "optional" tag`} + } + if field.Type.Kind != reflect.Slice { + return ts, TagError{Field: name, Tag: t, Err: "field type is not slice"} + } + default: + return ts, TagError{Field: name, Tag: t, Err: "unknown tag"} + } + } + return ts, nil +} + +func lastPublicField(fields []Field) int { + last := 0 + for _, f := range fields { + if f.Exported { + last = f.Index + } + } + return last +} + +func isUint(k reflect.Kind) bool { + return k >= reflect.Uint && k <= reflect.Uintptr +} + +func isByte(typ Type) bool { + return typ.Kind == reflect.Uint8 && !typ.IsEncoder +} + +func isByteArray(typ Type) bool { + return (typ.Kind == reflect.Slice || typ.Kind == reflect.Array) && isByte(*typ.Elem) +} diff --git a/rlp/iterator.go b/rlp/iterator.go index 559e03a8..6be57457 100644 --- a/rlp/iterator.go +++ b/rlp/iterator.go @@ -1,4 +1,4 @@ -// Copyright 2019 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 @@ -36,7 +36,6 @@ func NewListIterator(data RawValue) (*listIterator, error) { data: data[t : t+c], } return it, nil - } // Next forwards the iterator one step, returns true if it was not at end yet diff --git a/rlp/iterator_test.go b/rlp/iterator_test.go index 53c38191..a22aaec8 100644 --- a/rlp/iterator_test.go +++ b/rlp/iterator_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 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 diff --git a/rlp/raw.go b/rlp/raw.go index f355efc1..773aa7e6 100644 --- a/rlp/raw.go +++ b/rlp/raw.go @@ -28,13 +28,46 @@ type RawValue []byte var rawValueType = reflect.TypeOf(RawValue{}) +// StringSize returns the encoded size of a string. +func StringSize(s string) uint64 { + switch { + case len(s) == 0: + return 1 + case len(s) == 1: + if s[0] <= 0x7f { + return 1 + } else { + return 2 + } + default: + return uint64(headsize(uint64(len(s))) + len(s)) + } +} + +// BytesSize returns the encoded size of a byte slice. +func BytesSize(b []byte) uint64 { + switch { + case len(b) == 0: + return 1 + case len(b) == 1: + if b[0] <= 0x7f { + return 1 + } else { + return 2 + } + default: + return uint64(headsize(uint64(len(b))) + len(b)) + } +} + // ListSize returns the encoded size of an RLP list with the given // content size. func ListSize(contentSize uint64) uint64 { return uint64(headsize(contentSize)) + contentSize } -// IntSize returns the encoded size of the integer x. +// IntSize returns the encoded size of the integer x. Note: The return type of this +// function is 'int' for backwards-compatibility reasons. The result is always positive. func IntSize(x uint64) int { if x < 0x80 { return 1 diff --git a/rlp/raw_test.go b/rlp/raw_test.go index 185e269d..7b3255ec 100644 --- a/rlp/raw_test.go +++ b/rlp/raw_test.go @@ -18,8 +18,8 @@ package rlp import ( "bytes" + "errors" "io" - "reflect" "testing" "testing/quick" ) @@ -54,21 +54,41 @@ func TestCountValues(t *testing.T) { if count != test.count { t.Errorf("test %d: count mismatch, got %d want %d\ninput: %s", i, count, test.count, test.input) } - if !reflect.DeepEqual(err, test.err) { + if !errors.Is(err, test.err) { t.Errorf("test %d: err mismatch, got %q want %q\ninput: %s", i, err, test.err, test.input) } } } -func TestSplitTypes(t *testing.T) { - if _, _, err := SplitString(unhex("C100")); err != ErrExpectedString { - t.Errorf("SplitString returned %q, want %q", err, ErrExpectedString) - } - if _, _, err := SplitList(unhex("01")); err != ErrExpectedList { - t.Errorf("SplitString returned %q, want %q", err, ErrExpectedList) +func TestSplitString(t *testing.T) { + for i, test := range []string{ + "C0", + "C100", + "C3010203", + "C88363617483646F67", + "F8384C6F72656D20697073756D20646F6C6F722073697420616D65742C20636F6E7365637465747572206164697069736963696E6720656C6974", + } { + if _, _, err := SplitString(unhex(test)); !errors.Is(err, ErrExpectedString) { + t.Errorf("test %d: error mismatch: have %q, want %q", i, err, ErrExpectedString) + } } - if _, _, err := SplitList(unhex("81FF")); err != ErrExpectedList { - t.Errorf("SplitString returned %q, want %q", err, ErrExpectedList) +} + +func TestSplitList(t *testing.T) { + for i, test := range []string{ + "80", + "00", + "01", + "8180", + "81FF", + "820400", + "83636174", + "83646F67", + "B8384C6F72656D20697073756D20646F6C6F722073697420616D65742C20636F6E7365637465747572206164697069736963696E6720656C6974", + } { + if _, _, err := SplitList(unhex(test)); !errors.Is(err, ErrExpectedList) { + t.Errorf("test %d: error mismatch: have %q, want %q", i, err, ErrExpectedList) + } } } @@ -283,3 +303,36 @@ func TestAppendUint64Random(t *testing.T) { t.Fatal(err) } } + +func TestBytesSize(t *testing.T) { + tests := []struct { + v []byte + size uint64 + }{ + {v: []byte{}, size: 1}, + {v: []byte{0x1}, size: 1}, + {v: []byte{0x7E}, size: 1}, + {v: []byte{0x7F}, size: 1}, + {v: []byte{0x80}, size: 2}, + {v: []byte{0xFF}, size: 2}, + {v: []byte{0xFF, 0xF0}, size: 3}, + {v: make([]byte, 55), size: 56}, + {v: make([]byte, 56), size: 58}, + } + + for _, test := range tests { + s := BytesSize(test.v) + if s != test.size { + t.Errorf("BytesSize(%#x) -> %d, want %d", test.v, s, test.size) + } + s = StringSize(string(test.v)) + if s != test.size { + t.Errorf("StringSize(%#x) -> %d, want %d", test.v, s, test.size) + } + // Sanity check: + enc, _ := EncodeToBytes(test.v) + if uint64(len(enc)) != test.size { + t.Errorf("len(EncodeToBytes(%#x)) -> %d, test says %d", test.v, len(enc), test.size) + } + } +} diff --git a/rlp/rlpgen/gen.go b/rlp/rlpgen/gen.go new file mode 100644 index 00000000..1deb5a93 --- /dev/null +++ b/rlp/rlpgen/gen.go @@ -0,0 +1,751 @@ +// Copyright 2022 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 ( + "bytes" + "fmt" + "go/format" + "go/types" + "sort" + + "github.com/ethereum/go-ethereum/rlp/internal/rlpstruct" +) + +// buildContext keeps the data needed for make*Op. +type buildContext struct { + topType *types.Named // the type we're creating methods for + + encoderIface *types.Interface + decoderIface *types.Interface + rawValueType *types.Named + + typeToStructCache map[types.Type]*rlpstruct.Type +} + +func newBuildContext(packageRLP *types.Package) *buildContext { + enc := packageRLP.Scope().Lookup("Encoder").Type().Underlying() + dec := packageRLP.Scope().Lookup("Decoder").Type().Underlying() + rawv := packageRLP.Scope().Lookup("RawValue").Type() + return &buildContext{ + typeToStructCache: make(map[types.Type]*rlpstruct.Type), + encoderIface: enc.(*types.Interface), + decoderIface: dec.(*types.Interface), + rawValueType: rawv.(*types.Named), + } +} + +func (bctx *buildContext) isEncoder(typ types.Type) bool { + return types.Implements(typ, bctx.encoderIface) +} + +func (bctx *buildContext) isDecoder(typ types.Type) bool { + return types.Implements(typ, bctx.decoderIface) +} + +// typeToStructType converts typ to rlpstruct.Type. +func (bctx *buildContext) typeToStructType(typ types.Type) *rlpstruct.Type { + if prev := bctx.typeToStructCache[typ]; prev != nil { + return prev // short-circuit for recursive types. + } + + // Resolve named types to their underlying type, but keep the name. + name := types.TypeString(typ, nil) + for { + utype := typ.Underlying() + if utype == typ { + break + } + typ = utype + } + + // Create the type and store it in cache. + t := &rlpstruct.Type{ + Name: name, + Kind: typeReflectKind(typ), + IsEncoder: bctx.isEncoder(typ), + IsDecoder: bctx.isDecoder(typ), + } + bctx.typeToStructCache[typ] = t + + // Assign element type. + switch typ.(type) { + case *types.Array, *types.Slice, *types.Pointer: + etype := typ.(interface{ Elem() types.Type }).Elem() + t.Elem = bctx.typeToStructType(etype) + } + return t +} + +// genContext is passed to the gen* methods of op when generating +// the output code. It tracks packages to be imported by the output +// file and assigns unique names of temporary variables. +type genContext struct { + inPackage *types.Package + imports map[string]struct{} + tempCounter int +} + +func newGenContext(inPackage *types.Package) *genContext { + return &genContext{ + inPackage: inPackage, + imports: make(map[string]struct{}), + } +} + +func (ctx *genContext) temp() string { + v := fmt.Sprintf("_tmp%d", ctx.tempCounter) + ctx.tempCounter++ + return v +} + +func (ctx *genContext) resetTemp() { + ctx.tempCounter = 0 +} + +func (ctx *genContext) addImport(path string) { + if path == ctx.inPackage.Path() { + return // avoid importing the package that we're generating in. + } + // TODO: renaming? + ctx.imports[path] = struct{}{} +} + +// importsList returns all packages that need to be imported. +func (ctx *genContext) importsList() []string { + imp := make([]string, 0, len(ctx.imports)) + for k := range ctx.imports { + imp = append(imp, k) + } + sort.Strings(imp) + return imp +} + +// qualify is the types.Qualifier used for printing types. +func (ctx *genContext) qualify(pkg *types.Package) string { + if pkg.Path() == ctx.inPackage.Path() { + return "" + } + ctx.addImport(pkg.Path()) + // TODO: renaming? + return pkg.Name() +} + +type op interface { + // genWrite creates the encoder. The generated code should write v, + // which is any Go expression, to the rlp.EncoderBuffer 'w'. + genWrite(ctx *genContext, v string) string + + // genDecode creates the decoder. The generated code should read + // a value from the rlp.Stream 'dec' and store it to dst. + genDecode(ctx *genContext) (string, string) +} + +// basicOp handles basic types bool, uint*, string. +type basicOp struct { + typ types.Type + writeMethod string // calle write the value + writeArgType types.Type // parameter type of writeMethod + decMethod string + decResultType types.Type // return type of decMethod + decUseBitSize bool // if true, result bit size is appended to decMethod +} + +func (*buildContext) makeBasicOp(typ *types.Basic) (op, error) { + op := basicOp{typ: typ} + kind := typ.Kind() + switch { + case kind == types.Bool: + op.writeMethod = "WriteBool" + op.writeArgType = types.Typ[types.Bool] + op.decMethod = "Bool" + op.decResultType = types.Typ[types.Bool] + case kind >= types.Uint8 && kind <= types.Uint64: + op.writeMethod = "WriteUint64" + op.writeArgType = types.Typ[types.Uint64] + op.decMethod = "Uint" + op.decResultType = typ + op.decUseBitSize = true + case kind == types.String: + op.writeMethod = "WriteString" + op.writeArgType = types.Typ[types.String] + op.decMethod = "String" + op.decResultType = types.Typ[types.String] + default: + return nil, fmt.Errorf("unhandled basic type: %v", typ) + } + return op, nil +} + +func (*buildContext) makeByteSliceOp(typ *types.Slice) op { + if !isByte(typ.Elem()) { + panic("non-byte slice type in makeByteSliceOp") + } + bslice := types.NewSlice(types.Typ[types.Uint8]) + return basicOp{ + typ: typ, + writeMethod: "WriteBytes", + writeArgType: bslice, + decMethod: "Bytes", + decResultType: bslice, + } +} + +func (bctx *buildContext) makeRawValueOp() op { + bslice := types.NewSlice(types.Typ[types.Uint8]) + return basicOp{ + typ: bctx.rawValueType, + writeMethod: "Write", + writeArgType: bslice, + decMethod: "Raw", + decResultType: bslice, + } +} + +func (op basicOp) writeNeedsConversion() bool { + return !types.AssignableTo(op.typ, op.writeArgType) +} + +func (op basicOp) decodeNeedsConversion() bool { + return !types.AssignableTo(op.decResultType, op.typ) +} + +func (op basicOp) genWrite(ctx *genContext, v string) string { + if op.writeNeedsConversion() { + v = fmt.Sprintf("%s(%s)", op.writeArgType, v) + } + return fmt.Sprintf("w.%s(%s)\n", op.writeMethod, v) +} + +func (op basicOp) genDecode(ctx *genContext) (string, string) { + var ( + resultV = ctx.temp() + result = resultV + method = op.decMethod + ) + if op.decUseBitSize { + // Note: For now, this only works for platform-independent integer + // sizes. makeBasicOp forbids the platform-dependent types. + var sizes types.StdSizes + method = fmt.Sprintf("%s%d", op.decMethod, sizes.Sizeof(op.typ)*8) + } + + // Call the decoder method. + var b bytes.Buffer + fmt.Fprintf(&b, "%s, err := dec.%s()\n", resultV, method) + fmt.Fprintf(&b, "if err != nil { return err }\n") + if op.decodeNeedsConversion() { + conv := ctx.temp() + fmt.Fprintf(&b, "%s := %s(%s)\n", conv, types.TypeString(op.typ, ctx.qualify), resultV) + result = conv + } + return result, b.String() +} + +// byteArrayOp handles [...]byte. +type byteArrayOp struct { + typ types.Type + name types.Type // name != typ for named byte array types (e.g. common.Address) +} + +func (bctx *buildContext) makeByteArrayOp(name *types.Named, typ *types.Array) byteArrayOp { + nt := types.Type(name) + if name == nil { + nt = typ + } + return byteArrayOp{typ, nt} +} + +func (op byteArrayOp) genWrite(ctx *genContext, v string) string { + return fmt.Sprintf("w.WriteBytes(%s[:])\n", v) +} + +func (op byteArrayOp) genDecode(ctx *genContext) (string, string) { + var resultV = ctx.temp() + + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(op.name, ctx.qualify)) + fmt.Fprintf(&b, "if err := dec.ReadBytes(%s[:]); err != nil { return err }\n", resultV) + return resultV, b.String() +} + +// bigIntNoPtrOp handles non-pointer big.Int. +// This exists because big.Int has it's own decoder operation on rlp.Stream, +// but the decode method returns *big.Int, so it needs to be dereferenced. +type bigIntOp struct { + pointer bool +} + +func (op bigIntOp) genWrite(ctx *genContext, v string) string { + var b bytes.Buffer + + fmt.Fprintf(&b, "if %s.Sign() == -1 {\n", v) + fmt.Fprintf(&b, " return rlp.ErrNegativeBigInt\n") + fmt.Fprintf(&b, "}\n") + dst := v + if !op.pointer { + dst = "&" + v + } + fmt.Fprintf(&b, "w.WriteBigInt(%s)\n", dst) + + // Wrap with nil check. + if op.pointer { + code := b.String() + b.Reset() + fmt.Fprintf(&b, "if %s == nil {\n", v) + fmt.Fprintf(&b, " w.Write(rlp.EmptyString)") + fmt.Fprintf(&b, "} else {\n") + fmt.Fprint(&b, code) + fmt.Fprintf(&b, "}\n") + } + + return b.String() +} + +func (op bigIntOp) genDecode(ctx *genContext) (string, string) { + var resultV = ctx.temp() + + var b bytes.Buffer + fmt.Fprintf(&b, "%s, err := dec.BigInt()\n", resultV) + fmt.Fprintf(&b, "if err != nil { return err }\n") + + result := resultV + if !op.pointer { + result = "(*" + resultV + ")" + } + return result, b.String() +} + +// encoderDecoderOp handles rlp.Encoder and rlp.Decoder. +// In order to be used with this, the type must implement both interfaces. +// This restriction may be lifted in the future by creating separate ops for +// encoding and decoding. +type encoderDecoderOp struct { + typ types.Type +} + +func (op encoderDecoderOp) genWrite(ctx *genContext, v string) string { + return fmt.Sprintf("if err := %s.EncodeRLP(w); err != nil { return err }\n", v) +} + +func (op encoderDecoderOp) genDecode(ctx *genContext) (string, string) { + // DecodeRLP must have pointer receiver, and this is verified in makeOp. + etyp := op.typ.(*types.Pointer).Elem() + var resultV = ctx.temp() + + var b bytes.Buffer + fmt.Fprintf(&b, "%s := new(%s)\n", resultV, types.TypeString(etyp, ctx.qualify)) + fmt.Fprintf(&b, "if err := %s.DecodeRLP(dec); err != nil { return err }\n", resultV) + return resultV, b.String() +} + +// ptrOp handles pointer types. +type ptrOp struct { + elemTyp types.Type + elem op + nilOK bool + nilValue rlpstruct.NilKind +} + +func (bctx *buildContext) makePtrOp(elemTyp types.Type, tags rlpstruct.Tags) (op, error) { + elemOp, err := bctx.makeOp(nil, elemTyp, rlpstruct.Tags{}) + if err != nil { + return nil, err + } + op := ptrOp{elemTyp: elemTyp, elem: elemOp} + + // Determine nil value. + if tags.NilOK { + op.nilOK = true + op.nilValue = tags.NilKind + } else { + styp := bctx.typeToStructType(elemTyp) + op.nilValue = styp.DefaultNilValue() + } + return op, nil +} + +func (op ptrOp) genWrite(ctx *genContext, v string) string { + // Note: in writer functions, accesses to v are read-only, i.e. v is any Go + // expression. To make all accesses work through the pointer, we substitute + // v with (*v). This is required for most accesses including `v`, `call(v)`, + // and `v[index]` on slices. + // + // For `v.field` and `v[:]` on arrays, the dereference operation is not required. + var vv string + _, isStruct := op.elem.(structOp) + _, isByteArray := op.elem.(byteArrayOp) + if isStruct || isByteArray { + vv = v + } else { + vv = fmt.Sprintf("(*%s)", v) + } + + var b bytes.Buffer + fmt.Fprintf(&b, "if %s == nil {\n", v) + fmt.Fprintf(&b, " w.Write([]byte{0x%X})\n", op.nilValue) + fmt.Fprintf(&b, "} else {\n") + fmt.Fprintf(&b, " %s", op.elem.genWrite(ctx, vv)) + fmt.Fprintf(&b, "}\n") + return b.String() +} + +func (op ptrOp) genDecode(ctx *genContext) (string, string) { + result, code := op.elem.genDecode(ctx) + if !op.nilOK { + // If nil pointers are not allowed, we can just decode the element. + return "&" + result, code + } + + // nil is allowed, so check the kind and size first. + // If size is zero and kind matches the nilKind of the type, + // the value decodes as a nil pointer. + var ( + resultV = ctx.temp() + kindV = ctx.temp() + sizeV = ctx.temp() + wantKind string + ) + if op.nilValue == rlpstruct.NilKindList { + wantKind = "rlp.List" + } else { + wantKind = "rlp.String" + } + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(types.NewPointer(op.elemTyp), ctx.qualify)) + fmt.Fprintf(&b, "if %s, %s, err := dec.Kind(); err != nil {\n", kindV, sizeV) + fmt.Fprintf(&b, " return err\n") + fmt.Fprintf(&b, "} else if %s != 0 || %s != %s {\n", sizeV, kindV, wantKind) + fmt.Fprint(&b, code) + fmt.Fprintf(&b, " %s = &%s\n", resultV, result) + fmt.Fprintf(&b, "}\n") + return resultV, b.String() +} + +// structOp handles struct types. +type structOp struct { + named *types.Named + typ *types.Struct + fields []*structField + optionalFields []*structField +} + +type structField struct { + name string + typ types.Type + elem op +} + +func (bctx *buildContext) makeStructOp(named *types.Named, typ *types.Struct) (op, error) { + // Convert fields to []rlpstruct.Field. + var allStructFields []rlpstruct.Field + for i := 0; i < typ.NumFields(); i++ { + f := typ.Field(i) + allStructFields = append(allStructFields, rlpstruct.Field{ + Name: f.Name(), + Exported: f.Exported(), + Index: i, + Tag: typ.Tag(i), + Type: *bctx.typeToStructType(f.Type()), + }) + } + + // Filter/validate fields. + fields, tags, err := rlpstruct.ProcessFields(allStructFields) + if err != nil { + return nil, err + } + + // Create field ops. + var op = structOp{named: named, typ: typ} + for i, field := range fields { + // Advanced struct tags are not supported yet. + tag := tags[i] + if err := checkUnsupportedTags(field.Name, tag); err != nil { + return nil, err + } + typ := typ.Field(field.Index).Type() + elem, err := bctx.makeOp(nil, typ, tags[i]) + if err != nil { + return nil, fmt.Errorf("field %s: %v", field.Name, err) + } + f := &structField{name: field.Name, typ: typ, elem: elem} + if tag.Optional { + op.optionalFields = append(op.optionalFields, f) + } else { + op.fields = append(op.fields, f) + } + } + return op, nil +} + +func checkUnsupportedTags(field string, tag rlpstruct.Tags) error { + if tag.Tail { + return fmt.Errorf(`field %s has unsupported struct tag "tail"`, field) + } + return nil +} + +func (op structOp) genWrite(ctx *genContext, v string) string { + var b bytes.Buffer + var listMarker = ctx.temp() + fmt.Fprintf(&b, "%s := w.List()\n", listMarker) + for _, field := range op.fields { + selector := v + "." + field.name + fmt.Fprint(&b, field.elem.genWrite(ctx, selector)) + } + op.writeOptionalFields(&b, ctx, v) + fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker) + return b.String() +} + +func (op structOp) writeOptionalFields(b *bytes.Buffer, ctx *genContext, v string) { + if len(op.optionalFields) == 0 { + return + } + // First check zero-ness of all optional fields. + var zeroV = make([]string, len(op.optionalFields)) + for i, field := range op.optionalFields { + selector := v + "." + field.name + zeroV[i] = ctx.temp() + fmt.Fprintf(b, "%s := %s\n", zeroV[i], nonZeroCheck(selector, field.typ, ctx.qualify)) + } + // Now write the fields. + for i, field := range op.optionalFields { + selector := v + "." + field.name + cond := "" + for j := i; j < len(op.optionalFields); j++ { + if j > i { + cond += " || " + } + cond += zeroV[j] + } + fmt.Fprintf(b, "if %s {\n", cond) + fmt.Fprint(b, field.elem.genWrite(ctx, selector)) + fmt.Fprintf(b, "}\n") + } +} + +func (op structOp) genDecode(ctx *genContext) (string, string) { + // Get the string representation of the type. + // Here, named types are handled separately because the output + // would contain a copy of the struct definition otherwise. + var typeName string + if op.named != nil { + typeName = types.TypeString(op.named, ctx.qualify) + } else { + typeName = types.TypeString(op.typ, ctx.qualify) + } + + // Create struct object. + var resultV = ctx.temp() + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", resultV, typeName) + + // Decode fields. + fmt.Fprintf(&b, "{\n") + fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n") + for _, field := range op.fields { + result, code := field.elem.genDecode(ctx) + fmt.Fprintf(&b, "// %s:\n", field.name) + fmt.Fprint(&b, code) + fmt.Fprintf(&b, "%s.%s = %s\n", resultV, field.name, result) + } + op.decodeOptionalFields(&b, ctx, resultV) + fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n") + fmt.Fprintf(&b, "}\n") + return resultV, b.String() +} + +func (op structOp) decodeOptionalFields(b *bytes.Buffer, ctx *genContext, resultV string) { + var suffix bytes.Buffer + for _, field := range op.optionalFields { + result, code := field.elem.genDecode(ctx) + fmt.Fprintf(b, "// %s:\n", field.name) + fmt.Fprintf(b, "if dec.MoreDataInList() {\n") + fmt.Fprint(b, code) + fmt.Fprintf(b, "%s.%s = %s\n", resultV, field.name, result) + fmt.Fprintf(&suffix, "}\n") + } + suffix.WriteTo(b) +} + +// sliceOp handles slice types. +type sliceOp struct { + typ *types.Slice + elemOp op +} + +func (bctx *buildContext) makeSliceOp(typ *types.Slice) (op, error) { + elemOp, err := bctx.makeOp(nil, typ.Elem(), rlpstruct.Tags{}) + if err != nil { + return nil, err + } + return sliceOp{typ: typ, elemOp: elemOp}, nil +} + +func (op sliceOp) genWrite(ctx *genContext, v string) string { + var ( + listMarker = ctx.temp() // holds return value of w.List() + iterElemV = ctx.temp() // iteration variable + elemCode = op.elemOp.genWrite(ctx, iterElemV) + ) + + var b bytes.Buffer + fmt.Fprintf(&b, "%s := w.List()\n", listMarker) + fmt.Fprintf(&b, "for _, %s := range %s {\n", iterElemV, v) + fmt.Fprint(&b, elemCode) + fmt.Fprintf(&b, "}\n") + fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker) + return b.String() +} + +func (op sliceOp) genDecode(ctx *genContext) (string, string) { + var sliceV = ctx.temp() // holds the output slice + elemResult, elemCode := op.elemOp.genDecode(ctx) + + var b bytes.Buffer + fmt.Fprintf(&b, "var %s %s\n", sliceV, types.TypeString(op.typ, ctx.qualify)) + fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n") + fmt.Fprintf(&b, "for dec.MoreDataInList() {\n") + fmt.Fprintf(&b, " %s", elemCode) + fmt.Fprintf(&b, " %s = append(%s, %s)\n", sliceV, sliceV, elemResult) + fmt.Fprintf(&b, "}\n") + fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n") + return sliceV, b.String() +} + +func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstruct.Tags) (op, error) { + switch typ := typ.(type) { + case *types.Named: + if isBigInt(typ) { + return bigIntOp{}, nil + } + if typ == bctx.rawValueType { + return bctx.makeRawValueOp(), nil + } + if bctx.isDecoder(typ) { + return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ) + } + // TODO: same check for encoder? + return bctx.makeOp(typ, typ.Underlying(), tags) + case *types.Pointer: + if isBigInt(typ.Elem()) { + return bigIntOp{pointer: true}, nil + } + // Encoder/Decoder interfaces. + if bctx.isEncoder(typ) { + if bctx.isDecoder(typ) { + return encoderDecoderOp{typ}, nil + } + return nil, fmt.Errorf("type %v implements rlp.Encoder but not rlp.Decoder", typ) + } + if bctx.isDecoder(typ) { + return nil, fmt.Errorf("type %v implements rlp.Decoder but not rlp.Encoder", typ) + } + // Default pointer handling. + return bctx.makePtrOp(typ.Elem(), tags) + case *types.Basic: + return bctx.makeBasicOp(typ) + case *types.Struct: + return bctx.makeStructOp(name, typ) + case *types.Slice: + etyp := typ.Elem() + if isByte(etyp) && !bctx.isEncoder(etyp) { + return bctx.makeByteSliceOp(typ), nil + } + return bctx.makeSliceOp(typ) + case *types.Array: + etyp := typ.Elem() + if isByte(etyp) && !bctx.isEncoder(etyp) { + return bctx.makeByteArrayOp(name, typ), nil + } + return nil, fmt.Errorf("unhandled array type: %v", typ) + default: + return nil, fmt.Errorf("unhandled type: %v", typ) + } +} + +// generateDecoder generates the DecodeRLP method on 'typ'. +func generateDecoder(ctx *genContext, typ string, op op) []byte { + ctx.resetTemp() + ctx.addImport(pathOfPackageRLP) + + result, code := op.genDecode(ctx) + var b bytes.Buffer + fmt.Fprintf(&b, "func (obj *%s) DecodeRLP(dec *rlp.Stream) error {\n", typ) + fmt.Fprint(&b, code) + fmt.Fprintf(&b, " *obj = %s\n", result) + fmt.Fprintf(&b, " return nil\n") + fmt.Fprintf(&b, "}\n") + return b.Bytes() +} + +// generateEncoder generates the EncodeRLP method on 'typ'. +func generateEncoder(ctx *genContext, typ string, op op) []byte { + ctx.resetTemp() + ctx.addImport("io") + ctx.addImport(pathOfPackageRLP) + + var b bytes.Buffer + fmt.Fprintf(&b, "func (obj *%s) EncodeRLP(_w io.Writer) error {\n", typ) + fmt.Fprintf(&b, " w := rlp.NewEncoderBuffer(_w)\n") + fmt.Fprint(&b, op.genWrite(ctx, "obj")) + fmt.Fprintf(&b, " return w.Flush()\n") + fmt.Fprintf(&b, "}\n") + return b.Bytes() +} + +func (bctx *buildContext) generate(typ *types.Named, encoder, decoder bool) ([]byte, error) { + bctx.topType = typ + + pkg := typ.Obj().Pkg() + op, err := bctx.makeOp(nil, typ, rlpstruct.Tags{}) + if err != nil { + return nil, err + } + + var ( + ctx = newGenContext(pkg) + encSource []byte + decSource []byte + ) + if encoder { + encSource = generateEncoder(ctx, typ.Obj().Name(), op) + } + if decoder { + decSource = generateDecoder(ctx, typ.Obj().Name(), op) + } + + var b bytes.Buffer + fmt.Fprintf(&b, "package %s\n\n", pkg.Name()) + for _, imp := range ctx.importsList() { + fmt.Fprintf(&b, "import %q\n", imp) + } + if encoder { + fmt.Fprintln(&b) + b.Write(encSource) + } + if decoder { + fmt.Fprintln(&b) + b.Write(decSource) + } + + source := b.Bytes() + // fmt.Println(string(source)) + return format.Source(source) +} diff --git a/rlp/rlpgen/gen_test.go b/rlp/rlpgen/gen_test.go new file mode 100644 index 00000000..241c34b6 --- /dev/null +++ b/rlp/rlpgen/gen_test.go @@ -0,0 +1,107 @@ +// Copyright 2022 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 ( + "bytes" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "os" + "path/filepath" + "testing" +) + +// Package RLP is loaded only once and reused for all tests. +var ( + testFset = token.NewFileSet() + testImporter = importer.ForCompiler(testFset, "source", nil).(types.ImporterFrom) + testPackageRLP *types.Package +) + +func init() { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + testPackageRLP, err = testImporter.ImportFrom(pathOfPackageRLP, cwd, 0) + if err != nil { + panic(fmt.Errorf("can't load package RLP: %v", err)) + } +} + +var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint"} + +func TestOutput(t *testing.T) { + for _, test := range tests { + test := test + t.Run(test, func(t *testing.T) { + inputFile := filepath.Join("testdata", test+".in.txt") + outputFile := filepath.Join("testdata", test+".out.txt") + bctx, typ, err := loadTestSource(inputFile, "Test") + if err != nil { + t.Fatal("error loading test source:", err) + } + output, err := bctx.generate(typ, true, true) + if err != nil { + t.Fatal("error in generate:", err) + } + + // Set this environment variable to regenerate the test outputs. + if os.Getenv("WRITE_TEST_FILES") != "" { + os.WriteFile(outputFile, output, 0644) + } + + // Check if output matches. + wantOutput, err := os.ReadFile(outputFile) + if err != nil { + t.Fatal("error loading expected test output:", err) + } + if !bytes.Equal(output, wantOutput) { + t.Fatal("output mismatch:\n", string(output)) + } + }) + } +} + +func loadTestSource(file string, typeName string) (*buildContext, *types.Named, error) { + // Load the test input. + content, err := os.ReadFile(file) + if err != nil { + return nil, nil, err + } + f, err := parser.ParseFile(testFset, file, content, 0) + if err != nil { + return nil, nil, err + } + conf := types.Config{Importer: testImporter} + pkg, err := conf.Check("test", testFset, []*ast.File{f}, nil) + if err != nil { + return nil, nil, err + } + + // Find the test struct. + bctx := newBuildContext(testPackageRLP) + typ, err := lookupStructType(pkg.Scope(), typeName) + if err != nil { + return nil, nil, fmt.Errorf("can't find type %s: %v", typeName, err) + } + return bctx, typ, nil +} diff --git a/rlp/rlpgen/main.go b/rlp/rlpgen/main.go new file mode 100644 index 00000000..25d4393c --- /dev/null +++ b/rlp/rlpgen/main.go @@ -0,0 +1,147 @@ +// Copyright 2022 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 ( + "bytes" + "errors" + "flag" + "fmt" + "go/types" + "os" + + "golang.org/x/tools/go/packages" +) + +const pathOfPackageRLP = "github.com/ethereum/go-ethereum/rlp" + +func main() { + var ( + pkgdir = flag.String("dir", ".", "input package") + output = flag.String("out", "-", "output file (default is stdout)") + genEncoder = flag.Bool("encoder", true, "generate EncodeRLP?") + genDecoder = flag.Bool("decoder", false, "generate DecodeRLP?") + typename = flag.String("type", "", "type to generate methods for") + ) + flag.Parse() + + cfg := Config{ + Dir: *pkgdir, + Type: *typename, + GenerateEncoder: *genEncoder, + GenerateDecoder: *genDecoder, + } + code, err := cfg.process() + if err != nil { + fatal(err) + } + if *output == "-" { + os.Stdout.Write(code) + } else if err := os.WriteFile(*output, code, 0600); err != nil { + fatal(err) + } +} + +func fatal(args ...interface{}) { + fmt.Fprintln(os.Stderr, args...) + os.Exit(1) +} + +type Config struct { + Dir string // input package directory + Type string + + GenerateEncoder bool + GenerateDecoder bool +} + +// process generates the Go code. +func (cfg *Config) process() (code []byte, err error) { + // Load packages. + pcfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps, + Dir: cfg.Dir, + BuildFlags: []string{"-tags", "norlpgen"}, + } + ps, err := packages.Load(pcfg, pathOfPackageRLP, ".") + if err != nil { + return nil, err + } + if len(ps) == 0 { + return nil, fmt.Errorf("no Go package found in %s", cfg.Dir) + } + packages.PrintErrors(ps) + + // Find the packages that were loaded. + var ( + pkg *types.Package + packageRLP *types.Package + ) + for _, p := range ps { + if len(p.Errors) > 0 { + return nil, fmt.Errorf("package %s has errors", p.PkgPath) + } + if p.PkgPath == pathOfPackageRLP { + packageRLP = p.Types + } else { + pkg = p.Types + } + } + bctx := newBuildContext(packageRLP) + + // Find the type and generate. + typ, err := lookupStructType(pkg.Scope(), cfg.Type) + if err != nil { + return nil, fmt.Errorf("can't find %s in %s: %v", cfg.Type, pkg, err) + } + code, err = bctx.generate(typ, cfg.GenerateEncoder, cfg.GenerateDecoder) + if err != nil { + return nil, err + } + + // Add build comments. + // This is done here to avoid processing these lines with gofmt. + var header bytes.Buffer + fmt.Fprint(&header, "// Code generated by rlpgen. DO NOT EDIT.\n\n") + fmt.Fprint(&header, "//go:build !norlpgen\n") + fmt.Fprint(&header, "// +build !norlpgen\n\n") + return append(header.Bytes(), code...), nil +} + +func lookupStructType(scope *types.Scope, name string) (*types.Named, error) { + typ, err := lookupType(scope, name) + if err != nil { + return nil, err + } + _, ok := typ.Underlying().(*types.Struct) + if !ok { + return nil, errors.New("not a struct type") + } + return typ, nil +} + +func lookupType(scope *types.Scope, name string) (*types.Named, error) { + obj := scope.Lookup(name) + if obj == nil { + return nil, errors.New("no such identifier") + } + typ, ok := obj.(*types.TypeName) + if !ok { + return nil, errors.New("not a type") + } + return typ.Type().(*types.Named), nil +} diff --git a/rlp/rlpgen/testdata/bigint.in.txt b/rlp/rlpgen/testdata/bigint.in.txt new file mode 100644 index 00000000..d23d84a2 --- /dev/null +++ b/rlp/rlpgen/testdata/bigint.in.txt @@ -0,0 +1,10 @@ +// -*- mode: go -*- + +package test + +import "math/big" + +type Test struct { + Int *big.Int + IntNoPtr big.Int +} diff --git a/rlp/rlpgen/testdata/bigint.out.txt b/rlp/rlpgen/testdata/bigint.out.txt new file mode 100644 index 00000000..f54d1faa --- /dev/null +++ b/rlp/rlpgen/testdata/bigint.out.txt @@ -0,0 +1,49 @@ +package test + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + if obj.Int == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Int.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.Int) + } + if obj.IntNoPtr.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(&obj.IntNoPtr) + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // Int: + _tmp1, err := dec.BigInt() + if err != nil { + return err + } + _tmp0.Int = _tmp1 + // IntNoPtr: + _tmp2, err := dec.BigInt() + if err != nil { + return err + } + _tmp0.IntNoPtr = (*_tmp2) + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/testdata/nil.in.txt b/rlp/rlpgen/testdata/nil.in.txt new file mode 100644 index 00000000..a28ff344 --- /dev/null +++ b/rlp/rlpgen/testdata/nil.in.txt @@ -0,0 +1,30 @@ +// -*- mode: go -*- + +package test + +type Aux struct{ + A uint32 +} + +type Test struct{ + Uint8 *byte `rlp:"nil"` + Uint8List *byte `rlp:"nilList"` + + Uint32 *uint32 `rlp:"nil"` + Uint32List *uint32 `rlp:"nilList"` + + Uint64 *uint64 `rlp:"nil"` + Uint64List *uint64 `rlp:"nilList"` + + String *string `rlp:"nil"` + StringList *string `rlp:"nilList"` + + ByteArray *[3]byte `rlp:"nil"` + ByteArrayList *[3]byte `rlp:"nilList"` + + ByteSlice *[]byte `rlp:"nil"` + ByteSliceList *[]byte `rlp:"nilList"` + + Struct *Aux `rlp:"nil"` + StructString *Aux `rlp:"nilString"` +} diff --git a/rlp/rlpgen/testdata/nil.out.txt b/rlp/rlpgen/testdata/nil.out.txt new file mode 100644 index 00000000..e0d5dceb --- /dev/null +++ b/rlp/rlpgen/testdata/nil.out.txt @@ -0,0 +1,289 @@ +package test + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + if obj.Uint8 == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64(uint64((*obj.Uint8))) + } + if obj.Uint8List == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteUint64(uint64((*obj.Uint8List))) + } + if obj.Uint32 == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64(uint64((*obj.Uint32))) + } + if obj.Uint32List == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteUint64(uint64((*obj.Uint32List))) + } + if obj.Uint64 == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64((*obj.Uint64)) + } + if obj.Uint64List == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteUint64((*obj.Uint64List)) + } + if obj.String == nil { + w.Write([]byte{0x80}) + } else { + w.WriteString((*obj.String)) + } + if obj.StringList == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteString((*obj.StringList)) + } + if obj.ByteArray == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes(obj.ByteArray[:]) + } + if obj.ByteArrayList == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteBytes(obj.ByteArrayList[:]) + } + if obj.ByteSlice == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes((*obj.ByteSlice)) + } + if obj.ByteSliceList == nil { + w.Write([]byte{0xC0}) + } else { + w.WriteBytes((*obj.ByteSliceList)) + } + if obj.Struct == nil { + w.Write([]byte{0xC0}) + } else { + _tmp1 := w.List() + w.WriteUint64(uint64(obj.Struct.A)) + w.ListEnd(_tmp1) + } + if obj.StructString == nil { + w.Write([]byte{0x80}) + } else { + _tmp2 := w.List() + w.WriteUint64(uint64(obj.StructString.A)) + w.ListEnd(_tmp2) + } + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // Uint8: + var _tmp2 *byte + if _tmp3, _tmp4, err := dec.Kind(); err != nil { + return err + } else if _tmp4 != 0 || _tmp3 != rlp.String { + _tmp1, err := dec.Uint8() + if err != nil { + return err + } + _tmp2 = &_tmp1 + } + _tmp0.Uint8 = _tmp2 + // Uint8List: + var _tmp6 *byte + if _tmp7, _tmp8, err := dec.Kind(); err != nil { + return err + } else if _tmp8 != 0 || _tmp7 != rlp.List { + _tmp5, err := dec.Uint8() + if err != nil { + return err + } + _tmp6 = &_tmp5 + } + _tmp0.Uint8List = _tmp6 + // Uint32: + var _tmp10 *uint32 + if _tmp11, _tmp12, err := dec.Kind(); err != nil { + return err + } else if _tmp12 != 0 || _tmp11 != rlp.String { + _tmp9, err := dec.Uint32() + if err != nil { + return err + } + _tmp10 = &_tmp9 + } + _tmp0.Uint32 = _tmp10 + // Uint32List: + var _tmp14 *uint32 + if _tmp15, _tmp16, err := dec.Kind(); err != nil { + return err + } else if _tmp16 != 0 || _tmp15 != rlp.List { + _tmp13, err := dec.Uint32() + if err != nil { + return err + } + _tmp14 = &_tmp13 + } + _tmp0.Uint32List = _tmp14 + // Uint64: + var _tmp18 *uint64 + if _tmp19, _tmp20, err := dec.Kind(); err != nil { + return err + } else if _tmp20 != 0 || _tmp19 != rlp.String { + _tmp17, err := dec.Uint64() + if err != nil { + return err + } + _tmp18 = &_tmp17 + } + _tmp0.Uint64 = _tmp18 + // Uint64List: + var _tmp22 *uint64 + if _tmp23, _tmp24, err := dec.Kind(); err != nil { + return err + } else if _tmp24 != 0 || _tmp23 != rlp.List { + _tmp21, err := dec.Uint64() + if err != nil { + return err + } + _tmp22 = &_tmp21 + } + _tmp0.Uint64List = _tmp22 + // String: + var _tmp26 *string + if _tmp27, _tmp28, err := dec.Kind(); err != nil { + return err + } else if _tmp28 != 0 || _tmp27 != rlp.String { + _tmp25, err := dec.String() + if err != nil { + return err + } + _tmp26 = &_tmp25 + } + _tmp0.String = _tmp26 + // StringList: + var _tmp30 *string + if _tmp31, _tmp32, err := dec.Kind(); err != nil { + return err + } else if _tmp32 != 0 || _tmp31 != rlp.List { + _tmp29, err := dec.String() + if err != nil { + return err + } + _tmp30 = &_tmp29 + } + _tmp0.StringList = _tmp30 + // ByteArray: + var _tmp34 *[3]byte + if _tmp35, _tmp36, err := dec.Kind(); err != nil { + return err + } else if _tmp36 != 0 || _tmp35 != rlp.String { + var _tmp33 [3]byte + if err := dec.ReadBytes(_tmp33[:]); err != nil { + return err + } + _tmp34 = &_tmp33 + } + _tmp0.ByteArray = _tmp34 + // ByteArrayList: + var _tmp38 *[3]byte + if _tmp39, _tmp40, err := dec.Kind(); err != nil { + return err + } else if _tmp40 != 0 || _tmp39 != rlp.List { + var _tmp37 [3]byte + if err := dec.ReadBytes(_tmp37[:]); err != nil { + return err + } + _tmp38 = &_tmp37 + } + _tmp0.ByteArrayList = _tmp38 + // ByteSlice: + var _tmp42 *[]byte + if _tmp43, _tmp44, err := dec.Kind(); err != nil { + return err + } else if _tmp44 != 0 || _tmp43 != rlp.String { + _tmp41, err := dec.Bytes() + if err != nil { + return err + } + _tmp42 = &_tmp41 + } + _tmp0.ByteSlice = _tmp42 + // ByteSliceList: + var _tmp46 *[]byte + if _tmp47, _tmp48, err := dec.Kind(); err != nil { + return err + } else if _tmp48 != 0 || _tmp47 != rlp.List { + _tmp45, err := dec.Bytes() + if err != nil { + return err + } + _tmp46 = &_tmp45 + } + _tmp0.ByteSliceList = _tmp46 + // Struct: + var _tmp51 *Aux + if _tmp52, _tmp53, err := dec.Kind(); err != nil { + return err + } else if _tmp53 != 0 || _tmp52 != rlp.List { + var _tmp49 Aux + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp50, err := dec.Uint32() + if err != nil { + return err + } + _tmp49.A = _tmp50 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp51 = &_tmp49 + } + _tmp0.Struct = _tmp51 + // StructString: + var _tmp56 *Aux + if _tmp57, _tmp58, err := dec.Kind(); err != nil { + return err + } else if _tmp58 != 0 || _tmp57 != rlp.String { + var _tmp54 Aux + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp55, err := dec.Uint32() + if err != nil { + return err + } + _tmp54.A = _tmp55 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp56 = &_tmp54 + } + _tmp0.StructString = _tmp56 + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/testdata/optional.in.txt b/rlp/rlpgen/testdata/optional.in.txt new file mode 100644 index 00000000..f1ac9f78 --- /dev/null +++ b/rlp/rlpgen/testdata/optional.in.txt @@ -0,0 +1,17 @@ +// -*- mode: go -*- + +package test + +type Aux struct { + A uint64 +} + +type Test struct { + Uint64 uint64 `rlp:"optional"` + Pointer *uint64 `rlp:"optional"` + String string `rlp:"optional"` + Slice []uint64 `rlp:"optional"` + Array [3]byte `rlp:"optional"` + NamedStruct Aux `rlp:"optional"` + AnonStruct struct{ A string } `rlp:"optional"` +} diff --git a/rlp/rlpgen/testdata/optional.out.txt b/rlp/rlpgen/testdata/optional.out.txt new file mode 100644 index 00000000..02df8e45 --- /dev/null +++ b/rlp/rlpgen/testdata/optional.out.txt @@ -0,0 +1,153 @@ +package test + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + _tmp1 := obj.Uint64 != 0 + _tmp2 := obj.Pointer != nil + _tmp3 := obj.String != "" + _tmp4 := len(obj.Slice) > 0 + _tmp5 := obj.Array != ([3]byte{}) + _tmp6 := obj.NamedStruct != (Aux{}) + _tmp7 := obj.AnonStruct != (struct{ A string }{}) + if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + w.WriteUint64(obj.Uint64) + } + if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + if obj.Pointer == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64((*obj.Pointer)) + } + } + if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + w.WriteString(obj.String) + } + if _tmp4 || _tmp5 || _tmp6 || _tmp7 { + _tmp8 := w.List() + for _, _tmp9 := range obj.Slice { + w.WriteUint64(_tmp9) + } + w.ListEnd(_tmp8) + } + if _tmp5 || _tmp6 || _tmp7 { + w.WriteBytes(obj.Array[:]) + } + if _tmp6 || _tmp7 { + _tmp10 := w.List() + w.WriteUint64(obj.NamedStruct.A) + w.ListEnd(_tmp10) + } + if _tmp7 { + _tmp11 := w.List() + w.WriteString(obj.AnonStruct.A) + w.ListEnd(_tmp11) + } + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // Uint64: + if dec.MoreDataInList() { + _tmp1, err := dec.Uint64() + if err != nil { + return err + } + _tmp0.Uint64 = _tmp1 + // Pointer: + if dec.MoreDataInList() { + _tmp2, err := dec.Uint64() + if err != nil { + return err + } + _tmp0.Pointer = &_tmp2 + // String: + if dec.MoreDataInList() { + _tmp3, err := dec.String() + if err != nil { + return err + } + _tmp0.String = _tmp3 + // Slice: + if dec.MoreDataInList() { + var _tmp4 []uint64 + if _, err := dec.List(); err != nil { + return err + } + for dec.MoreDataInList() { + _tmp5, err := dec.Uint64() + if err != nil { + return err + } + _tmp4 = append(_tmp4, _tmp5) + } + if err := dec.ListEnd(); err != nil { + return err + } + _tmp0.Slice = _tmp4 + // Array: + if dec.MoreDataInList() { + var _tmp6 [3]byte + if err := dec.ReadBytes(_tmp6[:]); err != nil { + return err + } + _tmp0.Array = _tmp6 + // NamedStruct: + if dec.MoreDataInList() { + var _tmp7 Aux + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp8, err := dec.Uint64() + if err != nil { + return err + } + _tmp7.A = _tmp8 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp0.NamedStruct = _tmp7 + // AnonStruct: + if dec.MoreDataInList() { + var _tmp9 struct{ A string } + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp10, err := dec.String() + if err != nil { + return err + } + _tmp9.A = _tmp10 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp0.AnonStruct = _tmp9 + } + } + } + } + } + } + } + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/testdata/rawvalue.in.txt b/rlp/rlpgen/testdata/rawvalue.in.txt new file mode 100644 index 00000000..3a657bc9 --- /dev/null +++ b/rlp/rlpgen/testdata/rawvalue.in.txt @@ -0,0 +1,11 @@ +// -*- mode: go -*- + +package test + +import "github.com/ethereum/go-ethereum/rlp" + +type Test struct { + RawValue rlp.RawValue + PointerToRawValue *rlp.RawValue + SliceOfRawValue []rlp.RawValue +} diff --git a/rlp/rlpgen/testdata/rawvalue.out.txt b/rlp/rlpgen/testdata/rawvalue.out.txt new file mode 100644 index 00000000..3607c986 --- /dev/null +++ b/rlp/rlpgen/testdata/rawvalue.out.txt @@ -0,0 +1,64 @@ +package test + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.Write(obj.RawValue) + if obj.PointerToRawValue == nil { + w.Write([]byte{0x80}) + } else { + w.Write((*obj.PointerToRawValue)) + } + _tmp1 := w.List() + for _, _tmp2 := range obj.SliceOfRawValue { + w.Write(_tmp2) + } + w.ListEnd(_tmp1) + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // RawValue: + _tmp1, err := dec.Raw() + if err != nil { + return err + } + _tmp0.RawValue = _tmp1 + // PointerToRawValue: + _tmp2, err := dec.Raw() + if err != nil { + return err + } + _tmp0.PointerToRawValue = &_tmp2 + // SliceOfRawValue: + var _tmp3 []rlp.RawValue + if _, err := dec.List(); err != nil { + return err + } + for dec.MoreDataInList() { + _tmp4, err := dec.Raw() + if err != nil { + return err + } + _tmp3 = append(_tmp3, _tmp4) + } + if err := dec.ListEnd(); err != nil { + return err + } + _tmp0.SliceOfRawValue = _tmp3 + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/testdata/uints.in.txt b/rlp/rlpgen/testdata/uints.in.txt new file mode 100644 index 00000000..8095da99 --- /dev/null +++ b/rlp/rlpgen/testdata/uints.in.txt @@ -0,0 +1,10 @@ +// -*- mode: go -*- + +package test + +type Test struct{ + A uint8 + B uint16 + C uint32 + D uint64 +} diff --git a/rlp/rlpgen/testdata/uints.out.txt b/rlp/rlpgen/testdata/uints.out.txt new file mode 100644 index 00000000..1a354956 --- /dev/null +++ b/rlp/rlpgen/testdata/uints.out.txt @@ -0,0 +1,53 @@ +package test + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Test) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteUint64(uint64(obj.A)) + w.WriteUint64(uint64(obj.B)) + w.WriteUint64(uint64(obj.C)) + w.WriteUint64(obj.D) + w.ListEnd(_tmp0) + return w.Flush() +} + +func (obj *Test) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 Test + { + if _, err := dec.List(); err != nil { + return err + } + // A: + _tmp1, err := dec.Uint8() + if err != nil { + return err + } + _tmp0.A = _tmp1 + // B: + _tmp2, err := dec.Uint16() + if err != nil { + return err + } + _tmp0.B = _tmp2 + // C: + _tmp3, err := dec.Uint32() + if err != nil { + return err + } + _tmp0.C = _tmp3 + // D: + _tmp4, err := dec.Uint64() + if err != nil { + return err + } + _tmp0.D = _tmp4 + if err := dec.ListEnd(); err != nil { + return err + } + } + *obj = _tmp0 + return nil +} diff --git a/rlp/rlpgen/types.go b/rlp/rlpgen/types.go new file mode 100644 index 00000000..19694262 --- /dev/null +++ b/rlp/rlpgen/types.go @@ -0,0 +1,114 @@ +// Copyright 2022 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" + "go/types" + "reflect" +) + +// typeReflectKind gives the reflect.Kind that represents typ. +func typeReflectKind(typ types.Type) reflect.Kind { + switch typ := typ.(type) { + case *types.Basic: + k := typ.Kind() + if k >= types.Bool && k <= types.Complex128 { + // value order matches for Bool..Complex128 + return reflect.Bool + reflect.Kind(k-types.Bool) + } + if k == types.String { + return reflect.String + } + if k == types.UnsafePointer { + return reflect.UnsafePointer + } + panic(fmt.Errorf("unhandled BasicKind %v", k)) + case *types.Array: + return reflect.Array + case *types.Chan: + return reflect.Chan + case *types.Interface: + return reflect.Interface + case *types.Map: + return reflect.Map + case *types.Pointer: + return reflect.Ptr + case *types.Signature: + return reflect.Func + case *types.Slice: + return reflect.Slice + case *types.Struct: + return reflect.Struct + default: + panic(fmt.Errorf("unhandled type %T", typ)) + } +} + +// nonZeroCheck returns the expression that checks whether 'v' is a non-zero value of type 'vtyp'. +func nonZeroCheck(v string, vtyp types.Type, qualify types.Qualifier) string { + // Resolve type name. + typ := resolveUnderlying(vtyp) + switch typ := typ.(type) { + case *types.Basic: + k := typ.Kind() + switch { + case k == types.Bool: + return v + case k >= types.Uint && k <= types.Complex128: + return fmt.Sprintf("%s != 0", v) + case k == types.String: + return fmt.Sprintf(`%s != ""`, v) + default: + panic(fmt.Errorf("unhandled BasicKind %v", k)) + } + case *types.Array, *types.Struct: + return fmt.Sprintf("%s != (%s{})", v, types.TypeString(vtyp, qualify)) + case *types.Interface, *types.Pointer, *types.Signature: + return fmt.Sprintf("%s != nil", v) + case *types.Slice, *types.Map: + return fmt.Sprintf("len(%s) > 0", v) + default: + panic(fmt.Errorf("unhandled type %T", typ)) + } +} + +// isBigInt checks whether 'typ' is "math/big".Int. +func isBigInt(typ types.Type) bool { + named, ok := typ.(*types.Named) + if !ok { + return false + } + name := named.Obj() + return name.Pkg().Path() == "math/big" && name.Name() == "Int" +} + +// isByte checks whether the underlying type of 'typ' is uint8. +func isByte(typ types.Type) bool { + basic, ok := resolveUnderlying(typ).(*types.Basic) + return ok && basic.Kind() == types.Uint8 +} + +func resolveUnderlying(typ types.Type) types.Type { + for { + t := typ.Underlying() + if t == typ { + return t + } + typ = t + } +} diff --git a/rlp/safe.go b/rlp/safe.go index a80380ae..3c910337 100644 --- a/rlp/safe.go +++ b/rlp/safe.go @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +//go:build nacl || js || !cgo // +build nacl js !cgo package rlp @@ -21,6 +22,6 @@ 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() +func byteArrayBytes(v reflect.Value, length int) []byte { + return v.Slice(0, length).Bytes() } diff --git a/rlp/typecache.go b/rlp/typecache.go index 62553d3b..3e37c9d2 100644 --- a/rlp/typecache.go +++ b/rlp/typecache.go @@ -19,9 +19,10 @@ package rlp import ( "fmt" "reflect" - "strings" "sync" "sync/atomic" + + "github.com/ethereum/go-ethereum/rlp/internal/rlpstruct" ) // typeinfo is an entry in the type cache. @@ -32,35 +33,16 @@ type typeinfo struct { writerErr error // error from makeWriter } -// tags represents struct tags. -type tags struct { - // rlp:"nil" controls whether empty input results in a nil pointer. - // 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. - tail bool - - // rlp:"-" ignores fields. - ignored bool -} - // typekey is the key of a type in typeCache. It includes the struct tags because // they might generate a different decoder. type typekey struct { reflect.Type - tags + rlpstruct.Tags } type decoder func(*Stream, reflect.Value) error -type writer func(reflect.Value, *encbuf) error +type writer func(reflect.Value, *encBuffer) error var theTC = newTypeCache() @@ -95,10 +77,10 @@ func (c *typeCache) info(typ reflect.Type) *typeinfo { } // Not in the cache, need to generate info for this type. - return c.generate(typ, tags{}) + return c.generate(typ, rlpstruct.Tags{}) } -func (c *typeCache) generate(typ reflect.Type, tags tags) *typeinfo { +func (c *typeCache) generate(typ reflect.Type, tags rlpstruct.Tags) *typeinfo { c.mu.Lock() defer c.mu.Unlock() @@ -122,7 +104,7 @@ func (c *typeCache) generate(typ reflect.Type, tags tags) *typeinfo { return info } -func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags tags) *typeinfo { +func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags rlpstruct.Tags) *typeinfo { key := typekey{typ, tags} if info := c.next[key]; info != nil { return info @@ -144,35 +126,40 @@ type field struct { // structFields resolves the typeinfo of all public fields in a struct type. func structFields(typ reflect.Type) (fields []field, err error) { - var ( - lastPublic = lastPublicField(typ) - anyOptional = false - ) + // Convert fields to rlpstruct.Field. + var allStructFields []rlpstruct.Field 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 := theTC.infoWhileGenerating(f.Type, tags) - fields = append(fields, field{i, info, tags.optional}) + rf := typ.Field(i) + allStructFields = append(allStructFields, rlpstruct.Field{ + Name: rf.Name, + Index: i, + Exported: rf.PkgPath == "", + Tag: string(rf.Tag), + Type: *rtypeToStructType(rf.Type, nil), + }) + } + + // Filter/validate fields. + structFields, structTags, err := rlpstruct.ProcessFields(allStructFields) + if err != nil { + if tagErr, ok := err.(rlpstruct.TagError); ok { + tagErr.StructType = typ.String() + return nil, tagErr } + return nil, err + } + + // Resolve typeinfo. + for i, sf := range structFields { + typ := typ.Field(sf.Index).Type + tags := structTags[i] + info := theTC.infoWhileGenerating(typ, tags) + fields = append(fields, field{sf.Index, info, tags.Optional}) } return fields, nil } -// anyOptionalFields returns the index of the first field with "optional" tag. +// firstOptionalField returns the index of the first field with "optional" tag. func firstOptionalField(fields []field) int { for i, f := range fields { if f.optional { @@ -192,82 +179,56 @@ func (e structFieldError) Error() string { return fmt.Sprintf("%v (struct field %v.%s)", e.err, e.typ, e.typ.Field(e.field).Name) } -type structTagError struct { - typ reflect.Type - field, tag, err string +func (i *typeinfo) generate(typ reflect.Type, tags rlpstruct.Tags) { + i.decoder, i.decoderErr = makeDecoder(typ, tags) + i.writer, i.writerErr = makeWriter(typ, tags) } -func (e structTagError) Error() string { - return fmt.Sprintf("rlp: invalid struct tag %q for %v.%s (%s)", e.tag, e.typ, e.field, e.err) -} +// rtypeToStructType converts typ to rlpstruct.Type. +func rtypeToStructType(typ reflect.Type, rec map[reflect.Type]*rlpstruct.Type) *rlpstruct.Type { + k := typ.Kind() + if k == reflect.Invalid { + panic("invalid kind") + } -func parseStructTag(typ reflect.Type, fi, lastPublic int) (tags, error) { - f := typ.Field(fi) - var ts tags - for _, t := range strings.Split(f.Tag.Get("rlp"), ",") { - switch t = strings.TrimSpace(t); t { - case "": - case "-": - ts.ignored = true - case "nil", "nilString", "nilList": - ts.nilOK = true - if f.Type.Kind() != reflect.Ptr { - return ts, structTagError{typ, f.Name, t, "field is not a pointer"} - } - switch t { - case "nil": - ts.nilKind = defaultNilKind(f.Type.Elem()) - case "nilString": - ts.nilKind = String - 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"} - } - default: - return ts, fmt.Errorf("rlp: unknown struct tag %q on %v.%s", t, typ, f.Name) - } + if prev := rec[typ]; prev != nil { + return prev // short-circuit for recursive types + } + if rec == nil { + rec = make(map[reflect.Type]*rlpstruct.Type) } - return ts, nil -} -func lastPublicField(typ reflect.Type) int { - last := 0 - for i := 0; i < typ.NumField(); i++ { - if typ.Field(i).PkgPath == "" { - last = i - } + t := &rlpstruct.Type{ + Name: typ.String(), + Kind: k, + IsEncoder: typ.Implements(encoderInterface), + IsDecoder: typ.Implements(decoderInterface), + } + rec[typ] = t + if k == reflect.Array || k == reflect.Slice || k == reflect.Ptr { + t.Elem = rtypeToStructType(typ.Elem(), rec) } - return last + return t } -func (i *typeinfo) generate(typ reflect.Type, tags tags) { - i.decoder, i.decoderErr = makeDecoder(typ, tags) - i.writer, i.writerErr = makeWriter(typ, tags) -} +// typeNilKind gives the RLP value kind for nil pointers to 'typ'. +func typeNilKind(typ reflect.Type, tags rlpstruct.Tags) Kind { + styp := rtypeToStructType(typ, nil) -// defaultNilKind determines whether a nil pointer to typ encodes/decodes -// as an empty string or empty list. -func defaultNilKind(typ reflect.Type) Kind { - k := typ.Kind() - if isUint(k) || k == reflect.String || k == reflect.Bool || isByteArray(typ) { + var nk rlpstruct.NilKind + if tags.NilOK { + nk = tags.NilKind + } else { + nk = styp.DefaultNilValue() + } + switch nk { + case rlpstruct.NilKindString: return String + case rlpstruct.NilKindList: + return List + default: + panic("invalid nil kind value") } - return List } func isUint(k reflect.Kind) bool { @@ -277,7 +238,3 @@ func isUint(k reflect.Kind) bool { func isByte(typ reflect.Type) bool { return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface) } - -func isByteArray(typ reflect.Type) bool { - return (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array) && isByte(typ.Elem()) -} diff --git a/rlp/unsafe.go b/rlp/unsafe.go index 94ed5405..2152ba35 100644 --- a/rlp/unsafe.go +++ b/rlp/unsafe.go @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +//go:build !nacl && !js && cgo // +build !nacl,!js,cgo package rlp @@ -24,12 +25,11 @@ import ( ) // byteArrayBytes returns a slice of the byte array v. -func byteArrayBytes(v reflect.Value) []byte { - len := v.Len() +func byteArrayBytes(v reflect.Value, length int) []byte { var s []byte hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) hdr.Data = v.UnsafeAddr() - hdr.Cap = len - hdr.Len = len + hdr.Cap = length + hdr.Len = length return s } diff --git a/rpc/types.go b/rpc/types.go index d1b878c7..ad068def 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "math" + "strconv" "strings" "github.com/ethereum/go-ethereum/common" @@ -191,3 +192,24 @@ func BlockNumberOrHashWithHash(hash common.Hash, canonical bool) BlockNumberOrHa RequireCanonical: canonical, } } + +// DecimalOrHex unmarshals a non-negative decimal or hex parameter into a uint64. +type DecimalOrHex uint64 + +// UnmarshalJSON implements json.Unmarshaler. +func (dh *DecimalOrHex) UnmarshalJSON(data []byte) error { + input := strings.TrimSpace(string(data)) + if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] + } + + value, err := strconv.ParseUint(input, 10, 64) + if err != nil { + value, err = hexutil.DecodeUint64(input) + } + if err != nil { + return err + } + *dh = DecimalOrHex(value) + return nil +} diff --git a/trie/database.go b/trie/database.go index 1feeba42..e16c80a1 100644 --- a/trie/database.go +++ b/trie/database.go @@ -762,9 +762,10 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner, callback func(common.Hash)) error { // If the node does not exist, it's a previously committed node node, ok := db.dirties[hash] - if !ok { + if !ok || node.node == nil { return nil } + var err error node.forChilds(func(child common.Hash) { if err == nil { diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 2518f7ba..de36829c 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -471,7 +471,6 @@ func (l *loggingDb) NewBatch() ethdb.Batch { } 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) {