From d543cd6a7e0606438ed872a1e2071bae019eb554 Mon Sep 17 00:00:00 2001 From: Quorum Bot <46820074+quorumbot@users.noreply.github.com> Date: Mon, 21 Jun 2021 15:04:09 +0100 Subject: [PATCH] [Upgrade] Go-Ethereum release v1.9.23 (#1217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * params: begin v1.9.23 release cycle * accounts/abi: ABI explicit difference between Unpack and UnpackIntoInterface (#21091) * accounts/abi: refactored abi.Unpack * accounts/abi/bind: fixed error * accounts/abi/bind: modified template * accounts/abi/bind: added ToStruct for conversion * accounts/abi: reenabled tests * accounts/abi: fixed tests * accounts/abi: fixed tests for packing/unpacking * accounts/abi: fixed tests * accounts/abi: added more logic to ToStruct * accounts/abi/bind: fixed template * accounts/abi/bind: fixed ToStruct conversion * accounts/abi/: removed unused code * accounts/abi: updated template * accounts/abi: refactored unused code * contracts/checkpointoracle: updated contracts to sol ^0.6.0 * accounts/abi: refactored reflection logic * accounts/abi: less code duplication in Unpack* * accounts/abi: fixed rebasing bug * fix a few typos in comments * rebase on master Co-authored-by: Guillaume Ballet * mobile: added constructor for big int (#21597) * mobile: added constructor for big int * mobile: tiny nitpick * core/vm, params: make 2200 in line with spec (#21605) * core: free pointer from slice after popping element from price heap (#21572) * Fix potential memory leak in price heap * core: nil free pointer slice (alternative version) Co-authored-by: Martin Holst Swende * internal/web3ext: improve eth_getBlockByNumber and eth_getBlockByHash console api (#21608) * light: fix wrong description in a comment (#21573) * p2p/enode: remove unused code (#21612) * build: keep geth-sources.jar build result for JavaDoc (#21596) * ci: tooltips for javadoc for mobile app * f space * cmd/bootnode,internal/debug: fix some comments (#21623) * trie: use stacktrie for Derivesha operation (#21407) core/types: use stacktrie for derivesha trie: add stacktrie file trie: fix linter core/types: use stacktrie for derivesha rebased: adapt stacktrie to the newer version of DeriveSha Co-authored-by: Martin Holst Swende More linter fixes review feedback: no key offset for nodes converted to hashes trie: use EncodeRLP for full nodes core/types: insert txs in order in derivesha trie: tests for derivesha with stacktrie trie: make stacktrie use pooled hashers trie: make stacktrie reuse tmp slice space trie: minor polishes on stacktrie trie/stacktrie: less rlp dancing core/types: explain the contorsions in DeriveSha ci: fix goimport errors trie: clear mem on subtrie hashing squashme: linter fix stracktrie: use pooling, less allocs (#3) trie: in-place hex prefix, reduce allocs and add rawNode.EncodeRLP Reintroduce the `[]node` method, add the missing `EncodeRLP` implementation for `rawNode` and calculate the hex prefix in place. Co-authored-by: Martin Holst Swende Co-authored-by: Martin Holst Swende * accounts, signer: implement gnosis safe support (#21593) * accounts, signer: implement gnosis safe support * common/math: add type for marshalling big to dec * accounts, signer: properly sign gnosis requests * signer, clef: implement account_signGnosisTx * signer: fix auditlog print, change rpc-name (signGnosisTx to signGnosisSafeTx) * signer: pass validation-messages/warnings to the UI for gnonsis-safe txs * signer/core: minor change to validationmessages of typed data * trie: polishes to trie committer (#21351) * trie: update tests to check commit integrity * trie: polish committer * trie: fix typo * trie: remove hasvalue notion According to the benchmarks, type assertion between the pointer and interface is extremely fast. BenchmarkIntmethod-12 1000000000 1.91 ns/op BenchmarkInterface-12 1000000000 2.13 ns/op BenchmarkTypeSwitch-12 1000000000 1.81 ns/op BenchmarkTypeAssertion-12 2000000000 1.78 ns/op So the overhead for asserting whether the shortnode has "valuenode" child is super tiny. No necessary to have another field. * trie: linter nitpicks Co-authored-by: Martin Holst Swende * trie: add Commit-sequence tests for stacktrie commit (#21643) * core/state/snapshot: stop generator if it hits missing trie nodes (#21649) * core/state/snapshot: exit Geth if generator hits missing trie nodes * core/state/snapshot: error instead of hard die on generator fault * core/state/snapshot: don't enable logging on the tests * cmd/faucet: enable DNS discovery for known networks (#21636) * params: update goerli testnet bootnodes (#21659) * params: update pegasys besu bootnode * params: update goerli initiative bootnodes * core/bloombits: faster generator (#21625) * core/bloombits: add benchmark * core/bloombits: optimize inserts * core/types: optimize bloom filters (#21624) * core/types: tests for bloom * core/types: refactored bloom filter for receipts, added tests core/types: replaced old bloom implementation core/types: change interface of bloom add+test * core/types: refactor bloom * core/types: minor tweak on LogsBloom Co-authored-by: Marius van der Wijden * cmd/devp2p/internal/ethtest: improve eth test suite (#21615) This fixes issues with the protocol handshake and status exchange and adds support for responding to GetBlockHeaders requests. * node: relax websocket connection header check (#21646) This makes it accept the "upgrade,keep-alive" header value, which apparently is a thing. * signer/core: don't mismatch reject and no accounts (#21677) * signer/core: don't mismatch reject and zero accounts, fixes #21674 * signer/core: docs * p2p/discover: remove use of shared hash instance for key derivation (#21673) For some reason, using the shared hash causes a cryptographic incompatibility when using Go 1.15. I noticed this during the development of Discovery v5.1 when I added test vector verification. The go library commit that broke this is golang/go@97240d5, but the way we used HKDF is slightly dodgy anyway and it's not a regression. * core/vm: dedup config check in markdown logger (#21655) * core/vm: dedup config check * review feedback: reuse buffer * eth/downloader: fix data race around the ancientlimit (#21681) * eth/downloader: fix data race around the ancientlimit * eth/downloader: initialize the ancientlimit as 0 * eth/downloader: cache parent hash instead of recomputing (#21678) * core: fix txpool off-by-one error (#21683) * trie: polish commit function (#21692) * trie: polish commit function * trie: fix typo * accouts, consensus, core: fix some comments (#21617) * console: fix admin.sleepBlocks (#21629) * all: replace RWMutex with Mutex in places where RLock is not used (#21622) * consensus/clique: unexport calcDifficulty and improve comment (#21619) * trie: fix flaw in stacktrie pool reuse (#21699) * internal/web3ext: improve some web3 apis (#21639) * imporve some web3-ext apis * Update web3ext.go Co-authored-by: Felix Lange * eth, p2p: use truncated names (#21698) * peer: return localAddr instead of name to prevent spam We currently use the name (which can be freely set by the peer) in several log messages. This enables malicious actors to write spam into your geth log. This commit returns the localAddr instead of the freely settable name. * p2p: reduce usage of peer.Name in warn messages * eth, p2p: use truncated names * Update peer.go Co-authored-by: Marius van der Wijden Co-authored-by: Felix Lange * cmd/geth, cmd/utils: fixed flags name (#21700) * miner: don't interrupt mining after successful sync (#21701) * miner: exit loop when downloader Done or Failed Following the logic of the comment at the method, this fixes a regression introduced at 7cf56d6f064869cb62b1673f9ee437020c595391 , which would allow external parties to DoS with blocks, preventing mining progress. Signed-off-by: meows * miner: remove ineff assign (lint) Signed-off-by: meows * miner: update test re downloader events Signed-off-by: meows * Revert "miner: remove ineff assign (lint)" This reverts commit eaefcd34ab4862ebc936fb8a07578aa2744bc058. * Revert "miner: exit loop when downloader Done or Failed" This reverts commit 23abd34265aa246c38fc390bb72572ad6ae9fe3b. * miner: add test showing imprecise TestMiner Signed-off-by: meows * miner: fix waitForMiningState precision This helper function would return an affirmation on the first positive match on a desired bool. This was imprecise; it return false positives by not waiting initially for an 'updated' value. This fix causes TestMiner_2 to fail, which is expected. Signed-off-by: meows * miner: remove TestMiner_2 demonstrating broken test This test demonstrated the imprecision of the test helper function waitForMiningState. This function has been fixed with 6d365c2851, and this test test may now be removed. Signed-off-by: meows * miner: fix test regarding downloader event/mining expectations See comment for logic. Signed-off-by: meows * miner: add test describing expectations for downloader/mining events We expect that once the downloader emits a DoneEvent, signaling a successful sync, that subsequent StartEvents are not longer permitted to stop the miner. This prevents a security vulnerability where forced syncs via fake high blocks would stall mining operation. Signed-off-by: meows * miner: use 'canStop' state to fix downloader event handling - Break downloader event handling into event separating Done and Failed events. We need to treat these cases differently since a DoneEvent should prevent the miner from being stopped on subsequent downloader Start events. - Use canStop state to handle the one-off case when a downloader first succeeds. Signed-off-by: meows * miner: improve comment wording Signed-off-by: meows * miner: start mining on downloader events iff not already mining Signed-off-by: meows * miner: refactor miner update logic w/r/t downloader events This makes mining pause/start logic regarding downloader events more explicit. Instead of eternally handling downloader events after the first done event, the subscription is closed when downloader events are no longer actionable. Signed-off-by: meows * miner: fix handling downloader events on subcription closed Signed-off-by: meows * miner: (lint:gosimple) use range over chan instead of for/select Signed-off-by: meows * miner: refactor update loop to remove race condition The go routine handling the downloader events handling vars in parallel with the parent routine, causing a race condition. This change, though ugly, remove the condition while still allowing the downloader event subscription to be closed when the miner has no further use for it (ie DoneEvent). * miner: alternate fix for miner-flaw Co-authored-by: meows * accounts/keystore: fix flaky test (#21703) * accounts/keystore: add timeout to test to prevent failure on travis The TestWalletNotifications test sporadically fails on travis. This is because we shutdown the event collection before all events are received. Adding a small timeout (10 milliseconds) allows the collector to be scheduled and to consume all pending events before we shut it down. * accounts/keystore: added newlines back in * accounts/keystore: properly fix the walletNotifications test * params: update CHTs (#21706) * miner: set etherbase even if mining isn't possible at the moment (#21707) * p2p/discover: implement v5.1 wire protocol (#21647) This change implements the Discovery v5.1 wire protocol and also adds an interactive test suite for this protocol. * params: go-ethereum v1.9.23 stable * fix: adapt to #21091 - Update generated code (abigen, done with go generate) - Change Unpack to UnpacIntoInterface - Unused dependency (gotest.tools) * fix: revert contract generation Co-authored-by: Péter Szilágyi Co-authored-by: Marius van der Wijden Co-authored-by: Guillaume Ballet Co-authored-by: Martin Holst Swende Co-authored-by: aaronbuchwald Co-authored-by: mr_franklin Co-authored-by: shigeyuki azuchi Co-authored-by: gary rong Co-authored-by: Felix Lange Co-authored-by: Raw Pong Ghmoa <58883403+q9f@users.noreply.github.com> Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Hanjiang Yu Co-authored-by: Giuseppe Bertone Co-authored-by: meows Co-authored-by: Baptiste Boussemart --- Makefile | 4 +- accounts/abi/abi.go | 60 +- accounts/abi/abi_test.go | 17 +- accounts/abi/argument.go | 54 +- accounts/abi/bind/base.go | 15 +- accounts/abi/bind/base_test.go | 7 +- accounts/abi/bind/bind_test.go | 4 +- accounts/abi/bind/template.go | 34 +- accounts/abi/event_test.go | 22 +- accounts/abi/pack_test.go | 13 +- accounts/abi/packing_test.go | 50 +- accounts/abi/reflect.go | 26 + accounts/abi/reflect_test.go | 70 ++ accounts/abi/unpack_test.go | 60 +- accounts/accounts.go | 2 +- accounts/keystore/file_cache.go | 2 +- accounts/keystore/keystore_test.go | 4 +- accounts/scwallet/wallet.go | 2 +- accounts/usbwallet/wallet.go | 2 +- build/ci.go | 1 + cmd/bootnode/main.go | 2 +- cmd/clef/extapi_changelog.md | 58 ++ cmd/devp2p/README.md | 86 +++ cmd/devp2p/discv4cmd.go | 12 +- cmd/devp2p/discv5cmd.go | 35 + cmd/devp2p/internal/ethtest/chain.go | 53 ++ cmd/devp2p/internal/ethtest/chain_test.go | 150 ++++ cmd/devp2p/internal/ethtest/suite.go | 167 +---- .../internal/ethtest/testdata/chain.rlp.gz | Bin 0 -> 247322 bytes .../internal/ethtest/testdata/genesis.json | 26 + cmd/devp2p/internal/ethtest/types.go | 238 ++++++- cmd/devp2p/internal/v5test/discv5tests.go | 377 ++++++++++ cmd/devp2p/internal/v5test/framework.go | 263 +++++++ cmd/faucet/faucet.go | 2 + cmd/geth/main.go | 4 +- cmd/geth/usage.go | 4 +- cmd/utils/flags.go | 24 +- common/math/big.go | 34 + consensus/clique/clique.go | 14 +- consensus/clique/snapshot_test.go | 2 +- console/bridge.go | 12 +- contracts/checkpointoracle/contract/oracle.go | 68 +- .../checkpointoracle/contract/oracle.sol | 2 +- core/block_validator.go | 4 +- core/bloombits/generator.go | 23 +- core/bloombits/generator_test.go | 39 ++ core/chain_indexer.go | 2 +- core/state/snapshot/generate.go | 19 +- core/state/snapshot/generate_test.go | 190 +++++ core/state/state_object.go | 6 +- core/tx_list.go | 1 + core/tx_pool.go | 1 + core/types/bloom9.go | 100 ++- core/types/bloom9_test.go | 123 +++- core/types/derive_sha.go | 18 +- core/vm/gas_table.go | 34 +- core/vm/logger.go | 11 +- eth/downloader/downloader.go | 7 +- eth/downloader/downloader_test.go | 3 +- eth/downloader/queue.go | 9 +- eth/handler.go | 2 +- .../extensionContracts/contract_extender.go | 286 +++++--- .../extensionContracts/extensionHandler.go | 4 +- extension/extensionContracts/gen.go | 5 + go.mod | 1 - internal/debug/api.go | 2 +- internal/utesting/utesting.go | 26 +- internal/web3ext/web3ext.go | 20 +- les/txrelay.go | 2 +- light/odr.go | 2 +- miner/miner.go | 25 +- miner/miner_test.go | 89 ++- miner/unconfirmed.go | 2 +- mobile/big.go | 10 + mobile/bind.go | 18 +- node/rpcstack.go | 2 +- node/rpcstack_test.go | 15 + p2p/discover/node.go | 5 +- p2p/discover/table_util_test.go | 6 +- p2p/discover/v4_lookup_test.go | 16 +- p2p/discover/v5_encoding.go | 659 ------------------ p2p/discover/v5_encoding_test.go | 373 ---------- p2p/discover/v5_udp.go | 461 ++++++------ p2p/discover/v5_udp_test.go | 317 ++++++--- p2p/discover/v5wire/crypto.go | 180 +++++ p2p/discover/v5wire/crypto_test.go | 124 ++++ p2p/discover/v5wire/encoding.go | 648 +++++++++++++++++ p2p/discover/v5wire/encoding_test.go | 636 +++++++++++++++++ p2p/discover/v5wire/msg.go | 249 +++++++ .../{v5_session.go => v5wire/session.go} | 81 ++- .../testdata/v5.1-ping-handshake-enr.txt | 27 + .../v5wire/testdata/v5.1-ping-handshake.txt | 23 + .../v5wire/testdata/v5.1-ping-message.txt | 10 + .../v5wire/testdata/v5.1-whoareyou.txt | 9 + p2p/enode/node.go | 21 - p2p/netutil/error.go | 8 + p2p/peer.go | 13 +- p2p/server.go | 9 +- params/bootnodes.go | 8 +- params/config.go | 32 +- params/protocol_params.go | 12 +- params/version.go | 2 +- permission/v1/bind/accounts.go | 197 +++--- permission/v1/bind/nodes.go | 73 +- permission/v1/bind/org.go | 193 ++--- permission/v1/bind/permission_impl.go | 164 +++-- permission/v1/bind/permission_interface.go | 155 ++-- permission/v1/bind/permission_upgr.go | 79 ++- permission/v1/bind/roles.go | 148 ++-- permission/v1/bind/voter.go | 42 +- permission/v2/bind/accounts.go | 246 ++++--- permission/v2/bind/nodes.go | 102 ++- permission/v2/bind/org.go | 216 +++--- permission/v2/bind/permission_impl.go | 210 +++--- permission/v2/bind/permission_interface.go | 201 +++--- permission/v2/bind/permission_upgr.go | 77 +- permission/v2/bind/roles.go | 194 ++++-- permission/v2/bind/voter.go | 42 +- signer/core/api.go | 38 +- signer/core/auditlog.go | 22 +- signer/core/cliui.go | 7 + signer/core/gnosis_safe.go | 91 +++ signer/core/signed_data.go | 51 +- signer/core/signed_data_test.go | 117 ++++ tests/fuzzers/abi/abifuzzer.go | 4 +- trie/committer.go | 118 ++-- trie/database.go | 7 +- trie/encoding.go | 29 + trie/encoding_test.go | 36 + trie/hasher.go | 4 +- trie/stacktrie.go | 411 +++++++++++ trie/stacktrie_test.go | 242 +++++++ trie/trie.go | 5 +- trie/trie_test.go | 194 ++++++ 134 files changed, 7644 insertions(+), 3183 deletions(-) create mode 100644 cmd/devp2p/README.md create mode 100644 cmd/devp2p/internal/ethtest/chain_test.go create mode 100755 cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz create mode 100644 cmd/devp2p/internal/ethtest/testdata/genesis.json create mode 100644 cmd/devp2p/internal/v5test/discv5tests.go create mode 100644 cmd/devp2p/internal/v5test/framework.go create mode 100644 core/state/snapshot/generate_test.go create mode 100644 extension/extensionContracts/gen.go delete mode 100644 p2p/discover/v5_encoding.go delete mode 100644 p2p/discover/v5_encoding_test.go create mode 100644 p2p/discover/v5wire/crypto.go create mode 100644 p2p/discover/v5wire/crypto_test.go create mode 100644 p2p/discover/v5wire/encoding.go create mode 100644 p2p/discover/v5wire/encoding_test.go create mode 100644 p2p/discover/v5wire/msg.go rename p2p/discover/{v5_session.go => v5wire/session.go} (58%) create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-message.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-whoareyou.txt create mode 100644 signer/core/gnosis_safe.go create mode 100644 trie/stacktrie.go create mode 100644 trie/stacktrie_test.go diff --git a/Makefile b/Makefile index 0cac496449..66401f747a 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,9 @@ android: $(GORUN) build/ci.go aar --local @echo "Done building." @echo "Import \"$(GOBIN)/geth.aar\" to use the library." - + @echo "Import \"$(GOBIN)/geth-sources.jar\" to add javadocs" + @echo "For more info see https://stackoverflow.com/questions/20994336/android-studio-how-to-attach-javadoc" + ios: $(GORUN) build/ci.go xcode --local @echo "Done building." diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 38196ed25c..5ca7c241db 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -80,36 +80,56 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return append(method.ID, arguments...), nil } -// Unpack output in v according to the abi specification. -func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) { +func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { // since there can't be naming collisions with contracts and events, // we need to decide whether we're calling a method or an event + var args Arguments if method, ok := abi.Methods[name]; ok { if len(data)%32 != 0 { - return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) + return nil, fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) } - return method.Outputs.Unpack(v, data) + args = method.Outputs } if event, ok := abi.Events[name]; ok { - return event.Inputs.Unpack(v, data) + args = event.Inputs } - return fmt.Errorf("abi: could not locate named method or event") + if args == nil { + return nil, errors.New("abi: could not locate named method or event") + } + return args, nil +} + +// Unpack unpacks the output according to the abi specification. +func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) { + args, err := abi.getArguments(name, data) + if err != nil { + return nil, err + } + return args.Unpack(data) +} + +// UnpackIntoInterface unpacks the output in v according to the abi specification. +// It performs an additional copy. Please only use, if you want to unpack into a +// structure that does not strictly conform to the abi structure (e.g. has additional arguments) +func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + unpacked, err := args.Unpack(data) + if err != nil { + return err + } + return args.Copy(v, unpacked) } // UnpackIntoMap unpacks a log into the provided map[string]interface{}. func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { - // since there can't be naming collisions with contracts and events, - // we need to decide whether we're calling a method or an event - if method, ok := abi.Methods[name]; ok { - if len(data)%32 != 0 { - return fmt.Errorf("abi: improperly formatted output") - } - return method.Outputs.UnpackIntoMap(v, data) - } - if event, ok := abi.Events[name]; ok { - return event.Inputs.UnpackIntoMap(v, data) + args, err := abi.getArguments(name, data) + if err != nil { + return err } - return fmt.Errorf("abi: could not locate named method or event") + return args.UnpackIntoMap(v, data) } // UnmarshalJSON implements json.Unmarshaler interface. @@ -250,10 +270,10 @@ func UnpackRevert(data []byte) (string, error) { if !bytes.Equal(data[:4], revertSelector) { return "", errors.New("invalid data for unpacking") } - var reason string typ, _ := NewType("string", "", nil) - if err := (Arguments{{Type: typ}}).Unpack(&reason, data[4:]); err != nil { + unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) + if err != nil { return "", err } - return reason, nil + return unpacked[0].(string), nil } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index f7d7f7aa62..ad8acdf522 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -181,18 +181,15 @@ func TestConstructor(t *testing.T) { if err != nil { t.Error(err) } - v := struct { - A *big.Int - B *big.Int - }{new(big.Int), new(big.Int)} - //abi.Unpack(&v, "", packed) - if err := abi.Constructor.Inputs.Unpack(&v, packed); err != nil { + unpacked, err := abi.Constructor.Inputs.Unpack(packed) + if err != nil { t.Error(err) } - if !reflect.DeepEqual(v.A, big.NewInt(1)) { + + if !reflect.DeepEqual(unpacked[0], big.NewInt(1)) { t.Error("Unable to pack/unpack from constructor") } - if !reflect.DeepEqual(v.B, big.NewInt(2)) { + if !reflect.DeepEqual(unpacked[1], big.NewInt(2)) { t.Error("Unable to pack/unpack from constructor") } } @@ -743,7 +740,7 @@ func TestUnpackEvent(t *testing.T) { } var ev ReceivedEvent - err = abi.Unpack(&ev, "received", data) + err = abi.UnpackIntoInterface(&ev, "received", data) if err != nil { t.Error(err) } @@ -752,7 +749,7 @@ func TestUnpackEvent(t *testing.T) { Sender common.Address } var receivedAddrEv ReceivedAddrEvent - err = abi.Unpack(&receivedAddrEv, "receivedAddr", data) + err = abi.UnpackIntoInterface(&receivedAddrEv, "receivedAddr", data) if err != nil { t.Error(err) } diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index aaef3bd41c..e6d5245596 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -76,28 +76,20 @@ func (arguments Arguments) isTuple() bool { } // Unpack performs the operation hexdata -> Go format. -func (arguments Arguments) Unpack(v interface{}, data []byte) error { +func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { if len(data) == 0 { if len(arguments) != 0 { - return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") + return nil, fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") } - return nil // Nothing to unmarshal, return - } - // make sure the passed value is arguments pointer - if reflect.Ptr != reflect.ValueOf(v).Kind() { - return fmt.Errorf("abi: Unpack(non-pointer %T)", v) - } - marshalledValues, err := arguments.UnpackValues(data) - if err != nil { - return err - } - if len(marshalledValues) == 0 { - return fmt.Errorf("abi: Unpack(no-values unmarshalled %T)", v) - } - if arguments.isTuple() { - return arguments.unpackTuple(v, marshalledValues) + // Nothing to unmarshal, return default variables + nonIndexedArgs := arguments.NonIndexed() + defaultVars := make([]interface{}, len(nonIndexedArgs)) + for index, arg := range nonIndexedArgs { + defaultVars[index] = reflect.New(arg.Type.GetType()) + } + return defaultVars, nil } - return arguments.unpackAtomic(v, marshalledValues[0]) + return arguments.UnpackValues(data) } // UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value. @@ -122,8 +114,26 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) return nil } -// unpackAtomic unpacks ( hexdata -> go ) a single value. -func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { +// Copy performs the operation go format -> provided struct. +func (arguments Arguments) Copy(v interface{}, values []interface{}) error { + // make sure the passed value is arguments pointer + if reflect.Ptr != reflect.ValueOf(v).Kind() { + return fmt.Errorf("abi: Unpack(non-pointer %T)", v) + } + if len(values) == 0 { + if len(arguments) != 0 { + return fmt.Errorf("abi: attempting to copy no values while %d arguments are expected", len(arguments)) + } + return nil // Nothing to copy, return + } + if arguments.isTuple() { + return arguments.copyTuple(v, values) + } + return arguments.copyAtomic(v, values[0]) +} + +// unpackAtomic unpacks ( hexdata -> go ) a single value +func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error { dst := reflect.ValueOf(v).Elem() src := reflect.ValueOf(marshalledValues) @@ -133,8 +143,8 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interfac return set(dst, src) } -// unpackTuple unpacks ( hexdata -> go ) a batch of values. -func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { +// copyTuple copies a batch of values from marshalledValues to v. +func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface{}) error { value := reflect.ValueOf(v).Elem() nonIndexedArgs := arguments.NonIndexed() diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index c7088e7b78..e48d6df0c4 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -128,11 +128,14 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error { +func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error { // Don't crash on a lazy user if opts == nil { opts = new(CallOpts) } + if results == nil { + results = new([]interface{}) + } // Pack the input, call and unpack the results input, err := c.abi.Pack(method, params...) if err != nil { @@ -169,10 +172,14 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, } } } - if err != nil { + + if len(*results) == 0 { + res, err := c.abi.Unpack(method, output) + *results = res return err } - return c.abi.Unpack(result, method, output) + res := *results + return c.abi.UnpackIntoInterface(res[0], method, output) } // Transact invokes the (paid) contract method with params as input values. @@ -372,7 +379,7 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter // UnpackLog unpacks a retrieved log into the provided output structure. func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error { if len(log.Data) > 0 { - if err := c.abi.Unpack(out, event, log.Data); err != nil { + if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { return err } } diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 7d287850f4..c4740f68b7 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -71,11 +71,10 @@ func TestPassingBlockNumber(t *testing.T) { }, }, }, mc, nil, nil) - var ret string blockNumber := big.NewInt(42) - bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, &ret, "something") + bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, nil, "something") if mc.callContractBlockNumber != blockNumber { t.Fatalf("CallContract() was not passed the block number") @@ -85,7 +84,7 @@ func TestPassingBlockNumber(t *testing.T) { t.Fatalf("CodeAt() was not passed the block number") } - bc.Call(&bind.CallOpts{}, &ret, "something") + bc.Call(&bind.CallOpts{}, nil, "something") if mc.callContractBlockNumber != nil { t.Fatalf("CallContract() was passed a block number when it should not have been") @@ -95,7 +94,7 @@ func TestPassingBlockNumber(t *testing.T) { t.Fatalf("CodeAt() was passed a block number when it should not have been") } - bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, &ret, "something") + bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, nil, "something") if !mc.pendingCallContractCalled { t.Fatalf("CallContract() was not passed the block number") diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 86d85f35af..fd301bbead 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1696,11 +1696,11 @@ func TestGolangBindings(t *testing.T) { t.Skip("go sdk not found for testing") } // Create a temporary workspace for the test suite - ws, err := ioutil.TempDir("", "") + ws, err := ioutil.TempDir("", "binding-test") if err != nil { t.Fatalf("failed to create temporary workspace: %v", err) } - defer os.RemoveAll(ws) + //defer os.RemoveAll(ws) pkg := filepath.Join(ws, "bindtest") if err = os.MkdirAll(pkg, 0700); err != nil { diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 4fbdcdfca2..9e15a3d0a1 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -263,7 +263,7 @@ var ( // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. - func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...) } @@ -282,7 +282,7 @@ var ( // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. - func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...) } @@ -302,19 +302,23 @@ var ( // // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) { - {{if .Structured}}ret := new(struct{ - {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}} - {{end}} - }){{else}}var ( - {{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type $structs}}) - {{end}} - ){{end}} - out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{ - {{range $i, $_ := .Normalized.Outputs}}ret{{$i}}, - {{end}} - }{{end}}{{end}} - err := _{{$contract.Type}}.contract.Call(opts, out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) - return {{if .Structured}}*ret,{{else}}{{range $i, $_ := .Normalized.Outputs}}*ret{{$i}},{{end}}{{end}} err + var out []interface{} + err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + {{if .Structured}} + outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + {{range $i, $t := .Normalized.Outputs}} + outstruct.{{.Name}} = out[{{$i}}].({{bindtype .Type $structs}}){{end}} + + return *outstruct, err + {{else}} + if err != nil { + return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err + } + {{range $i, $t := .Normalized.Outputs}} + out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err + {{end}} } // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index 79504c28ce..3332f8a072 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -147,10 +147,6 @@ func TestEventString(t *testing.T) { // TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array. func TestEventMultiValueWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` - type testStruct struct { - Value1 [2]uint8 - Value2 uint8 - } abi, err := JSON(strings.NewReader(definition)) require.NoError(t, err) var b bytes.Buffer @@ -158,10 +154,10 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) { for ; i <= 3; i++ { b.Write(packNum(reflect.ValueOf(i))) } - var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) - require.Equal(t, [2]uint8{1, 2}, rst.Value1) - require.Equal(t, uint8(3), rst.Value2) + unpacked, err := abi.Unpack("test", b.Bytes()) + require.NoError(t, err) + require.Equal(t, [2]uint8{1, 2}, unpacked[0]) + require.Equal(t, uint8(3), unpacked[1]) } func TestEventTupleUnpack(t *testing.T) { @@ -351,14 +347,14 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass var e Event assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI") a := ABI{Events: map[string]Event{"e": e}} - return a.Unpack(dest, "e", data) + return a.UnpackIntoInterface(dest, "e", data) } // TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder. func TestEventUnpackIndexed(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` type testStruct struct { - Value1 uint8 + Value1 uint8 // indexed Value2 uint8 } abi, err := JSON(strings.NewReader(definition)) @@ -366,7 +362,7 @@ func TestEventUnpackIndexed(t *testing.T) { var b bytes.Buffer b.Write(packNum(reflect.ValueOf(uint8(8)))) var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) require.Equal(t, uint8(0), rst.Value1) require.Equal(t, uint8(8), rst.Value2) } @@ -375,7 +371,7 @@ func TestEventUnpackIndexed(t *testing.T) { func TestEventIndexedWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]` type testStruct struct { - Value1 [2]uint8 + Value1 [2]uint8 // indexed Value2 string } abi, err := JSON(strings.NewReader(definition)) @@ -388,7 +384,7 @@ func TestEventIndexedWithArrayUnpack(t *testing.T) { b.Write(common.RightPadBytes([]byte(stringOut), 32)) var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) require.Equal(t, [2]uint8{0, 0}, rst.Value1) require.Equal(t, stringOut, rst.Value2) } diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index 284215a7d7..5c7cb1cc1a 100644 --- a/accounts/abi/pack_test.go +++ b/accounts/abi/pack_test.go @@ -44,18 +44,7 @@ func TestPack(t *testing.T) { t.Fatalf("invalid ABI definition %s, %v", inDef, err) } var packed []byte - if reflect.TypeOf(test.unpacked).Kind() != reflect.Struct { - packed, err = inAbi.Pack("method", test.unpacked) - } else { - // if want is a struct we need to use the components. - elem := reflect.ValueOf(test.unpacked) - var values []interface{} - for i := 0; i < elem.NumField(); i++ { - field := elem.Field(i) - values = append(values, field.Interface()) - } - packed, err = inAbi.Pack("method", values...) - } + packed, err = inAbi.Pack("method", test.unpacked) if err != nil { t.Fatalf("test %d (%v) failed: %v", i, test.def, err) diff --git a/accounts/abi/packing_test.go b/accounts/abi/packing_test.go index 16b4dc43d7..eae3b0df20 100644 --- a/accounts/abi/packing_test.go +++ b/accounts/abi/packing_test.go @@ -620,7 +620,7 @@ var packUnpackTests = []packUnpackTest{ { def: `[{"type": "bytes32[]"}]`, - unpacked: []common.Hash{{1}, {2}}, + unpacked: [][32]byte{{1}, {2}}, packed: "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000002" + "0100000000000000000000000000000000000000000000000000000000000000" + @@ -722,7 +722,7 @@ var packUnpackTests = []packUnpackTest{ }, // struct outputs { - def: `[{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}]`, + def: `[{"components": [{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002", unpacked: struct { @@ -731,28 +731,28 @@ var packUnpackTests = []packUnpackTest{ }{big.NewInt(1), big.NewInt(2)}, }, { - def: `[{"name":"int_one","type":"int256"}]`, + def: `[{"components": [{"name":"int_one","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001", unpacked: struct { IntOne *big.Int }{big.NewInt(1)}, }, { - def: `[{"name":"int__one","type":"int256"}]`, + def: `[{"components": [{"name":"int__one","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001", unpacked: struct { IntOne *big.Int }{big.NewInt(1)}, }, { - def: `[{"name":"int_one_","type":"int256"}]`, + def: `[{"components": [{"name":"int_one_","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001", unpacked: struct { IntOne *big.Int }{big.NewInt(1)}, }, { - def: `[{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}]`, + def: `[{"components": [{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002", unpacked: struct { @@ -831,11 +831,11 @@ var packUnpackTests = []packUnpackTest{ }, { // static tuple - def: `[{"name":"a","type":"int64"}, + def: `[{"components": [{"name":"a","type":"int64"}, {"name":"b","type":"int256"}, {"name":"c","type":"int256"}, {"name":"d","type":"bool"}, - {"name":"e","type":"bytes32[3][2]"}]`, + {"name":"e","type":"bytes32[3][2]"}], "type":"tuple"}]`, unpacked: struct { A int64 B *big.Int @@ -855,21 +855,22 @@ var packUnpackTests = []packUnpackTest{ "0500000000000000000000000000000000000000000000000000000000000000", // struct[e] array[1][2] }, { - def: `[{"name":"a","type":"string"}, + def: `[{"components": [{"name":"a","type":"string"}, {"name":"b","type":"int64"}, {"name":"c","type":"bytes"}, {"name":"d","type":"string[]"}, {"name":"e","type":"int256[]"}, - {"name":"f","type":"address[]"}]`, + {"name":"f","type":"address[]"}], "type":"tuple"}]`, unpacked: struct { - FieldA string `abi:"a"` // Test whether abi tag works - FieldB int64 `abi:"b"` - C []byte - D []string - E []*big.Int - F []common.Address + A string + B int64 + C []byte + D []string + E []*big.Int + F []common.Address }{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}}, - packed: "00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset + packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a + "00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] "0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset "0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset @@ -894,23 +895,24 @@ var packUnpackTests = []packUnpackTest{ "0000000000000000000000000200000000000000000000000000000000000000", // common.Address{2} }, { - def: `[{"components": [{"name": "a","type": "uint256"}, + def: `[{"components": [{ "type": "tuple","components": [{"name": "a","type": "uint256"}, {"name": "b","type": "uint256[]"}], "name": "a","type": "tuple"}, - {"name": "b","type": "uint256[]"}]`, + {"name": "b","type": "uint256[]"}], "type": "tuple"}]`, unpacked: struct { A struct { - FieldA *big.Int `abi:"a"` - B []*big.Int + A *big.Int + B []*big.Int } B []*big.Int }{ A: struct { - FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple - B []*big.Int + A *big.Int + B []*big.Int }{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(2)}}, B: []*big.Int{big.NewInt(1), big.NewInt(2)}}, - packed: "0000000000000000000000000000000000000000000000000000000000000040" + // a offset + packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a + "0000000000000000000000000000000000000000000000000000000000000040" + // a offset "00000000000000000000000000000000000000000000000000000000000000e0" + // b offset "0000000000000000000000000000000000000000000000000000000000000001" + // a.a value "0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index f4812b06bf..11248e0730 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -24,6 +24,29 @@ import ( "strings" ) +// ConvertType converts an interface of a runtime type into a interface of the +// given type +// e.g. turn +// var fields []reflect.StructField +// fields = append(fields, reflect.StructField{ +// Name: "X", +// Type: reflect.TypeOf(new(big.Int)), +// Tag: reflect.StructTag("json:\"" + "x" + "\""), +// } +// into +// type TupleT struct { X *big.Int } +func ConvertType(in interface{}, proto interface{}) interface{} { + protoType := reflect.TypeOf(proto) + if reflect.TypeOf(in).ConvertibleTo(protoType) { + return reflect.ValueOf(in).Convert(protoType).Interface() + } + // Use set as a last ditch effort + if err := set(reflect.ValueOf(proto), reflect.ValueOf(in)); err != nil { + panic(err) + } + return proto +} + // indirect recursively dereferences the value until it either gets the value // or finds a big.Int func indirect(v reflect.Value) reflect.Value { @@ -119,6 +142,9 @@ func setSlice(dst, src reflect.Value) error { } func setArray(dst, src reflect.Value) error { + if src.Kind() == reflect.Ptr { + return set(dst, indirect(src)) + } array := reflect.New(dst.Type()).Elem() min := src.Len() if src.Len() > dst.Len() { diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go index c425e6e54b..bac4cd9530 100644 --- a/accounts/abi/reflect_test.go +++ b/accounts/abi/reflect_test.go @@ -17,6 +17,7 @@ package abi import ( + "math/big" "reflect" "testing" ) @@ -189,3 +190,72 @@ func TestReflectNameToStruct(t *testing.T) { }) } } + +func TestConvertType(t *testing.T) { + // Test Basic Struct + type T struct { + X *big.Int + Y *big.Int + } + // Create on-the-fly structure + var fields []reflect.StructField + fields = append(fields, reflect.StructField{ + Name: "X", + Type: reflect.TypeOf(new(big.Int)), + Tag: reflect.StructTag("json:\"" + "x" + "\""), + }) + fields = append(fields, reflect.StructField{ + Name: "Y", + Type: reflect.TypeOf(new(big.Int)), + Tag: reflect.StructTag("json:\"" + "y" + "\""), + }) + val := reflect.New(reflect.StructOf(fields)) + val.Elem().Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val.Elem().Field(1).Set(reflect.ValueOf(big.NewInt(2))) + // ConvertType + out := *ConvertType(val.Interface(), new(T)).(*T) + if out.X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out.X, big.NewInt(1)) + } + if out.Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out.Y, big.NewInt(2)) + } + // Slice Type + val2 := reflect.MakeSlice(reflect.SliceOf(reflect.StructOf(fields)), 2, 2) + val2.Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val2.Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) + val2.Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) + val2.Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out2 := *ConvertType(val2.Interface(), new([]T)).(*[]T) + if out2[0].X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) + } + if out2[0].Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) + } + if out2[1].X.Cmp(big.NewInt(3)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) + } + if out2[1].Y.Cmp(big.NewInt(4)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) + } + // Array Type + val3 := reflect.New(reflect.ArrayOf(2, reflect.StructOf(fields))) + val3.Elem().Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val3.Elem().Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) + val3.Elem().Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) + val3.Elem().Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out3 := *ConvertType(val3.Interface(), new([2]T)).(*[2]T) + if out3[0].X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) + } + if out3[0].Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) + } + if out3[1].X.Cmp(big.NewInt(3)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) + } + if out3[1].Y.Cmp(big.NewInt(4)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) + } +} diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 767d1540e6..b88f77805b 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -44,15 +44,13 @@ func TestUnpack(t *testing.T) { if err != nil { t.Fatalf("invalid hex %s: %v", test.packed, err) } - outptr := reflect.New(reflect.TypeOf(test.unpacked)) - err = abi.Unpack(outptr.Interface(), "method", encb) + out, err := abi.Unpack("method", encb) if err != nil { t.Errorf("test %d (%v) failed: %v", i, test.def, err) return } - out := outptr.Elem().Interface() - if !reflect.DeepEqual(test.unpacked, out) { - t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.unpacked, out) + if !reflect.DeepEqual(test.unpacked, ConvertType(out[0], test.unpacked)) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.unpacked, out[0]) } }) } @@ -221,7 +219,7 @@ func TestLocalUnpackTests(t *testing.T) { t.Fatalf("invalid hex %s: %v", test.enc, err) } outptr := reflect.New(reflect.TypeOf(test.want)) - err = abi.Unpack(outptr.Interface(), "method", encb) + err = abi.UnpackIntoInterface(outptr.Interface(), "method", encb) if err := test.checkError(err); err != nil { t.Errorf("test %d (%v) failed: %v", i, test.def, err) return @@ -234,7 +232,7 @@ func TestLocalUnpackTests(t *testing.T) { } } -func TestUnpackSetDynamicArrayOutput(t *testing.T) { +func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) { abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`)) if err != nil { t.Fatal(err) @@ -249,7 +247,7 @@ func TestUnpackSetDynamicArrayOutput(t *testing.T) { ) // test 32 - err = abi.Unpack(&out32, "testDynamicFixedBytes32", marshalledReturn32) + err = abi.UnpackIntoInterface(&out32, "testDynamicFixedBytes32", marshalledReturn32) if err != nil { t.Fatal(err) } @@ -266,7 +264,7 @@ func TestUnpackSetDynamicArrayOutput(t *testing.T) { } // test 15 - err = abi.Unpack(&out15, "testDynamicFixedBytes32", marshalledReturn15) + err = abi.UnpackIntoInterface(&out15, "testDynamicFixedBytes32", marshalledReturn15) if err != nil { t.Fatal(err) } @@ -367,7 +365,7 @@ func TestMethodMultiReturn(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { require := require.New(t) - err := abi.Unpack(tc.dest, "multi", data) + err := abi.UnpackIntoInterface(tc.dest, "multi", data) if tc.error == "" { require.Nil(err, "Should be able to unpack method outputs.") require.Equal(tc.expected, tc.dest) @@ -390,7 +388,7 @@ func TestMultiReturnWithArray(t *testing.T) { ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9} ret2, ret2Exp := new(uint64), uint64(8) - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -414,7 +412,7 @@ func TestMultiReturnWithStringArray(t *testing.T) { ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f") ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"} ret4, ret4Exp := new(bool), false - if err := abi.Unpack(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -452,7 +450,7 @@ func TestMultiReturnWithStringSlice(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000065")) // output[1][1] value ret1, ret1Exp := new([]string), []string{"ethereum", "go-ethereum"} ret2, ret2Exp := new([]*big.Int), []*big.Int{big.NewInt(100), big.NewInt(101)} - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -492,7 +490,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { {{0x411, 0x412, 0x413}, {0x421, 0x422, 0x423}}, } ret2, ret2Exp := new(uint64), uint64(0x9876) - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -531,7 +529,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a")) buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000")) - err = abi.Unpack(&mixedBytes, "mixedBytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&mixedBytes, "mixedBytes", buff.Bytes()) if err != nil { t.Error(err) } else { @@ -546,7 +544,7 @@ func TestUnmarshal(t *testing.T) { // marshal int var Int *big.Int - err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + err = abi.UnpackIntoInterface(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) if err != nil { t.Error(err) } @@ -557,7 +555,7 @@ func TestUnmarshal(t *testing.T) { // marshal bool var Bool bool - err = abi.Unpack(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + err = abi.UnpackIntoInterface(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) if err != nil { t.Error(err) } @@ -574,7 +572,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(bytesOut) var Bytes []byte - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -590,7 +588,7 @@ func TestUnmarshal(t *testing.T) { bytesOut = common.RightPadBytes([]byte("hello"), 64) buff.Write(bytesOut) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -606,7 +604,7 @@ func TestUnmarshal(t *testing.T) { bytesOut = common.RightPadBytes([]byte("hello"), 64) buff.Write(bytesOut) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -616,7 +614,7 @@ func TestUnmarshal(t *testing.T) { } // marshal dynamic bytes output empty - err = abi.Unpack(&Bytes, "bytes", nil) + err = abi.UnpackIntoInterface(&Bytes, "bytes", nil) if err == nil { t.Error("expected error") } @@ -627,7 +625,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) buff.Write(common.RightPadBytes([]byte("hello"), 32)) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -641,7 +639,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.RightPadBytes([]byte("hello"), 32)) var hash common.Hash - err = abi.Unpack(&hash, "fixed", buff.Bytes()) + err = abi.UnpackIntoInterface(&hash, "fixed", buff.Bytes()) if err != nil { t.Error(err) } @@ -654,12 +652,12 @@ func TestUnmarshal(t *testing.T) { // marshal error buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err == nil { t.Error("expected error") } - err = abi.Unpack(&Bytes, "multi", make([]byte, 64)) + err = abi.UnpackIntoInterface(&Bytes, "multi", make([]byte, 64)) if err == nil { t.Error("expected error") } @@ -670,7 +668,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003")) // marshal int array var intArray [3]*big.Int - err = abi.Unpack(&intArray, "intArraySingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&intArray, "intArraySingle", buff.Bytes()) if err != nil { t.Error(err) } @@ -691,7 +689,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) var outAddr []common.Address - err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) if err != nil { t.Fatal("didn't expect error:", err) } @@ -718,7 +716,7 @@ func TestUnmarshal(t *testing.T) { A []common.Address B []common.Address } - err = abi.Unpack(&outAddrStruct, "addressSliceDouble", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddrStruct, "addressSliceDouble", buff.Bytes()) if err != nil { t.Fatal("didn't expect error:", err) } @@ -746,7 +744,7 @@ func TestUnmarshal(t *testing.T) { buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100")) - err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) if err == nil { t.Fatal("expected error:", err) } @@ -769,7 +767,7 @@ func TestUnpackTuple(t *testing.T) { B *big.Int }{new(big.Int), new(big.Int)} - err = abi.Unpack(&v, "tuple", buff.Bytes()) + err = abi.UnpackIntoInterface(&v, "tuple", buff.Bytes()) if err != nil { t.Error(err) } else { @@ -841,7 +839,7 @@ func TestUnpackTuple(t *testing.T) { A: big.NewInt(1), } - err = abi.Unpack(&ret, "tuple", buff.Bytes()) + err = abi.UnpackIntoInterface(&ret, "tuple", buff.Bytes()) if err != nil { t.Error(err) } diff --git a/accounts/accounts.go b/accounts/accounts.go index 7a14e4e3e5..dc85cba174 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -88,7 +88,7 @@ type Wallet interface { // to discover non zero accounts and automatically add them to list of tracked // accounts. // - // Note, self derivaton will increment the last component of the specified path + // Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go index 73ff6ae9ee..8b309321d3 100644 --- a/accounts/keystore/file_cache.go +++ b/accounts/keystore/file_cache.go @@ -32,7 +32,7 @@ import ( type fileCache struct { all mapset.Set // Set of all files from the keystore folder lastMod time.Time // Last time instance when a file was modified - mu sync.RWMutex + mu sync.Mutex } // scan performs a new scan on the given directory, compares against the already diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index 29c251d7c1..cb5de11c0d 100644 --- a/accounts/keystore/keystore_test.go +++ b/accounts/keystore/keystore_test.go @@ -336,7 +336,9 @@ func TestWalletNotifications(t *testing.T) { // Shut down the event collector and check events. sub.Unsubscribe() - <-updates + for ev := range updates { + events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) + } checkAccounts(t, live, ks.Wallets()) checkEvents(t, wantEvents, events) } diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 80009fc5eb..85fae8c114 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -637,7 +637,7 @@ func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun // to discover non zero accounts and automatically add them to list of tracked // accounts. // -// Note, self derivaton will increment the last component of the specified path +// Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index e31bc143f0..107314b483 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -494,7 +494,7 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun // to discover non zero accounts and automatically add them to list of tracked // accounts. // -// Note, self derivaton will increment the last component of the specified path +// Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // diff --git a/build/ci.go b/build/ci.go index 1105ed46b6..51a3953712 100644 --- a/build/ci.go +++ b/build/ci.go @@ -840,6 +840,7 @@ func doAndroidArchive(cmdline []string) { if *local { // If we're building locally, copy bundle to build dir and skip Maven os.Rename("geth.aar", filepath.Join(GOBIN, "geth.aar")) + os.Rename("geth-sources.jar", filepath.Join(GOBIN, "geth-sources.jar")) return } meta := newMavenMetadata(env) diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index f6e2a14c3b..6c9ff615a1 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -44,7 +44,7 @@ func main() { natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|extip:)") netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)") runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode") - verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-9)") + verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-5)") vmodule = flag.String("vmodule", "", "log verbosity pattern") nodeKey *ecdsa.PrivateKey diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index dbc302631b..31554f0790 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -10,6 +10,64 @@ TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the: Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. +### 6.1.0 + +The API-method `account_signGnosisSafeTx` was added. This method takes two parameters, +`[address, safeTx]`. The latter, `safeTx`, can be copy-pasted from the gnosis relay. For example: + +``` +{ + "jsonrpc": "2.0", + "method": "account_signGnosisSafeTx", + "params": ["0xfd1c4226bfD1c436672092F4eCbfC270145b7256", + { + "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3", + "to": "0xB372a646f7F05Cc1785018dBDA7EBc734a2A20E2", + "value": "20000000000000000", + "data": null, + "operation": 0, + "gasToken": "0x0000000000000000000000000000000000000000", + "safeTxGas": 27845, + "baseGas": 0, + "gasPrice": "0", + "refundReceiver": "0x0000000000000000000000000000000000000000", + "nonce": 2, + "executionDate": null, + "submissionDate": "2020-09-15T21:54:49.617634Z", + "modified": "2020-09-15T21:54:49.617634Z", + "blockNumber": null, + "transactionHash": null, + "safeTxHash": "0x2edfbd5bc113ff18c0631595db32eb17182872d88d9bf8ee4d8c2dd5db6d95e2", + "executor": null, + "isExecuted": false, + "isSuccessful": null, + "ethGasPrice": null, + "gasUsed": null, + "fee": null, + "origin": null, + "dataDecoded": null, + "confirmationsRequired": null, + "confirmations": [ + { + "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34", + "submissionDate": "2020-09-15T21:54:49.663299Z", + "transactionHash": null, + "confirmationType": "CONFIRMATION", + "signature": "0x95a7250bb645f831c86defc847350e7faff815b2fb586282568e96cc859e39315876db20a2eed5f7a0412906ec5ab57652a6f645ad4833f345bda059b9da2b821c", + "signatureType": "EOA" + } + ], + "signatures": null + } + ], + "id": 67 +} +``` + +Not all fields are required, though. This method is really just a UX helper, which massages the +input to conform to the `EIP-712` [specification](https://docs.gnosis.io/safe/docs/contracts_tx_execution/#transaction-hash) +for the Gnosis Safe, and making the output be directly importable to by a relay service. + ### 6.0.0 diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md new file mode 100644 index 0000000000..2763c75085 --- /dev/null +++ b/cmd/devp2p/README.md @@ -0,0 +1,86 @@ +# The devp2p command + +The devp2p command line tool is a utility for low-level peer-to-peer debugging and +protocol development purposes. It can do many things. + +### ENR Decoding + +Use `devp2p enrdump ` to verify and display an Ethereum Node Record. + +### Node Key Management + +The `devp2p key ...` command family deals with node key files. + +Run `devp2p key generate mynode.key` to create a new node key in the `mynode.key` file. + +Run `devp2p key to-enode mynode.key -ip 127.0.0.1 -tcp 30303` to create an enode:// URL +corresponding to the given node key and address information. + +### Maintaining DNS Discovery Node Lists + +The devp2p command can create and publish DNS discovery node lists. + +Run `devp2p dns sign ` to update the signature of a DNS discovery tree. + +Run `devp2p dns sync ` to download a complete DNS discovery tree. + +Run `devp2p dns to-cloudflare ` to publish a tree to CloudFlare DNS. + +Run `devp2p dns to-route53 ` to publish a tree to Amazon Route53. + +You can find more information about these commands in the [DNS Discovery Setup Guide][dns-tutorial]. + +### Discovery v4 Utilities + +The `devp2p discv4 ...` command family deals with the [Node Discovery v4][discv4] +protocol. + +Run `devp2p discv4 ping ` to ping a node. + +Run `devp2p discv4 resolve ` to find the most recent node record of a node in +the DHT. + +Run `devp2p discv4 crawl ` to create or update a JSON node set. + +### Discovery v5 Utilities + +The `devp2p discv5 ...` command family deals with the [Node Discovery v5][discv5] +protocol. This protocol is currently under active development. + +Run `devp2p discv5 ping ` to ping a node. + +Run `devp2p discv5 resolve ` to find the most recent node record of a node in +the discv5 DHT. + +Run `devp2p discv5 listen` to run a Discovery v5 node. + +Run `devp2p discv5 crawl ` to create or update a JSON node set containing +discv5 nodes. + +### Discovery Test Suites + +The devp2p command also contains interactive test suites for Discovery v4 and Discovery +v5. + +To run these tests against your implementation, you need to set up a networking +environment where two separate UDP listening addresses are available on the same machine. +The two listening addresses must also be routed such that they are able to reach the node +you want to test. + +For example, if you want to run the test on your local host, and the node under test is +also on the local host, you need to assign two IP addresses (or a larger range) to your +loopback interface. On macOS, this can be done by executing the following command: + + sudo ifconfig lo0 add 127.0.0.2 + +You can now run either test suite as follows: Start the node under test first, ensuring +that it won't talk to the Internet (i.e. disable bootstrapping). An easy way to prevent +unintended connections to the global DHT is listening on `127.0.0.1`. + +Now get the ENR of your node and store it in the `NODE` environment variable. + +Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.0.2 $NODE`. + +[dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup +[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md +[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 99b0957ab3..467c20deb5 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -286,7 +286,11 @@ func listen(ln *enode.LocalNode, addr string) *net.UDPConn { } usocket := socket.(*net.UDPConn) uaddr := socket.LocalAddr().(*net.UDPAddr) - ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + if uaddr.IP.IsUnspecified() { + ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + } else { + ln.SetFallbackIP(uaddr.IP) + } ln.SetFallbackUDP(uaddr.Port) return usocket } @@ -294,7 +298,11 @@ func listen(ln *enode.LocalNode, addr string) *net.UDPConn { func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { s := params.RinkebyBootnodes if ctx.IsSet(bootnodesFlag.Name) { - s = strings.Split(ctx.String(bootnodesFlag.Name), ",") + input := ctx.String(bootnodesFlag.Name) + if input == "" { + return nil, nil + } + s = strings.Split(input, ",") } nodes := make([]*enode.Node, len(s)) var err error diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go index f871821ea2..1d7442144f 100644 --- a/cmd/devp2p/discv5cmd.go +++ b/cmd/devp2p/discv5cmd.go @@ -18,9 +18,13 @@ package main import ( "fmt" + "os" "time" + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discover" "gopkg.in/urfave/cli.v1" ) @@ -33,6 +37,7 @@ var ( discv5PingCommand, discv5ResolveCommand, discv5CrawlCommand, + discv5TestCommand, discv5ListenCommand, }, } @@ -53,6 +58,12 @@ var ( Action: discv5Crawl, Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag}, } + discv5TestCommand = cli.Command{ + Name: "test", + Usage: "Runs protocol tests against a node", + Action: discv5Test, + Flags: []cli.Flag{testPatternFlag, testListen1Flag, testListen2Flag}, + } discv5ListenCommand = cli.Command{ Name: "listen", Usage: "Runs a node", @@ -103,6 +114,30 @@ func discv5Crawl(ctx *cli.Context) error { return nil } +func discv5Test(ctx *cli.Context) error { + // Disable logging unless explicitly enabled. + if !ctx.GlobalIsSet("verbosity") && !ctx.GlobalIsSet("vmodule") { + log.Root().SetHandler(log.DiscardHandler()) + } + + // Filter and run test cases. + suite := &v5test.Suite{ + Dest: getNodeArg(ctx), + Listen1: ctx.String(testListen1Flag.Name), + Listen2: ctx.String(testListen2Flag.Name), + } + tests := suite.AllTests() + if ctx.IsSet(testPatternFlag.Name) { + tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) + } + results := utesting.RunTests(tests, os.Stdout) + if fails := utesting.CountFailures(results); fails > 0 { + return fmt.Errorf("%v/%v tests passed.", len(tests)-fails, len(tests)) + } + fmt.Printf("%v/%v passed\n", len(tests), len(tests)) + return nil +} + func discv5Listen(ctx *cli.Context) error { disc := startV5(ctx) defer disc.Close() diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 250be64fe6..654888a4ca 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -1,3 +1,19 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package ethtest import ( @@ -68,6 +84,43 @@ func (c *Chain) Head() *types.Block { return c.blocks[c.Len()-1] } +func (c *Chain) GetHeaders(req GetBlockHeaders) (BlockHeaders, error) { + if req.Amount < 1 { + return nil, fmt.Errorf("no block headers requested") + } + + headers := make(BlockHeaders, req.Amount) + var blockNumber uint64 + + // range over blocks to check if our chain has the requested header + for _, block := range c.blocks { + if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number { + headers[0] = block.Header() + blockNumber = block.Number().Uint64() + } + } + if headers[0] == nil { + return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash) + } + + if req.Reverse { + for i := 1; i < int(req.Amount); i++ { + blockNumber -= (1 - req.Skip) + headers[i] = c.blocks[blockNumber].Header() + + } + + return headers, nil + } + + for i := 1; i < int(req.Amount); i++ { + blockNumber += (1 + req.Skip) + headers[i] = c.blocks[blockNumber].Header() + } + + return headers, nil +} + // loadChain takes the given chain.rlp file, and decodes and returns // the blocks from the file. func loadChain(chainfile string, genesis string) (*Chain, error) { diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go new file mode 100644 index 0000000000..c8b977d237 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -0,0 +1,150 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "path/filepath" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/stretchr/testify/assert" +) + +// TestEthProtocolNegotiation tests whether the test suite +// can negotiate the highest eth protocol in a status message exchange +func TestEthProtocolNegotiation(t *testing.T) { + var tests = []struct { + conn *Conn + caps []p2p.Cap + expected uint32 + }{ + { + conn: &Conn{}, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{}, + caps: []p2p.Cap{ + {Name: "eth", Version: 0}, + {Name: "eth", Version: 89}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{}, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "wrongProto", Version: 65}, + }, + expected: uint32(64), + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + tt.conn.negotiateEthProtocol(tt.caps) + assert.Equal(t, tt.expected, uint32(tt.conn.ethProtocolVersion)) + }) + } +} + +// TestChain_GetHeaders tests whether the test suite can correctly +// respond to a GetBlockHeaders request from a node. +func TestChain_GetHeaders(t *testing.T) { + chainFile, err := filepath.Abs("./testdata/chain.rlp.gz") + if err != nil { + t.Fatal(err) + } + genesisFile, err := filepath.Abs("./testdata/genesis.json") + if err != nil { + t.Fatal(err) + } + + chain, err := loadChain(chainFile, genesisFile) + if err != nil { + t.Fatal(err) + } + + var tests = []struct { + req GetBlockHeaders + expected BlockHeaders + }{ + { + req: GetBlockHeaders{ + Origin: hashOrNumber{ + Number: uint64(2), + }, + Amount: uint64(5), + Skip: 1, + Reverse: false, + }, + expected: BlockHeaders{ + chain.blocks[2].Header(), + chain.blocks[4].Header(), + chain.blocks[6].Header(), + chain.blocks[8].Header(), + chain.blocks[10].Header(), + }, + }, + { + req: GetBlockHeaders{ + Origin: hashOrNumber{ + Number: uint64(chain.Len() - 1), + }, + Amount: uint64(3), + Skip: 0, + Reverse: true, + }, + expected: BlockHeaders{ + chain.blocks[chain.Len()-1].Header(), + chain.blocks[chain.Len()-2].Header(), + chain.blocks[chain.Len()-3].Header(), + }, + }, + { + req: GetBlockHeaders{ + Origin: hashOrNumber{ + Hash: chain.Head().Hash(), + }, + Amount: uint64(1), + Skip: 0, + Reverse: false, + }, + expected: BlockHeaders{ + chain.Head().Header(), + }, + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + headers, err := chain.GetHeaders(tt.req) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, headers, tt.expected) + }) + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 6951f13bfb..f70bc43efa 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -1,19 +1,29 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package ethtest import ( - "crypto/ecdsa" "fmt" "net" - "reflect" - "time" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/rlpx" - "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/assert" ) @@ -26,137 +36,6 @@ type Suite struct { fullChain *Chain } -type Conn struct { - *rlpx.Conn - ourKey *ecdsa.PrivateKey -} - -func (c *Conn) Read() Message { - code, rawData, _, err := c.Conn.Read() - if err != nil { - return &Error{fmt.Errorf("could not read from connection: %v", err)} - } - - var msg Message - switch int(code) { - case (Hello{}).Code(): - msg = new(Hello) - case (Disconnect{}).Code(): - msg = new(Disconnect) - case (Status{}).Code(): - msg = new(Status) - case (GetBlockHeaders{}).Code(): - msg = new(GetBlockHeaders) - case (BlockHeaders{}).Code(): - msg = new(BlockHeaders) - case (GetBlockBodies{}).Code(): - msg = new(GetBlockBodies) - case (BlockBodies{}).Code(): - msg = new(BlockBodies) - case (NewBlock{}).Code(): - msg = new(NewBlock) - case (NewBlockHashes{}).Code(): - msg = new(NewBlockHashes) - default: - return &Error{fmt.Errorf("invalid message code: %d", code)} - } - - if err := rlp.DecodeBytes(rawData, msg); err != nil { - return &Error{fmt.Errorf("could not rlp decode message: %v", err)} - } - - return msg -} - -func (c *Conn) Write(msg Message) error { - payload, err := rlp.EncodeToBytes(msg) - if err != nil { - return err - } - _, err = c.Conn.Write(uint64(msg.Code()), payload) - return err - -} - -// handshake checks to make sure a `HELLO` is received. -func (c *Conn) handshake(t *utesting.T) Message { - // write protoHandshake to client - pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] - ourHandshake := &Hello{ - Version: 5, - Caps: []p2p.Cap{{Name: "eth", Version: 64}, {Name: "eth", Version: 65}}, - ID: pub0, - } - if err := c.Write(ourHandshake); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - // read protoHandshake from client - switch msg := c.Read().(type) { - case *Hello: - return msg - default: - t.Fatalf("bad handshake: %v", msg) - return nil - } -} - -// statusExchange performs a `Status` message exchange with the given -// node. -func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { - // read status message from client - var message Message - switch msg := c.Read().(type) { - case *Status: - if msg.Head != chain.blocks[chain.Len()-1].Hash() { - t.Fatalf("wrong head in status: %v", msg.Head) - } - if msg.TD.Cmp(chain.TD(chain.Len())) != 0 { - t.Fatalf("wrong TD in status: %v", msg.TD) - } - if !reflect.DeepEqual(msg.ForkID, chain.ForkID()) { - t.Fatalf("wrong fork ID in status: %v", msg.ForkID) - } - message = msg - default: - t.Fatalf("bad status message: %v", msg) - } - // write status message to client - status := Status{ - ProtocolVersion: 64, - NetworkID: 1, - TD: chain.TD(chain.Len()), - Head: chain.blocks[chain.Len()-1].Hash(), - Genesis: chain.blocks[0].Hash(), - ForkID: chain.ForkID(), - } - if err := c.Write(status); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - - return message -} - -// waitForBlock waits for confirmation from the client that it has -// imported the given block. -func (c *Conn) waitForBlock(block *types.Block) error { - for { - req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1} - if err := c.Write(req); err != nil { - return err - } - - switch msg := c.Read().(type) { - case *BlockHeaders: - if len(*msg) > 0 { - return nil - } - time.Sleep(100 * time.Millisecond) - default: - return fmt.Errorf("invalid message: %v", msg) - } - } -} - // NewSuite creates and returns a new eth-test suite that can // be used to test the given node against the given blockchain // data. @@ -196,7 +75,7 @@ func (s *Suite) TestStatus(t *utesting.T) { case *Status: t.Logf("%+v\n", msg) default: - t.Fatalf("error: %v", msg) + t.Fatalf("unexpected: %#v", msg) } } @@ -225,7 +104,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := conn.Read().(type) { + switch msg := conn.ReadAndServe(s.chain).(type) { case *BlockHeaders: headers := msg for _, header := range *headers { @@ -234,7 +113,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { t.Logf("\nHEADER FOR BLOCK NUMBER %d: %+v\n", header.Number, header) } default: - t.Fatalf("error: %v", msg) + t.Fatalf("unexpected: %#v", msg) } } @@ -254,14 +133,14 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := conn.Read().(type) { + switch msg := conn.ReadAndServe(s.chain).(type) { case *BlockBodies: bodies := msg for _, body := range *bodies { t.Logf("\nBODY: %+v\n", body) } default: - t.Fatalf("error: %v", msg) + t.Fatalf("unexpected: %#v", msg) } } @@ -294,7 +173,7 @@ func (s *Suite) TestBroadcast(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := receiveConn.Read().(type) { + switch msg := receiveConn.ReadAndServe(s.chain).(type) { case *NewBlock: assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(), "wrong block header in announcement") @@ -305,7 +184,7 @@ func (s *Suite) TestBroadcast(t *utesting.T) { assert.Equal(t, blockAnnouncement.Block.Hash(), hashes[0].Hash, "wrong block hash in announcement") default: - t.Fatal(msg) + t.Fatalf("unexpected: %#v", msg) } // update test suite chain s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000]) diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz b/cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz new file mode 100755 index 0000000000000000000000000000000000000000..bdd6290ce6e569c50323c02fcf3cb12b0d803d1e GIT binary patch literal 247322 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

=XnITf)MFj(D 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 literal 0 HcmV?d00001 diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json new file mode 100644 index 0000000000..ea5e2725b5 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -0,0 +1,26 @@ +{ + "config": { + "chainId": 1, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "ethash": {} + }, + "nonce": "0xdeadbeefdeadbeef", + "timestamp": "0x0", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x8000000", + "difficulty": "0x10", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "71562b71999873db5b286df957af199ec94617f7": { + "balance": "0xf4240" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index ef2c52ddfd..b6298e8083 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -1,14 +1,36 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package ethtest import ( + "crypto/ecdsa" "fmt" "io" "math/big" + "reflect" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/ethereum/go-ethereum/rlp" ) @@ -20,9 +42,10 @@ type Error struct { err error } -func (e *Error) Unwrap() error { return e.err } -func (e *Error) Error() string { return e.err.Error() } -func (e *Error) Code() int { return -1 } +func (e *Error) Unwrap() error { return e.err } +func (e *Error) Error() string { return e.err.Error() } +func (e *Error) Code() int { return -1 } +func (e *Error) GoString() string { return e.Error() } // Hello is the RLP structure of the protocol handshake. type Hello struct { @@ -45,6 +68,14 @@ type Disconnect struct { func (d Disconnect) Code() int { return 0x01 } +type Ping struct{} + +func (p Ping) Code() int { return 0x02 } + +type Pong struct{} + +func (p Pong) Code() int { return 0x03 } + // Status is the network packet for the status message for eth/64 and later. type Status struct { ProtocolVersion uint32 @@ -132,3 +163,204 @@ func (gbb GetBlockBodies) Code() int { return 21 } type BlockBodies []*types.Body func (bb BlockBodies) Code() int { return 22 } + +// Conn represents an individual connection with a peer +type Conn struct { + *rlpx.Conn + ourKey *ecdsa.PrivateKey + ethProtocolVersion uint +} + +func (c *Conn) Read() Message { + code, rawData, _, err := c.Conn.Read() + if err != nil { + return &Error{fmt.Errorf("could not read from connection: %v", err)} + } + + var msg Message + switch int(code) { + case (Hello{}).Code(): + msg = new(Hello) + case (Ping{}).Code(): + msg = new(Ping) + case (Pong{}).Code(): + msg = new(Pong) + case (Disconnect{}).Code(): + msg = new(Disconnect) + case (Status{}).Code(): + msg = new(Status) + case (GetBlockHeaders{}).Code(): + msg = new(GetBlockHeaders) + case (BlockHeaders{}).Code(): + msg = new(BlockHeaders) + case (GetBlockBodies{}).Code(): + msg = new(GetBlockBodies) + case (BlockBodies{}).Code(): + msg = new(BlockBodies) + case (NewBlock{}).Code(): + msg = new(NewBlock) + case (NewBlockHashes{}).Code(): + msg = new(NewBlockHashes) + default: + return &Error{fmt.Errorf("invalid message code: %d", code)} + } + + if err := rlp.DecodeBytes(rawData, msg); err != nil { + return &Error{fmt.Errorf("could not rlp decode message: %v", err)} + } + + return msg +} + +// ReadAndServe serves GetBlockHeaders requests while waiting +// on another message from the node. +func (c *Conn) ReadAndServe(chain *Chain) Message { + for { + switch msg := c.Read().(type) { + case *Ping: + c.Write(&Pong{}) + case *GetBlockHeaders: + req := *msg + headers, err := chain.GetHeaders(req) + if err != nil { + return &Error{fmt.Errorf("could not get headers for inbound header request: %v", err)} + } + + if err := c.Write(headers); err != nil { + return &Error{fmt.Errorf("could not write to connection: %v", err)} + } + default: + return msg + } + } +} + +func (c *Conn) Write(msg Message) error { + payload, err := rlp.EncodeToBytes(msg) + if err != nil { + return err + } + _, err = c.Conn.Write(uint64(msg.Code()), payload) + return err + +} + +// handshake checks to make sure a `HELLO` is received. +func (c *Conn) handshake(t *utesting.T) Message { + // write protoHandshake to client + pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] + ourHandshake := &Hello{ + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: pub0, + } + if err := c.Write(ourHandshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // read protoHandshake from client + switch msg := c.Read().(type) { + case *Hello: + // set snappy if version is at least 5 + if msg.Version >= 5 { + c.SetSnappy(true) + } + + c.negotiateEthProtocol(msg.Caps) + if c.ethProtocolVersion == 0 { + t.Fatalf("unexpected eth protocol version") + } + return msg + default: + t.Fatalf("bad handshake: %#v", msg) + return nil + } +} + +// negotiateEthProtocol sets the Conn's eth protocol version +// to highest advertised capability from peer +func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { + var highestEthVersion uint + for _, capability := range caps { + if capability.Name != "eth" { + continue + } + if capability.Version > highestEthVersion && capability.Version <= 65 { + highestEthVersion = capability.Version + } + } + c.ethProtocolVersion = highestEthVersion +} + +// statusExchange performs a `Status` message exchange with the given +// node. +func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { + // read status message from client + var message Message + +loop: + for { + switch msg := c.Read().(type) { + case *Status: + if msg.Head != chain.blocks[chain.Len()-1].Hash() { + t.Fatalf("wrong head in status: %v", msg.Head) + } + if msg.TD.Cmp(chain.TD(chain.Len())) != 0 { + t.Fatalf("wrong TD in status: %v", msg.TD) + } + if !reflect.DeepEqual(msg.ForkID, chain.ForkID()) { + t.Fatalf("wrong fork ID in status: %v", msg.ForkID) + } + message = msg + break loop + case *Disconnect: + t.Fatalf("disconnect received: %v", msg.Reason) + case *Ping: + c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error + // (PINGs should not be a response upon fresh connection) + default: + t.Fatalf("bad status message: %#v", msg) + } + } + // make sure eth protocol version is set for negotiation + if c.ethProtocolVersion == 0 { + t.Fatalf("eth protocol version must be set in Conn") + } + // write status message to client + status := Status{ + ProtocolVersion: uint32(c.ethProtocolVersion), + NetworkID: 1, + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + if err := c.Write(status); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + return message +} + +// waitForBlock waits for confirmation from the client that it has +// imported the given block. +func (c *Conn) waitForBlock(block *types.Block) error { + for { + req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1} + if err := c.Write(req); err != nil { + return err + } + + switch msg := c.Read().(type) { + case *BlockHeaders: + if len(*msg) > 0 { + return nil + } + time.Sleep(100 * time.Millisecond) + default: + return fmt.Errorf("invalid message: %v", msg) + } + } +} diff --git a/cmd/devp2p/internal/v5test/discv5tests.go b/cmd/devp2p/internal/v5test/discv5tests.go new file mode 100644 index 0000000000..7866498f73 --- /dev/null +++ b/cmd/devp2p/internal/v5test/discv5tests.go @@ -0,0 +1,377 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package v5test + +import ( + "bytes" + "net" + "sync" + "time" + + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/netutil" +) + +// Suite is the discv5 test suite. +type Suite struct { + Dest *enode.Node + Listen1, Listen2 string // listening addresses +} + +func (s *Suite) listen1(log logger) (*conn, net.PacketConn) { + c := newConn(s.Dest, log) + l := c.listen(s.Listen1) + return c, l +} + +func (s *Suite) listen2(log logger) (*conn, net.PacketConn, net.PacketConn) { + c := newConn(s.Dest, log) + l1, l2 := c.listen(s.Listen1), c.listen(s.Listen2) + return c, l1, l2 +} + +func (s *Suite) AllTests() []utesting.Test { + return []utesting.Test{ + {Name: "Ping", Fn: s.TestPing}, + {Name: "PingLargeRequestID", Fn: s.TestPingLargeRequestID}, + {Name: "PingMultiIP", Fn: s.TestPingMultiIP}, + {Name: "PingHandshakeInterrupted", Fn: s.TestPingHandshakeInterrupted}, + {Name: "TalkRequest", Fn: s.TestTalkRequest}, + {Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance}, + {Name: "FindnodeResults", Fn: s.TestFindnodeResults}, + } +} + +// This test sends PING and expects a PONG response. +func (s *Suite) TestPing(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + switch resp := conn.reqresp(l1, ping).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping, l1) + default: + t.Fatal("expected PONG, got", resp.Name()) + } +} + +func checkPong(t *utesting.T, pong *v5wire.Pong, ping *v5wire.Ping, c net.PacketConn) { + if !bytes.Equal(pong.ReqID, ping.ReqID) { + t.Fatalf("wrong request ID %x in PONG, want %x", pong.ReqID, ping.ReqID) + } + if !pong.ToIP.Equal(laddr(c).IP) { + t.Fatalf("wrong destination IP %v in PONG, want %v", pong.ToIP, laddr(c).IP) + } + if int(pong.ToPort) != laddr(c).Port { + t.Fatalf("wrong destination port %v in PONG, want %v", pong.ToPort, laddr(c).Port) + } +} + +// This test sends PING with a 9-byte request ID, which isn't allowed by the spec. +// The remote node should not respond. +func (s *Suite) TestPingLargeRequestID(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + ping := &v5wire.Ping{ReqID: make([]byte, 9)} + switch resp := conn.reqresp(l1, ping).(type) { + case *v5wire.Pong: + t.Errorf("PONG response with unknown request ID %x", resp.ReqID) + case *readError: + if resp.err == v5wire.ErrInvalidReqID { + t.Error("response with oversized request ID") + } else if !netutil.IsTimeout(resp.err) { + t.Error(resp) + } + } +} + +// In this test, a session is established from one IP as usual. The session is then reused +// on another IP, which shouldn't work. The remote node should respond with WHOAREYOU for +// the attempt from a different IP. +func (s *Suite) TestPingMultiIP(t *utesting.T) { + conn, l1, l2 := s.listen2(t) + defer conn.close() + + // Create the session on l1. + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + resp := conn.reqresp(l1, ping) + if resp.Kind() != v5wire.PongMsg { + t.Fatal("expected PONG, got", resp) + } + checkPong(t, resp.(*v5wire.Pong), ping, l1) + + // Send on l2. This reuses the session because there is only one codec. + ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l2, ping2, nil) + switch resp := conn.read(l2).(type) { + case *v5wire.Pong: + t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l2).IP, laddr(l1).IP) + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for new session as expected") + resp.Node = s.Dest + conn.write(l2, ping2, resp) + default: + t.Fatal("expected WHOAREYOU, got", resp) + } + + // Catch the PONG on l2. + switch resp := conn.read(l2).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping2, l2) + default: + t.Fatal("expected PONG, got", resp) + } + + // Try on l1 again. + ping3 := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l1, ping3, nil) + switch resp := conn.read(l1).(type) { + case *v5wire.Pong: + t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l1).IP, laddr(l2).IP) + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for new session as expected") + default: + t.Fatal("expected WHOAREYOU, got", resp) + } +} + +// This test starts a handshake, but doesn't finish it and sends a second ordinary message +// packet instead of a handshake message packet. The remote node should respond with +// another WHOAREYOU challenge for the second packet. +func (s *Suite) TestPingHandshakeInterrupted(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + // First PING triggers challenge. + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l1, ping, nil) + switch resp := conn.read(l1).(type) { + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for PING") + default: + t.Fatal("expected WHOAREYOU, got", resp) + } + + // Send second PING. + ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} + switch resp := conn.reqresp(l1, ping2).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping2, l1) + default: + t.Fatal("expected WHOAREYOU, got", resp) + } +} + +// This test sends TALKREQ and expects an empty TALKRESP response. +func (s *Suite) TestTalkRequest(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + // Non-empty request ID. + id := conn.nextReqID() + resp := conn.reqresp(l1, &v5wire.TalkRequest{ReqID: id, Protocol: "test-protocol"}) + switch resp := resp.(type) { + case *v5wire.TalkResponse: + if !bytes.Equal(resp.ReqID, id) { + t.Fatalf("wrong request ID %x in TALKRESP, want %x", resp.ReqID, id) + } + if len(resp.Message) > 0 { + t.Fatalf("non-empty message %x in TALKRESP", resp.Message) + } + default: + t.Fatal("expected TALKRESP, got", resp.Name()) + } + + // Empty request ID. + resp = conn.reqresp(l1, &v5wire.TalkRequest{Protocol: "test-protocol"}) + switch resp := resp.(type) { + case *v5wire.TalkResponse: + if len(resp.ReqID) > 0 { + t.Fatalf("wrong request ID %x in TALKRESP, want empty byte array", resp.ReqID) + } + if len(resp.Message) > 0 { + t.Fatalf("non-empty message %x in TALKRESP", resp.Message) + } + default: + t.Fatal("expected TALKRESP, got", resp.Name()) + } +} + +// This test checks that the remote node returns itself for FINDNODE with distance zero. +func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + nodes, err := conn.findnode(l1, []uint{0}) + if err != nil { + t.Fatal(err) + } + if len(nodes) != 1 { + t.Fatalf("remote returned more than one node for FINDNODE [0]") + } + if nodes[0].ID() != conn.remote.ID() { + t.Errorf("ID of response node is %v, want %v", nodes[0].ID(), conn.remote.ID()) + } +} + +// In this test, multiple nodes ping the node under test. After waiting for them to be +// accepted into the remote table, the test checks that they are returned by FINDNODE. +func (s *Suite) TestFindnodeResults(t *utesting.T) { + // Create bystanders. + nodes := make([]*bystander, 5) + added := make(chan enode.ID, len(nodes)) + for i := range nodes { + nodes[i] = newBystander(t, s, added) + defer nodes[i].close() + } + + // Get them added to the remote table. + timeout := 60 * time.Second + timeoutCh := time.After(timeout) + for count := 0; count < len(nodes); { + select { + case id := <-added: + t.Logf("bystander node %v added to remote table", id) + count++ + case <-timeoutCh: + t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes)) + t.Logf("this can happen if the node has a non-empty table from previous runs") + return + } + } + t.Logf("all %d bystander nodes were added", len(nodes)) + + // Collect our nodes by distance. + var dists []uint + expect := make(map[enode.ID]*enode.Node) + for _, bn := range nodes { + n := bn.conn.localNode.Node() + expect[n.ID()] = n + d := uint(enode.LogDist(n.ID(), s.Dest.ID())) + if !containsUint(dists, d) { + dists = append(dists, d) + } + } + + // Send FINDNODE for all distances. + conn, l1 := s.listen1(t) + defer conn.close() + foundNodes, err := conn.findnode(l1, dists) + if err != nil { + t.Fatal(err) + } + t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists) + for _, n := range foundNodes { + delete(expect, n.ID()) + } + if len(expect) > 0 { + t.Errorf("missing %d nodes in FINDNODE result", len(expect)) + t.Logf("this can happen if the test is run multiple times in quick succession") + t.Logf("and the remote node hasn't removed dead nodes from previous runs yet") + } else { + t.Logf("all %d expected nodes were returned", len(nodes)) + } +} + +// A bystander is a node whose only purpose is filling a spot in the remote table. +type bystander struct { + dest *enode.Node + conn *conn + l net.PacketConn + + addedCh chan enode.ID + done sync.WaitGroup +} + +func newBystander(t *utesting.T, s *Suite, added chan enode.ID) *bystander { + conn, l := s.listen1(t) + conn.setEndpoint(l) // bystander nodes need IP/port to get pinged + bn := &bystander{ + conn: conn, + l: l, + dest: s.Dest, + addedCh: added, + } + bn.done.Add(1) + go bn.loop() + return bn +} + +// id returns the node ID of the bystander. +func (bn *bystander) id() enode.ID { + return bn.conn.localNode.ID() +} + +// close shuts down loop. +func (bn *bystander) close() { + bn.conn.close() + bn.done.Wait() +} + +// loop answers packets from the remote node until quit. +func (bn *bystander) loop() { + defer bn.done.Done() + + var ( + lastPing time.Time + wasAdded bool + ) + for { + // Ping the remote node. + if !wasAdded && time.Since(lastPing) > 10*time.Second { + bn.conn.reqresp(bn.l, &v5wire.Ping{ + ReqID: bn.conn.nextReqID(), + ENRSeq: bn.dest.Seq(), + }) + lastPing = time.Now() + } + // Answer packets. + switch p := bn.conn.read(bn.l).(type) { + case *v5wire.Ping: + bn.conn.write(bn.l, &v5wire.Pong{ + ReqID: p.ReqID, + ENRSeq: bn.conn.localNode.Seq(), + ToIP: bn.dest.IP(), + ToPort: uint16(bn.dest.UDP()), + }, nil) + wasAdded = true + bn.notifyAdded() + case *v5wire.Findnode: + bn.conn.write(bn.l, &v5wire.Nodes{ReqID: p.ReqID, Total: 1}, nil) + wasAdded = true + bn.notifyAdded() + case *v5wire.TalkRequest: + bn.conn.write(bn.l, &v5wire.TalkResponse{ReqID: p.ReqID}, nil) + case *readError: + if !netutil.IsTemporaryError(p.err) { + bn.conn.logf("shutting down: %v", p.err) + return + } + } + } +} + +func (bn *bystander) notifyAdded() { + if bn.addedCh != nil { + bn.addedCh <- bn.id() + bn.addedCh = nil + } +} diff --git a/cmd/devp2p/internal/v5test/framework.go b/cmd/devp2p/internal/v5test/framework.go new file mode 100644 index 0000000000..9eac37520f --- /dev/null +++ b/cmd/devp2p/internal/v5test/framework.go @@ -0,0 +1,263 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package v5test + +import ( + "bytes" + "crypto/ecdsa" + "encoding/binary" + "fmt" + "net" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" +) + +// readError represents an error during packet reading. +// This exists to facilitate type-switching on the result of conn.read. +type readError struct { + err error +} + +func (p *readError) Kind() byte { return 99 } +func (p *readError) Name() string { return fmt.Sprintf("error: %v", p.err) } +func (p *readError) Error() string { return p.err.Error() } +func (p *readError) Unwrap() error { return p.err } +func (p *readError) RequestID() []byte { return nil } +func (p *readError) SetRequestID([]byte) {} + +// readErrorf creates a readError with the given text. +func readErrorf(format string, args ...interface{}) *readError { + return &readError{fmt.Errorf(format, args...)} +} + +// This is the response timeout used in tests. +const waitTime = 300 * time.Millisecond + +// conn is a connection to the node under test. +type conn struct { + localNode *enode.LocalNode + localKey *ecdsa.PrivateKey + remote *enode.Node + remoteAddr *net.UDPAddr + listeners []net.PacketConn + + log logger + codec *v5wire.Codec + lastRequest v5wire.Packet + lastChallenge *v5wire.Whoareyou + idCounter uint32 +} + +type logger interface { + Logf(string, ...interface{}) +} + +// newConn sets up a connection to the given node. +func newConn(dest *enode.Node, log logger) *conn { + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + db, err := enode.OpenDB("") + if err != nil { + panic(err) + } + ln := enode.NewLocalNode(db, key) + + return &conn{ + localKey: key, + localNode: ln, + remote: dest, + remoteAddr: &net.UDPAddr{IP: dest.IP(), Port: dest.UDP()}, + codec: v5wire.NewCodec(ln, key, mclock.System{}), + log: log, + } +} + +func (tc *conn) setEndpoint(c net.PacketConn) { + tc.localNode.SetStaticIP(laddr(c).IP) + tc.localNode.SetFallbackUDP(laddr(c).Port) +} + +func (tc *conn) listen(ip string) net.PacketConn { + l, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", ip)) + if err != nil { + panic(err) + } + tc.listeners = append(tc.listeners, l) + return l +} + +// close shuts down all listeners and the local node. +func (tc *conn) close() { + for _, l := range tc.listeners { + l.Close() + } + tc.localNode.Database().Close() +} + +// nextReqID creates a request id. +func (tc *conn) nextReqID() []byte { + id := make([]byte, 4) + tc.idCounter++ + binary.BigEndian.PutUint32(id, tc.idCounter) + return id +} + +// reqresp performs a request/response interaction on the given connection. +// The request is retried if a handshake is requested. +func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet { + reqnonce := tc.write(c, req, nil) + switch resp := tc.read(c).(type) { + case *v5wire.Whoareyou: + if resp.Nonce != reqnonce { + return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:]) + } + resp.Node = tc.remote + tc.write(c, req, resp) + return tc.read(c) + default: + return resp + } +} + +// findnode sends a FINDNODE request and waits for its responses. +func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error) { + var ( + findnode = &v5wire.Findnode{ReqID: tc.nextReqID(), Distances: dists} + reqnonce = tc.write(c, findnode, nil) + first = true + total uint8 + results []*enode.Node + ) + for n := 1; n > 0; { + switch resp := tc.read(c).(type) { + case *v5wire.Whoareyou: + // Handle handshake. + if resp.Nonce == reqnonce { + resp.Node = tc.remote + tc.write(c, findnode, resp) + } else { + return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:]) + } + case *v5wire.Ping: + // Handle ping from remote. + tc.write(c, &v5wire.Pong{ + ReqID: resp.ReqID, + ENRSeq: tc.localNode.Seq(), + }, nil) + case *v5wire.Nodes: + // Got NODES! Check request ID. + if !bytes.Equal(resp.ReqID, findnode.ReqID) { + return nil, fmt.Errorf("NODES response has wrong request id %x", resp.ReqID) + } + // Check total count. It should be greater than one + // and needs to be the same across all responses. + if first { + if resp.Total == 0 || resp.Total > 6 { + return nil, fmt.Errorf("invalid NODES response 'total' %d (not in (0,7))", resp.Total) + } + total = resp.Total + n = int(total) - 1 + first = false + } else { + n-- + if resp.Total != total { + return nil, fmt.Errorf("invalid NODES response 'total' %d (!= %d)", resp.Total, total) + } + } + // Check nodes. + nodes, err := checkRecords(resp.Nodes) + if err != nil { + return nil, fmt.Errorf("invalid node in NODES response: %v", err) + } + results = append(results, nodes...) + default: + return nil, fmt.Errorf("expected NODES, got %v", resp) + } + } + return results, nil +} + +// write sends a packet on the given connection. +func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou) v5wire.Nonce { + packet, nonce, err := tc.codec.Encode(tc.remote.ID(), tc.remoteAddr.String(), p, challenge) + if err != nil { + panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err)) + } + if _, err := c.WriteTo(packet, tc.remoteAddr); err != nil { + tc.logf("Can't send %s: %v", p.Name(), err) + } else { + tc.logf(">> %s", p.Name()) + } + return nonce +} + +// read waits for an incoming packet on the given connection. +func (tc *conn) read(c net.PacketConn) v5wire.Packet { + buf := make([]byte, 1280) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { + return &readError{err} + } + n, fromAddr, err := c.ReadFrom(buf) + if err != nil { + return &readError{err} + } + _, _, p, err := tc.codec.Decode(buf[:n], fromAddr.String()) + if err != nil { + return &readError{err} + } + tc.logf("<< %s", p.Name()) + return p +} + +// logf prints to the test log. +func (tc *conn) logf(format string, args ...interface{}) { + if tc.log != nil { + tc.log.Logf("(%s) %s", tc.localNode.ID().TerminalString(), fmt.Sprintf(format, args...)) + } +} + +func laddr(c net.PacketConn) *net.UDPAddr { + return c.LocalAddr().(*net.UDPAddr) +} + +func checkRecords(records []*enr.Record) ([]*enode.Node, error) { + nodes := make([]*enode.Node, len(records)) + for i := range records { + n, err := enode.New(enode.ValidSchemes, records[i]) + if err != nil { + return nil, err + } + nodes[i] = n + } + return nodes, nil +} + +func containsUint(ints []uint, x uint) bool { + for i := range ints { + if ints[i] == x { + return true + } + } + return false +} diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 781e6e3026..009e9090a4 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -45,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -243,6 +244,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis + utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash()) lesBackend, err := les.New(stack, &cfg) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 4455956411..2240400462 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -223,8 +223,8 @@ var ( utils.IPCDisabledFlag, utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, - utils.RPCGlobalGasCap, - utils.RPCGlobalTxFeeCap, + utils.RPCGlobalGasCapFlag, + utils.RPCGlobalTxFeeCapFlag, } whisperFlags = []cli.Flag{ diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index da41bca8da..22f0293d82 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -149,8 +149,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.GraphQLEnabledFlag, utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, - utils.RPCGlobalGasCap, - utils.RPCGlobalTxFeeCap, + utils.RPCGlobalGasCapFlag, + utils.RPCGlobalTxFeeCapFlag, utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c5a55cc3b9..ed14fa5835 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -470,12 +470,12 @@ var ( Name: "allow-insecure-unlock", Usage: "Allow insecure account unlocking when account-related RPCs are exposed by http", } - RPCGlobalGasCap = cli.Uint64Flag{ + RPCGlobalGasCapFlag = cli.Uint64Flag{ Name: "rpc.gascap", Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", Value: eth.DefaultConfig.RPCGasCap, } - RPCGlobalTxFeeCap = cli.Float64Flag{ + RPCGlobalTxFeeCapFlag = cli.Float64Flag{ Name: "rpc.txfeecap", Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)", Value: eth.DefaultConfig.RPCTxFeeCap, @@ -1837,16 +1837,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(EVMInterpreterFlag.Name) { cfg.EVMInterpreter = ctx.GlobalString(EVMInterpreterFlag.Name) } - if ctx.GlobalIsSet(RPCGlobalGasCap.Name) { - cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCap.Name) + if ctx.GlobalIsSet(RPCGlobalGasCapFlag.Name) { + cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCapFlag.Name) } if cfg.RPCGasCap != 0 { log.Info("Set global gas cap", "cap", cfg.RPCGasCap) } else { log.Info("Global gas cap disabled") } - if ctx.GlobalIsSet(RPCGlobalTxFeeCap.Name) { - cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCap.Name) + if ctx.GlobalIsSet(RPCGlobalTxFeeCapFlag.Name) { + cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name) } if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { urls := ctx.GlobalString(DNSDiscoveryFlag.Name) @@ -1867,19 +1867,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.NetworkId = 3 } cfg.Genesis = core.DefaultRopstenGenesisBlock() - setDNSDiscoveryDefaults(cfg, params.RopstenGenesisHash) + SetDNSDiscoveryDefaults(cfg, params.RopstenGenesisHash) case ctx.GlobalBool(RinkebyFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 4 } cfg.Genesis = core.DefaultRinkebyGenesisBlock() - setDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash) + SetDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash) case ctx.GlobalBool(GoerliFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 5 } cfg.Genesis = core.DefaultGoerliGenesisBlock() - setDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) + SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) case ctx.GlobalBool(YoloV1Flag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 133519467574833 // "yolov1" @@ -1933,14 +1933,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } default: if cfg.NetworkId == 1 { - setDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) + SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) } } } -// setDNSDiscoveryDefaults configures DNS discovery with the given URL if +// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if // no URLs are set. -func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { +func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { if cfg.DiscoveryURLs != nil { return // already set through flags/config } diff --git a/common/math/big.go b/common/math/big.go index 17a57df9dc..1af5b4d879 100644 --- a/common/math/big.go +++ b/common/math/big.go @@ -67,6 +67,40 @@ func (i *HexOrDecimal256) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil } +// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling, +// it however accepts either "0x"-prefixed (hex encoded) or non-prefixed (decimal) +type Decimal256 big.Int + +// NewHexOrDecimal256 creates a new Decimal256 +func NewDecimal256(x int64) *Decimal256 { + b := big.NewInt(x) + d := Decimal256(*b) + return &d +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *Decimal256) UnmarshalText(input []byte) error { + bigint, ok := ParseBig256(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = Decimal256(*bigint) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i *Decimal256) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// String implements Stringer. +func (i *Decimal256) String() string { + if i == nil { + return "0" + } + return fmt.Sprintf("%#d", (*big.Int)(i)) +} + // ParseBig256 parses s as a 256 bit integer in decimal or hexadecimal syntax. // Leading zeros are accepted. The empty string parses as zero. func ParseBig256(s string) (*big.Int, bool) { diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 35a00af5be..fcb1121f57 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -521,7 +521,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header c.lock.RUnlock() } // Set the correct difficulty - header.Difficulty = CalcDifficulty(snap, c.signer) + header.Difficulty = calcDifficulty(snap, c.signer) // Ensure the extra data has all its components if len(header.Extra) < extraVanity { @@ -653,20 +653,18 @@ func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, res } // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty -// that a new block should have based on the previous blocks in the chain and the -// current signer. +// that a new block should have: +// * DIFF_NOTURN(2) if BLOCK_NUMBER % SIGNER_COUNT != SIGNER_INDEX +// * DIFF_INTURN(1) if BLOCK_NUMBER % SIGNER_COUNT == SIGNER_INDEX func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil) if err != nil { return nil } - return CalcDifficulty(snap, c.signer) + return calcDifficulty(snap, c.signer) } -// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty -// that a new block should have based on the previous blocks in the chain and the -// current signer. -func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int { +func calcDifficulty(snap *Snapshot, signer common.Address) *big.Int { if snap.inturn(snap.Number+1, signer) { return new(big.Int).Set(diffInTurn) } diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index 3890fc51dd..039ba919bf 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -423,7 +423,7 @@ func TestClique(t *testing.T) { }) // Iterate through the blocks and seal them individually for j, block := range blocks { - // Geth the header and prepare it for signing + // Get the header and prepare it for signing header := block.Header() if j > 0 { header.ParentHash = blocks[j-1].Hash() diff --git a/console/bridge.go b/console/bridge.go index 9303496b28..1a23269194 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -353,14 +353,14 @@ func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) { } // Poll the current block number until either it or a timeout is reached. - var ( - deadline = time.Now().Add(time.Duration(sleep) * time.Second) - lastNumber = ^hexutil.Uint64(0) - ) + deadline := time.Now().Add(time.Duration(sleep) * time.Second) + var lastNumber hexutil.Uint64 + if err := b.client.Call(&lastNumber, "eth_blockNumber"); err != nil { + return nil, err + } for time.Now().Before(deadline) { var number hexutil.Uint64 - err := b.client.Call(&number, "eth_blockNumber") - if err != nil { + if err := b.client.Call(&number, "eth_blockNumber"); err != nil { return nil, err } if number != lastNumber { diff --git a/contracts/checkpointoracle/contract/oracle.go b/contracts/checkpointoracle/contract/oracle.go index 998ccb93c2..a4a308f5c5 100644 --- a/contracts/checkpointoracle/contract/oracle.go +++ b/contracts/checkpointoracle/contract/oracle.go @@ -27,10 +27,17 @@ var ( ) // CheckpointOracleABI is the input ABI used to generate the binding from. -const CheckpointOracleABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint64\"},{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"name\":\"_hash\",\"type\":\"bytes32\"},{\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"name\":\"v\",\"type\":\"uint8[]\"},{\"name\":\"r\",\"type\":\"bytes32[]\"},{\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"name\":\"_threshold\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"}]" +const CheckpointOracleABI = "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"internalType\":\"uint256\",\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_hash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"internalType\":\"uint8[]\",\"name\":\"v\",\"type\":\"uint8[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"r\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + +// CheckpointOracleFuncSigs maps the 4-byte function signature to its string representation. +var CheckpointOracleFuncSigs = map[string]string{ + "45848dfc": "GetAllAdmin()", + "4d6a304c": "GetLatestCheckpoint()", + "d459fc46": "SetCheckpoint(uint256,bytes32,bytes32,uint64,uint8[],bytes32[],bytes32[])", +} // CheckpointOracleBin is the compiled bytecode used for deploying new contracts. -const CheckpointOracleBin = `0x608060405234801561001057600080fd5b506040516108153803806108158339818101604052608081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8201602081018481111561005e57600080fd5b815185602082028301116401000000008211171561007b57600080fd5b505060208201516040830151606090930151919450925060005b84518110156101415760016000808784815181106100af57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff02191690831515021790555060018582815181106100fc57fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b039093169290921790915501610095565b50600592909255600655600755506106b78061015e6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a661034f565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061036a945050505050565b604080519115158252519081900360200190f35b6060806001805490506040519080825280602002602001820160405280156102e2578160200160208202803883390190505b50905060005b60015481101561034957600181815481106102ff57fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061032957fe5b6001600160a01b03909216602092830291909101909101526001016102e8565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661038657600080fd5b8688401461039357600080fd5b82518451146103a157600080fd5b81518451146103af57600080fd5b6006546005548660010167ffffffffffffffff1602014310156103d457506000610677565b60025467ffffffffffffffff90811690861610156103f457506000610677565b60025467ffffffffffffffff8681169116148015610426575067ffffffffffffffff8516151580610426575060035415155b1561043357506000610677565b8561044057506000610677565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106715760006001848984815181106104b457fe5b60200260200101518985815181106104c857fe5b60200260200101518986815181106104dc57fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa15801561053b573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061057057600080fd5b826001600160a01b0316816001600160a01b03161161058e57600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105ca57fe5b60200260200101518a86815181106105de57fe5b60200260200101518a87815181106105f257fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a260075482600101106106685750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff86161790556001610677565b5060010161049a565b50600080fd5b97965050505050505056fea265627a7a723058207f6a191ce575596a2f1e907c8c0a01003d16b69fb2c4f432d10878e8c0a99a0264736f6c634300050a0032` +var CheckpointOracleBin = "0x608060405234801561001057600080fd5b506040516108703803806108708339818101604052608081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825186602082028301116401000000008211171561008557600080fd5b82525081516020918201928201910280838360005b838110156100b257818101518382015260200161009a565b50505050919091016040908152602083015190830151606090930151909450919250600090505b84518110156101855760016000808784815181106100f357fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff021916908315150217905550600185828151811061014057fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155016100d9565b50600592909255600655600755506106ce806101a26000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a6610365565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610380945050505050565b604080519115158252519081900360200190f35b600154606090819067ffffffffffffffff811180156102ce57600080fd5b506040519080825280602002602001820160405280156102f8578160200160208202803683370190505b50905060005b60015481101561035f576001818154811061031557fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061033f57fe5b6001600160a01b03909216602092830291909101909101526001016102fe565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661039c57600080fd5b868840146103a957600080fd5b82518451146103b757600080fd5b81518451146103c557600080fd5b6006546005548660010167ffffffffffffffff1602014310156103ea5750600061068d565b60025467ffffffffffffffff908116908616101561040a5750600061068d565b60025467ffffffffffffffff868116911614801561043c575067ffffffffffffffff851615158061043c575060035415155b156104495750600061068d565b856104565750600061068d565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106875760006001848984815181106104ca57fe5b60200260200101518985815181106104de57fe5b60200260200101518986815181106104f257fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015610551573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061058657600080fd5b826001600160a01b0316816001600160a01b0316116105a457600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105e057fe5b60200260200101518a86815181106105f457fe5b60200260200101518a878151811061060857fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a2600754826001011061067e5750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff8616179055600161068d565b506001016104b0565b50600080fd5b97965050505050505056fea26469706673582212202ddf9eda76bf59c0fc65584c0b22d84ecef2c703765de60439596d6ac34c2b7264736f6c634300060b0033" // DeployCheckpointOracle deploys a new Ethereum contract, binding an instance of CheckpointOracle to it. func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBackend, _adminlist []common.Address, _sectionSize *big.Int, _processConfirms *big.Int, _threshold *big.Int) (common.Address, *types.Transaction, *CheckpointOracle, error) { @@ -38,6 +45,7 @@ func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBacken if err != nil { return common.Address{}, nil, nil, err } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(CheckpointOracleBin), backend, _adminlist, _sectionSize, _processConfirms, _threshold) if err != nil { return common.Address{}, nil, nil, err @@ -153,7 +161,7 @@ func bindCheckpointOracle(address common.Address, caller bind.ContractCaller, tr // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_CheckpointOracle *CheckpointOracleRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_CheckpointOracle *CheckpointOracleRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _CheckpointOracle.Contract.CheckpointOracleCaller.contract.Call(opts, result, method, params...) } @@ -172,7 +180,7 @@ func (_CheckpointOracle *CheckpointOracleRaw) Transact(opts *bind.TransactOpts, // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_CheckpointOracle *CheckpointOracleCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_CheckpointOracle *CheckpointOracleCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _CheckpointOracle.Contract.contract.Call(opts, result, method, params...) } @@ -189,58 +197,64 @@ func (_CheckpointOracle *CheckpointOracleTransactorRaw) Transact(opts *bind.Tran // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. // -// Solidity: function GetAllAdmin() constant returns(address[]) +// Solidity: function GetAllAdmin() view returns(address[]) func (_CheckpointOracle *CheckpointOracleCaller) GetAllAdmin(opts *bind.CallOpts) ([]common.Address, error) { - var ( - ret0 = new([]common.Address) - ) - out := ret0 - err := _CheckpointOracle.contract.Call(opts, out, "GetAllAdmin") - return *ret0, err + var out []interface{} + err := _CheckpointOracle.contract.Call(opts, &out, "GetAllAdmin") + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + } // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. // -// Solidity: function GetAllAdmin() constant returns(address[]) +// Solidity: function GetAllAdmin() view returns(address[]) func (_CheckpointOracle *CheckpointOracleSession) GetAllAdmin() ([]common.Address, error) { return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts) } // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. // -// Solidity: function GetAllAdmin() constant returns(address[]) +// Solidity: function GetAllAdmin() view returns(address[]) func (_CheckpointOracle *CheckpointOracleCallerSession) GetAllAdmin() ([]common.Address, error) { return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts) } // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. // -// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256) +// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256) func (_CheckpointOracle *CheckpointOracleCaller) GetLatestCheckpoint(opts *bind.CallOpts) (uint64, [32]byte, *big.Int, error) { - var ( - ret0 = new(uint64) - ret1 = new([32]byte) - ret2 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, + var out []interface{} + err := _CheckpointOracle.contract.Call(opts, &out, "GetLatestCheckpoint") + + if err != nil { + return *new(uint64), *new([32]byte), *new(*big.Int), err } - err := _CheckpointOracle.contract.Call(opts, out, "GetLatestCheckpoint") - return *ret0, *ret1, *ret2, err + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + out1 := *abi.ConvertType(out[1], new([32]byte)).(*[32]byte) + out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + + return out0, out1, out2, err + } // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. // -// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256) +// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256) func (_CheckpointOracle *CheckpointOracleSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) { return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts) } // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. // -// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256) +// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256) func (_CheckpointOracle *CheckpointOracleCallerSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) { return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts) } diff --git a/contracts/checkpointoracle/contract/oracle.sol b/contracts/checkpointoracle/contract/oracle.sol index 0106447273..65bac09d28 100644 --- a/contracts/checkpointoracle/contract/oracle.sol +++ b/contracts/checkpointoracle/contract/oracle.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.10; +pragma solidity ^0.6.0; /** * @title CheckpointOracle diff --git a/core/block_validator.go b/core/block_validator.go index d3b98ab3ca..b86790a17f 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -62,7 +62,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash { return fmt.Errorf("uncle root hash mismatch: have %x, want %x", hash, header.UncleHash) } - if hash := types.DeriveSha(block.Transactions(), new(trie.Trie)); hash != header.TxHash { + if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash { return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash) } if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { @@ -92,7 +92,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) } // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) - receiptSha := types.DeriveSha(receipts, new(trie.Trie)) + receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil)) if receiptSha != header.ReceiptHash { return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) } diff --git a/core/bloombits/generator.go b/core/bloombits/generator.go index ae07481ada..646151db0b 100644 --- a/core/bloombits/generator.go +++ b/core/bloombits/generator.go @@ -65,18 +65,23 @@ func (b *Generator) AddBloom(index uint, bloom types.Bloom) error { } // Rotate the bloom and insert into our collection byteIndex := b.nextSec / 8 - bitMask := byte(1) << byte(7-b.nextSec%8) - - for i := 0; i < types.BloomBitLength; i++ { - bloomByteIndex := types.BloomByteLength - 1 - i/8 - bloomBitMask := byte(1) << byte(i%8) - - if (bloom[bloomByteIndex] & bloomBitMask) != 0 { - b.blooms[i][byteIndex] |= bitMask + bitIndex := byte(7 - b.nextSec%8) + for byt := 0; byt < types.BloomByteLength; byt++ { + bloomByte := bloom[types.BloomByteLength-1-byt] + if bloomByte == 0 { + continue } + base := 8 * byt + b.blooms[base+7][byteIndex] |= ((bloomByte >> 7) & 1) << bitIndex + b.blooms[base+6][byteIndex] |= ((bloomByte >> 6) & 1) << bitIndex + b.blooms[base+5][byteIndex] |= ((bloomByte >> 5) & 1) << bitIndex + b.blooms[base+4][byteIndex] |= ((bloomByte >> 4) & 1) << bitIndex + b.blooms[base+3][byteIndex] |= ((bloomByte >> 3) & 1) << bitIndex + b.blooms[base+2][byteIndex] |= ((bloomByte >> 2) & 1) << bitIndex + b.blooms[base+1][byteIndex] |= ((bloomByte >> 1) & 1) << bitIndex + b.blooms[base][byteIndex] |= (bloomByte & 1) << bitIndex } b.nextSec++ - return nil } diff --git a/core/bloombits/generator_test.go b/core/bloombits/generator_test.go index f9bcef96e0..88e3ed6b06 100644 --- a/core/bloombits/generator_test.go +++ b/core/bloombits/generator_test.go @@ -58,3 +58,42 @@ func TestGenerator(t *testing.T) { } } } + +func BenchmarkGenerator(b *testing.B) { + var input [types.BloomBitLength][types.BloomByteLength]byte + b.Run("empty", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Crunch the input through the generator and verify the result + gen, err := NewGenerator(types.BloomBitLength) + if err != nil { + b.Fatalf("failed to create bloombit generator: %v", err) + } + for j, bloom := range input { + if err := gen.AddBloom(uint(j), bloom); err != nil { + b.Fatalf("bloom %d: failed to add: %v", i, err) + } + } + } + }) + for i := 0; i < types.BloomBitLength; i++ { + rand.Read(input[i][:]) + } + b.Run("random", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Crunch the input through the generator and verify the result + gen, err := NewGenerator(types.BloomBitLength) + if err != nil { + b.Fatalf("failed to create bloombit generator: %v", err) + } + for j, bloom := range input { + if err := gen.AddBloom(uint(j), bloom); err != nil { + b.Fatalf("bloom %d: failed to add: %v", i, err) + } + } + } + }) +} diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 066bca1000..4b326c970b 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -94,7 +94,7 @@ type ChainIndexer struct { throttling time.Duration // Disk throttling to prevent a heavy upgrade from hogging resources log log.Logger - lock sync.RWMutex + lock sync.Mutex } // NewChainIndexer creates a new chain indexer to do background processing on diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index cf9b2b0393..a6b3e4420d 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -129,7 +129,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { stats.wiping = nil stats.start = time.Now() - // If generator was aboted during wipe, return + // If generator was aborted during wipe, return case abort := <-dl.genAbort: abort <- stats return @@ -203,7 +203,10 @@ func (dl *diskLayer) generate(stats *generatorStats) { if acc.Root != emptyRoot { storeTrie, err := trie.NewSecure(acc.Root, dl.triedb) if err != nil { - log.Crit("Storage trie inaccessible for snapshot generation", "err", err) + log.Error("Generator failed to access storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) + abort := <-dl.genAbort + abort <- stats + return } var storeMarker []byte if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength { @@ -238,6 +241,12 @@ func (dl *diskLayer) generate(stats *generatorStats) { } } } + if err := storeIt.Err; err != nil { + log.Error("Generator failed to iterate storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) + abort := <-dl.genAbort + abort <- stats + return + } } if time.Since(logged) > 8*time.Second { stats.Log("Generating state snapshot", dl.root, accIt.Key) @@ -246,6 +255,12 @@ func (dl *diskLayer) generate(stats *generatorStats) { // Some account processed, unmark the marker accMarker = nil } + if err := accIt.Err; err != nil { + log.Error("Generator failed to iterate account trie", "root", dl.root, "err", err) + abort := <-dl.genAbort + abort <- stats + return + } // Snapshot fully generated, set the marker to nil if batch.ValueSize() > 0 { batch.Write() diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go new file mode 100644 index 0000000000..03263f3976 --- /dev/null +++ b/core/state/snapshot/generate_test.go @@ -0,0 +1,190 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// Tests that snapshot generation errors out correctly in case of a missing trie +// node in the account trie. +func TestGenerateCorruptAccountTrie(t *testing.T) { + // We can't use statedb to make a test trie (circular dependency), so make + // a fake one manually. We're going with a small account trie of 3 accounts, + // without any storage slots to keep the test smaller. + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + tr, _ := trie.NewSecure(common.Hash{}, triedb) + acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + tr.Update([]byte("acc-1"), val) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074 + + acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + tr.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + + acc = &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + tr.Update([]byte("acc-3"), val) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4 + tr.Commit(nil) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978 + + // Delete an account trie leaf and ensure the generator chokes + triedb.Commit(common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), false, nil) + diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes()) + + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), nil) + select { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt account trie") + + case <-time.After(250 * time.Millisecond): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation errors out correctly in case of a missing root +// trie node for a storage trie. It's similar to internal corruption but it is +// handled differently inside the generator. +func TestGenerateMissingStorageTrie(t *testing.T) { + // We can't use statedb to make a test trie (circular dependency), so make + // a fake one manually. We're going with a small account trie of 3 accounts, + // two of which also has the same 3-slot storage trie attached. + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + stTrie, _ := trie.NewSecure(common.Hash{}, triedb) + stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 + stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 + stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 + stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + + acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + + acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd + + // We can only corrupt the disk database, so flush the tries out + triedb.Reference( + common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), + common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"), + ) + triedb.Reference( + common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), + common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"), + ) + triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil) + + // Delete a storage trie root and ensure the generator chokes + diskdb.Delete(common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67").Bytes()) + + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil) + select { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt storage trie") + + case <-time.After(250 * time.Millisecond): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation errors out correctly in case of a missing trie +// node in a storage trie. +func TestGenerateCorruptStorageTrie(t *testing.T) { + // We can't use statedb to make a test trie (circular dependency), so make + // a fake one manually. We're going with a small account trie of 3 accounts, + // two of which also has the same 3-slot storage trie attached. + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + stTrie, _ := trie.NewSecure(common.Hash{}, triedb) + stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 + stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 + stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 + stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + + acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + + acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd + + // We can only corrupt the disk database, so flush the tries out + triedb.Reference( + common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), + common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"), + ) + triedb.Reference( + common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), + common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"), + ) + triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil) + + // Delete a storage trie leaf and ensure the generator chokes + diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes()) + + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil) + select { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt storage trie") + + case <-time.After(250 * time.Millisecond): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} diff --git a/core/state/state_object.go b/core/state/state_object.go index 16e8d93cf5..8961c135b2 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -315,7 +315,7 @@ func (s *stateObject) updateTrie(db Database) Trie { if len(s.pendingStorage) == 0 { return s.trie } - // Track the amount of time wasted on updating the storge trie + // Track the amount of time wasted on updating the storage trie if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) } @@ -363,7 +363,7 @@ func (s *stateObject) updateRoot(db Database) { if s.updateTrie(db) == nil { return } - // Track the amount of time wasted on hashing the storge trie + // Track the amount of time wasted on hashing the storage trie if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now()) } @@ -380,7 +380,7 @@ func (s *stateObject) CommitTrie(db Database) error { if s.dbErr != nil { return s.dbErr } - // Track the amount of time wasted on committing the storge trie + // Track the amount of time wasted on committing the storage trie if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now()) } diff --git a/core/tx_list.go b/core/tx_list.go index bf304eedcf..cdd3df14c5 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -433,6 +433,7 @@ func (h *priceHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] + old[n-1] = nil *h = old[0 : n-1] return x } diff --git a/core/tx_pool.go b/core/tx_pool.go index 9c2a6a77bb..602d19718d 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -857,6 +857,7 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { nilSlot++ } errs[nilSlot] = err + nilSlot++ } // Reorg the pool internals if needed and return done := pool.requestPromoteExecutables(dirtyAddrs) diff --git a/core/types/bloom9.go b/core/types/bloom9.go index 4970477d4d..abe02b4f8e 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -17,6 +17,7 @@ package types import ( + "encoding/binary" "fmt" "math/big" @@ -57,10 +58,16 @@ func (b *Bloom) SetBytes(d []byte) { } // Add adds d to the filter. Future calls of Test(d) will return true. -func (b *Bloom) Add(d *big.Int) { - bin := new(big.Int).SetBytes(b[:]) - bin.Or(bin, bloom9(d.Bytes())) - b.SetBytes(bin.Bytes()) +func (b *Bloom) Add(d []byte) { + b.add(d, make([]byte, 6)) +} + +// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) +func (b *Bloom) add(d []byte, buf []byte) { + i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) + b[i1] |= v1 + b[i2] |= v2 + b[i3] |= v3 } // Quorum @@ -73,21 +80,23 @@ func (b *Bloom) OrBloom(bl []byte) { } // Big converts b to a big integer. +// Note: Converting a bloom filter to a big.Int and then calling GetBytes +// does not return the same bytes, since big.Int will trim leading zeroes func (b Bloom) Big() *big.Int { return new(big.Int).SetBytes(b[:]) } +// Bytes returns the backing byte slice of the bloom func (b Bloom) Bytes() []byte { return b[:] } -func (b Bloom) Test(test *big.Int) bool { - return BloomLookup(b, test) -} - -func (b Bloom) TestBytes(test []byte) bool { - return b.Test(new(big.Int).SetBytes(test)) - +// Test checks if the given topic is present in the bloom filter +func (b Bloom) Test(topic []byte) bool { + i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) + return v1 == v1&b[i1] && + v2 == v2&b[i2] && + v3 == v3&b[i3] } // MarshalText encodes b as a hex string with 0x prefix. @@ -100,46 +109,61 @@ func (b *Bloom) UnmarshalText(input []byte) error { return hexutil.UnmarshalFixedText("Bloom", input, b[:]) } +// CreateBloom creates a bloom filter out of the give Receipts (+Logs) func CreateBloom(receipts Receipts) Bloom { - bin := new(big.Int) + buf := make([]byte, 6) + var bin Bloom for _, receipt := range receipts { - bin.Or(bin, LogsBloom(receipt.Logs)) + for _, log := range receipt.Logs { + bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { + bin.add(b[:], buf) + } + } } - - return BytesToBloom(bin.Bytes()) + return bin } -func LogsBloom(logs []*Log) *big.Int { - bin := new(big.Int) +// LogsBloom returns the bloom bytes for the given logs +func LogsBloom(logs []*Log) []byte { + buf := make([]byte, 6) + var bin Bloom for _, log := range logs { - bin.Or(bin, bloom9(log.Address.Bytes())) + bin.add(log.Address.Bytes(), buf) for _, b := range log.Topics { - bin.Or(bin, bloom9(b[:])) + bin.add(b[:], buf) } } - - return bin + return bin[:] } -func bloom9(b []byte) *big.Int { - b = crypto.Keccak256(b) - - r := new(big.Int) - - for i := 0; i < 6; i += 2 { - t := big.NewInt(1) - b := (uint(b[i+1]) + (uint(b[i]) << 8)) & 2047 - r.Or(r, t.Lsh(t, b)) - } - - return r +// Bloom9 returns the bloom filter for the given data +func Bloom9(data []byte) []byte { + var b Bloom + b.SetBytes(data) + return b.Bytes() } -var Bloom9 = bloom9 +// bloomValues returns the bytes (index-value pairs) to set for the given data +func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { + sha := hasherPool.Get().(crypto.KeccakState) + sha.Reset() + sha.Write(data) + sha.Read(hashbuf) + hasherPool.Put(sha) + // The actual bits to flip + v1 := byte(1 << (hashbuf[1] & 0x7)) + v2 := byte(1 << (hashbuf[3] & 0x7)) + v3 := byte(1 << (hashbuf[5] & 0x7)) + // The indices for the bytes to OR in + i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 + i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1 + i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 + + return i1, v1, i2, v2, i3, v3 +} +// BloomLookup is a convenience-method to check presence int he bloom filter func BloomLookup(bin Bloom, topic bytesBacked) bool { - bloom := bin.Big() - cmp := bloom9(topic.Bytes()) - - return bloom.And(bloom, cmp).Cmp(cmp) == 0 + return bin.Test(topic.Bytes()) } diff --git a/core/types/bloom9_test.go b/core/types/bloom9_test.go index a28ac0e7af..893df486dd 100644 --- a/core/types/bloom9_test.go +++ b/core/types/bloom9_test.go @@ -17,8 +17,12 @@ package types import ( + "fmt" "math/big" "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" ) func TestBloom(t *testing.T) { @@ -35,47 +39,118 @@ func TestBloom(t *testing.T) { var bloom Bloom for _, data := range positive { - bloom.Add(new(big.Int).SetBytes([]byte(data))) + bloom.Add([]byte(data)) } for _, data := range positive { - if !bloom.TestBytes([]byte(data)) { + if !bloom.Test([]byte(data)) { t.Error("expected", data, "to test true") } } for _, data := range negative { - if bloom.TestBytes([]byte(data)) { + if bloom.Test([]byte(data)) { t.Error("did not expect", data, "to test true") } } } -/* -import ( - "testing" - - "github.com/ethereum/go-ethereum/core/state" -) +// TestBloomExtensively does some more thorough tests +func TestBloomExtensively(t *testing.T) { + var exp = common.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263") + var b Bloom + // Add 100 "random" things + for i := 0; i < 100; i++ { + data := fmt.Sprintf("xxxxxxxxxx data %d yyyyyyyyyyyyyy", i) + b.Add([]byte(data)) + //b.Add(new(big.Int).SetBytes([]byte(data))) + } + got := crypto.Keccak256Hash(b.Bytes()) + if got != exp { + t.Errorf("Got %x, exp %x", got, exp) + } + var b2 Bloom + b2.SetBytes(b.Bytes()) + got2 := crypto.Keccak256Hash(b2.Bytes()) + if got != got2 { + t.Errorf("Got %x, exp %x", got, got2) + } +} -func TestBloom9(t *testing.T) { - testCase := []byte("testtest") - bin := LogsBloom([]state.Log{ - {testCase, [][]byte{[]byte("hellohello")}, nil}, - }).Bytes() - res := BloomLookup(bin, testCase) +func BenchmarkBloom9(b *testing.B) { + test := []byte("testestestest") + for i := 0; i < b.N; i++ { + Bloom9(test) + } +} - if !res { - t.Errorf("Bloom lookup failed") +func BenchmarkBloom9Lookup(b *testing.B) { + toTest := []byte("testtest") + bloom := new(Bloom) + for i := 0; i < b.N; i++ { + bloom.Test(toTest) } } +func BenchmarkCreateBloom(b *testing.B) { -func TestAddress(t *testing.T) { - block := &Block{} - block.Coinbase = common.Hex2Bytes("22341ae42d6dd7384bc8584e50419ea3ac75b83f") - fmt.Printf("%x\n", crypto.Keccak256(block.Coinbase)) + var txs = Transactions{ + NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil), + NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil), + } + var rSmall = Receipts{ + &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x11})}, + {Address: common.BytesToAddress([]byte{0x01, 0x11})}, + }, + TxHash: txs[0].Hash(), + ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 1, + }, + &Receipt{ + PostState: common.Hash{2}.Bytes(), + CumulativeGasUsed: 3, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x22})}, + {Address: common.BytesToAddress([]byte{0x02, 0x22})}, + }, + TxHash: txs[1].Hash(), + ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), + GasUsed: 2, + }, + } - bin := CreateBloom(block) - fmt.Printf("bin = %x\n", common.LeftPadBytes(bin, 64)) + var rLarge = make(Receipts, 200) + // Fill it with 200 receipts x 2 logs + for i := 0; i < 200; i += 2 { + copy(rLarge[i:], rSmall) + } + b.Run("small", func(b *testing.B) { + b.ReportAllocs() + var bl Bloom + for i := 0; i < b.N; i++ { + bl = CreateBloom(rSmall) + } + b.StopTimer() + var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) + if got != exp { + b.Errorf("Got %x, exp %x", got, exp) + } + }) + b.Run("large", func(b *testing.B) { + b.ReportAllocs() + var bl Bloom + for i := 0; i < b.N; i++ { + bl = CreateBloom(rLarge) + } + b.StopTimer() + var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) + if got != exp { + b.Errorf("Got %x, exp %x", got, exp) + } + }) } -*/ diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go index 7d40c7f660..51b8506bce 100644 --- a/core/types/derive_sha.go +++ b/core/types/derive_sha.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// DerivableList is the interface which can derive the hash. type DerivableList interface { Len() int GetRlp(i int) []byte @@ -39,7 +38,22 @@ type Hasher interface { func DeriveSha(list DerivableList, hasher Hasher) common.Hash { hasher.Reset() keybuf := new(bytes.Buffer) - for i := 0; i < list.Len(); i++ { + + // StackTrie requires values to be inserted in increasing + // hash order, which is not the order that `list` provides + // hashes in. This insertion sequence ensures that the + // order is correct. + for i := 1; i < list.Len() && i <= 0x7f; i++ { + keybuf.Reset() + rlp.Encode(keybuf, uint(i)) + hasher.Update(keybuf.Bytes(), list.GetRlp(i)) + } + if list.Len() > 0 { + keybuf.Reset() + rlp.Encode(keybuf, uint(0)) + hasher.Update(keybuf.Bytes(), list.GetRlp(0)) + } + for i := 0x80; i < list.Len(); i++ { keybuf.Reset() rlp.Encode(keybuf, uint(i)) hasher.Update(keybuf.Bytes(), list.GetRlp(i)) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index b8cc5e5a14..751f911f56 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -164,18 +164,18 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi } // 0. If *gasleft* is less than or equal to 2300, fail the current call. -// 1. If current value equals new value (this is a no-op), SSTORE_NOOP_GAS gas is deducted. +// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted. // 2. If current value does not equal new value: // 2.1. If original value equals current value (this storage slot has not been changed by the current execution context): -// 2.1.1. If original value is 0, SSTORE_INIT_GAS gas is deducted. -// 2.1.2. Otherwise, SSTORE_CLEAN_GAS gas is deducted. If new value is 0, add SSTORE_CLEAR_REFUND to refund counter. -// 2.2. If original value does not equal current value (this storage slot is dirty), SSTORE_DIRTY_GAS gas is deducted. Apply both of the following clauses: +// 2.1.1. If original value is 0, SSTORE_SET_GAS (20K) gas is deducted. +// 2.1.2. Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter. +// 2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses: // 2.2.1. If original value is not 0: -// 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEAR_REFUND gas from refund counter. We can prove that refund counter will never go below 0. -// 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEAR_REFUND gas to refund counter. +// 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter. +// 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter. // 2.2.2. If original value equals new value (this storage slot is reset): -// 2.2.2.1. If original value is 0, add SSTORE_INIT_REFUND to refund counter. -// 2.2.2.2. Otherwise, add SSTORE_CLEAN_REFUND gas to refund counter. +// 2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. +// 2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // If we fail the minimum gas availability invariant, fail (0) if contract.Gas <= params.SstoreSentryGasEIP2200 { @@ -189,33 +189,33 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m value := common.Hash(y.Bytes32()) if current == value { // noop (1) - return params.SstoreNoopGasEIP2200, nil + return params.SloadGasEIP2200, nil } original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return params.SstoreInitGasEIP2200, nil + return params.SstoreSetGasEIP2200, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) - evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } - return params.SstoreCleanGasEIP2200, nil // write existing slot (2.1.2) + return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) - evm.StateDB.SubRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200) } else if value == (common.Hash{}) { // delete slot (2.2.1.2) - evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } } if original == value { if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) - evm.StateDB.AddRefund(params.SstoreInitRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) } else { // reset to original existing slot (2.2.2.2) - evm.StateDB.AddRefund(params.SstoreCleanRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) } } - return params.SstoreDirtyGasEIP2200, nil // dirty update (2.2) + return params.SloadGasEIP2200, nil // dirty update (2.2) } func makeGasLog(n uint64) gasFunc { diff --git a/core/vm/logger.go b/core/vm/logger.go index e1d7c67ef1..3b166b5d26 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -322,20 +322,21 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) - if !t.cfg.DisableStack { // format stack + if !t.cfg.DisableStack { + // format stack var a []string for _, elem := range stack.data { a = append(a, fmt.Sprintf("%d", elem)) } b := fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) - } - if !t.cfg.DisableStack { // format return stack - var a []string + + // format return stack + a = a[:0] for _, elem := range rStack.data { a = append(a, fmt.Sprintf("%2d", elem)) } - b := fmt.Sprintf("[%v]", strings.Join(a, ",")) + b = fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) } fmt.Fprintln(t.out, "") diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 0fbf2d5b79..f06331c2d8 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -156,7 +156,7 @@ type Downloader struct { cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited. quitCh chan struct{} // Quit channel to signal termination - quitLock sync.RWMutex // Lock to prevent double closes + quitLock sync.Mutex // Lock to prevent double closes // Testing hooks syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run @@ -531,6 +531,8 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.ancientLimit = d.checkpoint } else if height > fullMaxForkAncestry+1 { d.ancientLimit = height - fullMaxForkAncestry - 1 + } else { + d.ancientLimit = 0 } frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. @@ -622,9 +624,6 @@ func (d *Downloader) cancel() { func (d *Downloader) Cancel() { d.cancel() d.cancelWg.Wait() - - d.ancientLimit = 0 - log.Debug("Reset ancient limit to zero") } // Terminate interrupts the downloader, canceling all pending operations. diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index e38219d5cf..b4e68c3c3f 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -415,7 +415,6 @@ func (dl *downloadTester) dropPeer(id string) { type downloadTesterPeer struct { dl *downloadTester id string - lock sync.RWMutex chain *testChain missingStates map[common.Hash]bool // State entries that fast sync should not return } @@ -1605,7 +1604,7 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { {15000, 13006, []int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999}, }, - //Remote is pretty close to us. We don't have to fetch as many + // Remote is pretty close to us. We don't have to fetch as many {1200, 1150, []int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199}, }, diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 745f7c7480..d2ec8ba694 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -712,6 +712,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh } } if accepted { + parentHash := headers[0].Hash() for i, header := range headers[1:] { hash := header.Hash() if want := request.From + 1 + uint64(i); header.Number.Uint64() != want { @@ -719,11 +720,13 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh accepted = false break } - if headers[i].Hash() != header.ParentHash { + if parentHash != header.ParentHash { log.Warn("Header broke chain ancestry", "peer", id, "number", header.Number, "hash", hash) accepted = false break } + // Set-up parent hash for next round + parentHash = hash } } // If the batch of headers wasn't accepted, mark as unavailable @@ -774,7 +777,7 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLi q.lock.Lock() defer q.lock.Unlock() validate := func(index int, header *types.Header) error { - if types.DeriveSha(types.Transactions(txLists[index]), new(trie.Trie)) != header.TxHash { + if types.DeriveSha(types.Transactions(txLists[index]), trie.NewStackTrie(nil)) != header.TxHash { return errInvalidBody } if types.CalcUncleHash(uncleLists[index]) != header.UncleHash { @@ -799,7 +802,7 @@ func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int, q.lock.Lock() defer q.lock.Unlock() validate := func(index int, header *types.Header) error { - if types.DeriveSha(types.Receipts(receiptList[index]), new(trie.Trie)) != header.ReceiptHash { + if types.DeriveSha(types.Receipts(receiptList[index]), trie.NewStackTrie(nil)) != header.ReceiptHash { return errInvalidReceipt } return nil diff --git a/eth/handler.go b/eth/handler.go index 65acc9bb11..87b68a3413 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -782,7 +782,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { log.Warn("Propagated block has invalid uncles", "have", hash, "exp", request.Block.UncleHash()) break // TODO(karalabe): return error eventually, but wait a few releases } - if hash := types.DeriveSha(request.Block.Transactions(), new(trie.Trie)); hash != request.Block.TxHash() { + if hash := types.DeriveSha(request.Block.Transactions(), trie.NewStackTrie(nil)); hash != request.Block.TxHash() { log.Warn("Propagated block has invalid body", "have", hash, "exp", request.Block.TxHash()) break // TODO(karalabe): return error eventually, but wait a few releases } diff --git a/extension/extensionContracts/contract_extender.go b/extension/extensionContracts/contract_extender.go index 56e800b09c..234bcdaf0b 100644 --- a/extension/extensionContracts/contract_extender.go +++ b/extension/extensionContracts/contract_extender.go @@ -4,7 +4,6 @@ package extensionContracts import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -29,12 +27,12 @@ var ( ) // ContractExtenderABI is the input ABI used to generate the binding from. -const ContractExtenderABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"creator\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"contractToExtend\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"checkIfExtensionFinished\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalNumberOfVoters\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"walletAddressesToVote\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"isFinished\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"nextuuid\",\"type\":\"string\"}],\"name\":\"setUuid\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"sharedDataHash\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"hash\",\"type\":\"string\"}],\"name\":\"setSharedStateHash\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"updatePartyMembers\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"voteOutcome\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"checkIfVoted\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"finish\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"votes\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"vote\",\"type\":\"bool\"},{\"name\":\"nextuuid\",\"type\":\"string\"}],\"name\":\"doVote\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"targetRecipientPTMKey\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"haveAllNodesVoted\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"contractAddress\",\"type\":\"address\"},{\"name\":\"recipientAddress\",\"type\":\"address\"},{\"name\":\"recipientPTMKey\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"recipientPTMKey\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"recipientAddress\",\"type\":\"address\"}],\"name\":\"NewContractExtensionContractCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"outcome\",\"type\":\"bool\"}],\"name\":\"AllNodesHaveAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CanPerformStateShare\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"ExtensionFinished\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"vote\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"voter\",\"type\":\"address\"}],\"name\":\"NewVote\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"tesserahash\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"uuid\",\"type\":\"string\"}],\"name\":\"StateShared\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"uuid\",\"type\":\"string\"}],\"name\":\"UpdateMembers\",\"type\":\"event\"}]" +const ContractExtenderABI = "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"contractAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipientAddress\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"recipientPTMKey\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"outcome\",\"type\":\"bool\"}],\"name\":\"AllNodesHaveAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CanPerformStateShare\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"ExtensionFinished\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"recipientPTMKey\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipientAddress\",\"type\":\"address\"}],\"name\":\"NewContractExtensionContractCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"vote\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"}],\"name\":\"NewVote\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"tesserahash\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"uuid\",\"type\":\"string\"}],\"name\":\"StateShared\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"toExtend\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"uuid\",\"type\":\"string\"}],\"name\":\"UpdateMembers\",\"type\":\"event\"},{\"constant\":true,\"inputs\":[],\"name\":\"checkIfExtensionFinished\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"checkIfVoted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"contractToExtend\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"creator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"bool\",\"name\":\"vote\",\"type\":\"bool\"},{\"internalType\":\"string\",\"name\":\"nextuuid\",\"type\":\"string\"}],\"name\":\"doVote\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"finish\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"haveAllNodesVoted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"isFinished\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"string\",\"name\":\"hash\",\"type\":\"string\"}],\"name\":\"setSharedStateHash\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"string\",\"name\":\"nextuuid\",\"type\":\"string\"}],\"name\":\"setUuid\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"sharedDataHash\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"targetRecipientPTMKey\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalNumberOfVoters\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"updatePartyMembers\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"voteOutcome\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"votes\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"walletAddressesToVote\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]" var ContractExtenderParsedABI, _ = abi.JSON(strings.NewReader(ContractExtenderABI)) // ContractExtenderBin is the compiled bytecode used for deploying new contracts. -var ContractExtenderBin = "0x60806040523480156200001157600080fd5b506040516200135738038062001357833981018060405260608110156200003757600080fd5b81516020830151604084018051929491938201926401000000008111156200005e57600080fd5b820160208101848111156200007257600080fd5b81516401000000008111828201871017156200008d57600080fd5b5050600080546001600160a01b031916331790558051909350620000bb92506001915060208401906200028d565b50600280546001600160a01b038086166001600160a01b031992831617909255600380546001818101835560008381527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b92830180548616331790558354918201909355018054938616939092169290921790556040805160208101918290528290526200014d91600a91906200028d565b506009805460ff19166001179055600060068190555b600354811015620001c1576001600560006003848154811015156200018457fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff191691151591909117905560010162000163565b50600354600455604080516001600160a01b0380861682528416918101919091526060602080830182815284519284019290925283517f04576ede6057794ada68966eebc285c98a2726cbc4929ffd1ad9900336728d9393879386938893608084019186019080838360005b83811015620002475781810151838201526020016200022d565b50505050905090810190601f168015620002755780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a150505062000332565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620002d057805160ff191683800117855562000300565b8280016001018555821562000300579182015b8281111562000300578251825591602001919060010190620002e3565b506200030e92915062000312565b5090565b6200032f91905b808211156200030e576000815560010162000319565b90565b61101580620003426000396000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c8063893971ba116100a2578063d56b288911610071578063d56b28891461037a578063d8bff5a514610382578063de5828cb146103a8578063e5af0f3014610457578063f57077d81461045f5761010b565b8063893971ba146102bc578063ac8b920514610362578063b5da45bb1461036a578063cb2805ec146103725761010b565b806379d41b8f116100de57806379d41b8f146101725780637b3529621461018f578063821e93da1461019757806388f520a01461023f5761010b565b806302d05d3f1461011057806315e56a6a146101345780631962cb9b1461013c5780633852772714610158575b600080fd5b610118610467565b604080516001600160a01b039092168252519081900360200190f35b610118610476565b610144610485565b604080519115158252519081900360200190f35b61016061048f565b60408051918252519081900360200190f35b6101186004803603602081101561018857600080fd5b5035610495565b6101446104bd565b61023d600480360360208110156101ad57600080fd5b8101906020810181356401000000008111156101c857600080fd5b8201836020820111156101da57600080fd5b803590602001918460018302840111640100000000831117156101fc57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506104c6945050505050565b005b610247610554565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610281578181015183820152602001610269565b50505050905090810190601f1680156102ae5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61023d600480360360208110156102d257600080fd5b8101906020810181356401000000008111156102ed57600080fd5b8201836020820111156102ff57600080fd5b8035906020019184600183028401116401000000008311171561032157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506105e2945050505050565b61023d61094c565b610144610a47565b610144610a50565b61023d610a66565b6101446004803603602081101561039857600080fd5b50356001600160a01b0316610b01565b61023d600480360360408110156103be57600080fd5b8135151591908101906040810160208201356401000000008111156103e257600080fd5b8201836020820111156103f457600080fd5b8035906020019184600183028401116401000000008311171561041657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610b16945050505050565b610247610bbb565b610144610c15565b6000546001600160a01b031681565b6002546001600160a01b031681565b600c5460ff165b90565b60045481565b60038054829081106104a357fe5b6000918252602090912001546001600160a01b0316905081565b600c5460ff1681565b600c5460ff161561050b57604051600160e51b62461bcd028152600401808060200182810382526025815260200180610fc56025913960400191505060405180910390fd5b600b805460018101808355600092909252825161054f917f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db901906020850190610ee1565b505050565b600a805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156105da5780601f106105af576101008083540402835291602001916105da565b820191906000526020600020905b8154815290600101906020018083116105bd57829003601f168201915b505050505081565b6000546001600160a01b0316331461062e57604051600160e51b62461bcd028152600401808060200182810382526023815260200180610fa26023913960400191505060405180910390fd5b600c5460ff161561067357604051600160e51b62461bcd028152600401808060200182810382526025815260200180610fc56025913960400191505060405180910390fd5b600a8054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156106ff5780601f106106d4576101008083540402835291602001916106ff565b820191906000526020600020905b8154815290600101906020018083116106e257829003601f168201915b505085519394508593151592506107639150505760408051600160e51b62461bcd02815260206004820152601860248201527f6e657720686173682063616e6e6f7420626520656d7074790000000000000000604482015290519081900360640190fd5b8151156107ba5760408051600160e51b62461bcd02815260206004820152601660248201527f7374617465206861736820616c72656164792073657400000000000000000000604482015290519081900360640190fd5b82516107cd90600a906020860190610ee1565b5060005b600b5481101561094357600254600b80547f67a92539f3cbd7c5a9b36c23c0e2beceb27d2e1b3cd8eda02c623689267ae71e926001600160a01b031691600a918590811061081b57fe5b6000918252602091829020604080516001600160a01b038716815260609481018581528654600260001961010060018416150201909116049582018690529290930193908301906080840190869080156108b65780601f1061088b576101008083540402835291602001916108b6565b820191906000526020600020905b81548152906001019060200180831161089957829003601f168201915b505083810382528454600260001961010060018416150201909116048082526020909101908590801561092a5780601f106108ff5761010080835404028352916020019161092a565b820191906000526020600020905b81548152906001019060200180831161090d57829003601f168201915b50509550505050505060405180910390a16001016107d1565b5061054f610a66565b60005b600b54811015610a4457600254600b80547f8adc4573f947f9930560525736f61b116be55049125cb63a36887a40f92f3b44926001600160a01b031691908490811061099757fe5b6000918252602091829020604080516001600160a01b0386168152938401818152919092018054600260001961010060018416150201909116049284018390529291606083019084908015610a2d5780601f10610a0257610100808354040283529160200191610a2d565b820191906000526020600020905b815481529060010190602001808311610a1057829003601f168201915b5050935050505060405180910390a160010161094f565b50565b60095460ff1681565b3360009081526007602052604090205460ff1690565b600c5460ff1615610aab57604051600160e51b62461bcd028152600401808060200182810382526025815260200180610fc56025913960400191505060405180910390fd5b6000546001600160a01b03163314610af757604051600160e51b62461bcd028152600401808060200182810382526023815260200180610fa26023913960400191505060405180910390fd5b610aff610c1f565b565b60086020526000908152604090205460ff1681565b600c5460ff1615610b5b57604051600160e51b62461bcd028152600401808060200182810382526025815260200180610fc56025913960400191505060405180910390fd5b610b6482610c57565b8115610b7357610b73816104c6565b610b7b610e28565b60408051831515815233602082015281517f225708d30006b0cc86d855ab91047edb5fe9c2e416412f36c18c6e90fe4e461f929181900390910190a15050565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156105da5780601f106105af576101008083540402835291602001916105da565b6006546003541490565b600c805460ff191660011790556040517f79c47b570b18a8a814b785800e5fcbf104e067663589cef1bba07756e3c6ede990600090a1565b600c5460ff1615610c9c57604051600160e51b62461bcd028152600401808060200182810382526028815260200180610f7a6028913960400191505060405180910390fd5b3360009081526005602052604090205460ff161515610d055760408051600160e51b62461bcd02815260206004820152601360248201527f6e6f7420616c6c6f77656420746f20766f746500000000000000000000000000604482015290519081900360640190fd5b3360009081526007602052604090205460ff1615610d6d5760408051600160e51b62461bcd02815260206004820152600d60248201527f616c726561647920766f74656400000000000000000000000000000000000000604482015290519081900360640190fd5b60095460ff161515610dc95760408051600160e51b62461bcd02815260206004820152601760248201527f766f74696e6720616c7265616479206465636c696e6564000000000000000000604482015290519081900360640190fd5b3360009081526007602090815260408083208054600160ff19918216811790925560089093529220805490911683151517905560068054909101905560095460ff168015610e145750805b6009805460ff191691151591909117905550565b60095460ff161515610e7557604080516000815290517ff20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd369181900360200190a1610e70610c1f565b610aff565b610e7d610c15565b15610aff57604080516001815290517ff20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd369181900360200190a16040517ffd46cafaa71d87561071b8095703a7f081265fad232945049f5cf2d2c39b3d2890600090a1565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610f2257805160ff1916838001178555610f4f565b82800160010185558215610f4f579182015b82811115610f4f578251825591602001919060010190610f34565b50610f5b929150610f5f565b5090565b61048c91905b80821115610f5b5760008155600101610f6556fe657874656e73696f6e2070726f6365737320636f6d706c657465642e2063616e6e6f7420766f74656f6e6c79206c6561646572206d617920706572666f726d207468697320616374696f6e657874656e73696f6e20686173206265656e206d61726b65642061732066696e6973686564a165627a7a723058201430571f2ddc1fc4db18fbe992d5cb9955a993fb01a34d163aebb1b5d891a6f00029" +var ContractExtenderBin = "0x60806040523480156200001157600080fd5b5060405162001cbb38038062001cbb833981810160405260608110156200003757600080fd5b810190808051906020019092919080519060200190929190805160405193929190846401000000008211156200006c57600080fd5b838201915060208201858111156200008357600080fd5b8251866001820283011164010000000082111715620000a157600080fd5b8083526020830192505050908051906020019080838360005b83811015620000d7578082015181840152602081019050620000ba565b50505050905090810190601f168015620001055780820380516001836020036101000a031916815260200191505b50604052505050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060019080519060200190620001649291906200048c565b5082600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060033390806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060038290806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060405180602001604052806000815250600a9080519060200190620002999291906200048c565b506001600960006101000a81548160ff021916908315150217905550600060068190555060008090505b6003805490508110156200036f5760016005600060038481548110620002e557fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508080600101915050620002c3565b506003805490506004819055507f04576ede6057794ada68966eebc285c98a2726cbc4929ffd1ad9900336728d93838284604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001806020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019080838360005b838110156200044657808201518184015260208101905062000429565b50505050905090810190601f168015620004745780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a15050506200053b565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620004cf57805160ff191683800117855562000500565b8280016001018555821562000500579182015b82811115620004ff578251825591602001919060010190620004e2565b5b5090506200050f919062000513565b5090565b6200053891905b80821115620005345760008160009055506001016200051a565b5090565b90565b611770806200054b6000396000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c8063893971ba116100a2578063d56b288911610071578063d56b2889146104bb578063d8bff5a5146104c5578063de5828cb14610521578063e5af0f30146105e8578063f57077d81461066b5761010b565b8063893971ba146103b2578063ac8b92051461046d578063b5da45bb14610477578063cb2805ec146104995761010b565b806379d41b8f116100de57806379d41b8f146101e45780637b35296214610252578063821e93da1461027457806388f520a01461032f5761010b565b806302d05d3f1461011057806315e56a6a1461015a5780631962cb9b146101a457806338527727146101c6575b600080fd5b61011861068d565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101626106b2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101ac6106d8565b604051808215151515815260200191505060405180910390f35b6101ce6106ef565b6040518082815260200191505060405180910390f35b610210600480360360208110156101fa57600080fd5b81019080803590602001909291905050506106f5565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61025a610731565b604051808215151515815260200191505060405180910390f35b61032d6004803603602081101561028a57600080fd5b81019080803590602001906401000000008111156102a757600080fd5b8201836020820111156102b957600080fd5b803590602001918460018302840111640100000000831117156102db57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610744565b005b6103376107ec565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561037757808201518184015260208101905061035c565b50505050905090810190601f1680156103a45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61046b600480360360208110156103c857600080fd5b81019080803590602001906401000000008111156103e557600080fd5b8201836020820111156103f757600080fd5b8035906020019184600183028401116401000000008311171561041957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061088a565b005b610475610d1d565b005b61047f610e65565b604051808215151515815260200191505060405180910390f35b6104a1610e78565b604051808215151515815260200191505060405180910390f35b6104c3610ecc565b005b610507600480360360208110156104db57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610fe1565b604051808215151515815260200191505060405180910390f35b6105e66004803603604081101561053757600080fd5b810190808035151590602001909291908035906020019064010000000081111561056057600080fd5b82018360208201111561057257600080fd5b8035906020019184600183028401116401000000008311171561059457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050611001565b005b6105f06110fb565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610630578082015181840152602081019050610615565b50505050905090810190601f16801561065d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610673611199565b604051808215151515815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600c60009054906101000a900460ff16905090565b60045481565b6003818154811061070257fe5b906000526020600020016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600c60009054906101000a900460ff1681565b600c60009054906101000a900460ff16156107aa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806117176025913960400191505060405180910390fd5b600b8190806001815401808255809150509060018203906000526020600020016000909192909190915090805190602001906107e7929190611626565b505050565b600a8054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156108825780601f1061085757610100808354040283529160200191610882565b820191906000526020600020905b81548152906001019060200180831161086557829003601f168201915b505050505081565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461092f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806116f46023913960400191505060405180910390fd5b600c60009054906101000a900460ff1615610995576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806117176025913960400191505060405180910390fd5b6060600a8054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610a2d5780601f10610a0257610100808354040283529160200191610a2d565b820191906000526020600020905b815481529060010190602001808311610a1057829003601f168201915b505050505090506060829050600081511415610ab1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f6e657720686173682063616e6e6f7420626520656d707479000000000000000081525060200191505060405180910390fd5b6000825114610b28576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f7374617465206861736820616c7265616479207365740000000000000000000081525060200191505060405180910390fd5b82600a9080519060200190610b3e929190611626565b5060008090505b600b80549050811015610d0f577f67a92539f3cbd7c5a9b36c23c0e2beceb27d2e1b3cd8eda02c623689267ae71e600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600a600b8481548110610ba557fe5b90600052602060002001604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018060200180602001838103835285818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610c6e5780601f10610c4357610100808354040283529160200191610c6e565b820191906000526020600020905b815481529060010190602001808311610c5157829003601f168201915b5050838103825284818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610cf15780601f10610cc657610100808354040283529160200191610cf1565b820191906000526020600020905b815481529060010190602001808311610cd457829003601f168201915b50509550505050505060405180910390a18080600101915050610b45565b50610d18610ecc565b505050565b60008090505b600b80549050811015610e62577f8adc4573f947f9930560525736f61b116be55049125cb63a36887a40f92f3b44600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600b8381548110610d8157fe5b90600052602060002001604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610e465780601f10610e1b57610100808354040283529160200191610e46565b820191906000526020600020905b815481529060010190602001808311610e2957829003601f168201915b5050935050505060405180910390a18080600101915050610d23565b50565b600960009054906101000a900460ff1681565b6000600760003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905090565b600c60009054906101000a900460ff1615610f32576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806117176025913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610fd7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806116f46023913960400191505060405180910390fd5b610fdf6111aa565b565b60086020528060005260406000206000915054906101000a900460ff1681565b600c60009054906101000a900460ff1615611067576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806117176025913960400191505060405180910390fd5b611070826111f3565b81156110805761107f81610744565b5b611088611550565b7f225708d30006b0cc86d855ab91047edb5fe9c2e416412f36c18c6e90fe4e461f823360405180831515151581526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a15050565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156111915780601f1061116657610100808354040283529160200191611191565b820191906000526020600020905b81548152906001019060200180831161117457829003601f168201915b505050505081565b600060065460038054905014905090565b6001600c60006101000a81548160ff0219169083151502179055507f79c47b570b18a8a814b785800e5fcbf104e067663589cef1bba07756e3c6ede960405160405180910390a1565b600c60009054906101000a900460ff1615611259576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806116cc6028913960400191505060405180910390fd5b600560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16611318576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f6e6f7420616c6c6f77656420746f20766f74650000000000000000000000000081525060200191505060405180910390fd5b600760003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16156113d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f616c726561647920766f7465640000000000000000000000000000000000000081525060200191505060405180910390fd5b600960009054906101000a900460ff1661145a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260178152602001807f766f74696e6720616c7265616479206465636c696e656400000000000000000081525060200191505060405180910390fd5b6001600760003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555080600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550600660008154809291906001019190505550600960009054906101000a900460ff1680156115345750805b600960006101000a81548160ff02191690831515021790555050565b600960009054906101000a900460ff166115ad577ff20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd366000604051808215151515815260200191505060405180910390a16115a86111aa565b611624565b6115b5611199565b15611623577ff20540914db019dd7c8d05ed165316a58d1583642772ac46f3d0c29b8644bd366001604051808215151515815260200191505060405180910390a17ffd46cafaa71d87561071b8095703a7f081265fad232945049f5cf2d2c39b3d2860405160405180910390a15b5b565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061166757805160ff1916838001178555611695565b82800160010185558215611695579182015b82811115611694578251825591602001919060010190611679565b5b5090506116a291906116a6565b5090565b6116c891905b808211156116c45760008160009055506001016116ac565b5090565b9056fe657874656e73696f6e2070726f6365737320636f6d706c657465642e2063616e6e6f7420766f74656f6e6c79206c6561646572206d617920706572666f726d207468697320616374696f6e657874656e73696f6e20686173206265656e206d61726b65642061732066696e6973686564a265627a7a72315820625108b92f7ff30d44757ae1bb19335828b2892b67a277794ea401fa969f7bdf64736f6c63430005110032" // DeployContractExtender deploys a new Ethereum contract, binding an instance of ContractExtender to it. func DeployContractExtender(auth *bind.TransactOpts, backend bind.ContractBackend, contractAddress common.Address, recipientAddress common.Address, recipientPTMKey string) (common.Address, *types.Transaction, *ContractExtender, error) { @@ -158,7 +156,7 @@ func bindContractExtender(address common.Address, caller bind.ContractCaller, tr // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_ContractExtender *ContractExtenderRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_ContractExtender *ContractExtenderRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _ContractExtender.Contract.ContractExtenderCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_ContractExtender *ContractExtenderRaw) Transact(opts *bind.TransactOpts, // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_ContractExtender *ContractExtenderCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_ContractExtender *ContractExtenderCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _ContractExtender.Contract.contract.Call(opts, result, method, params...) } @@ -194,312 +192,372 @@ func (_ContractExtender *ContractExtenderTransactorRaw) Transact(opts *bind.Tran // CheckIfExtensionFinished is a free data retrieval call binding the contract method 0x1962cb9b. // -// Solidity: function checkIfExtensionFinished() constant returns(bool) +// Solidity: function checkIfExtensionFinished() view returns(bool) func (_ContractExtender *ContractExtenderCaller) CheckIfExtensionFinished(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "checkIfExtensionFinished") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "checkIfExtensionFinished") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckIfExtensionFinished is a free data retrieval call binding the contract method 0x1962cb9b. // -// Solidity: function checkIfExtensionFinished() constant returns(bool) +// Solidity: function checkIfExtensionFinished() view returns(bool) func (_ContractExtender *ContractExtenderSession) CheckIfExtensionFinished() (bool, error) { return _ContractExtender.Contract.CheckIfExtensionFinished(&_ContractExtender.CallOpts) } // CheckIfExtensionFinished is a free data retrieval call binding the contract method 0x1962cb9b. // -// Solidity: function checkIfExtensionFinished() constant returns(bool) +// Solidity: function checkIfExtensionFinished() view returns(bool) func (_ContractExtender *ContractExtenderCallerSession) CheckIfExtensionFinished() (bool, error) { return _ContractExtender.Contract.CheckIfExtensionFinished(&_ContractExtender.CallOpts) } // CheckIfVoted is a free data retrieval call binding the contract method 0xcb2805ec. // -// Solidity: function checkIfVoted() constant returns(bool) +// Solidity: function checkIfVoted() view returns(bool) func (_ContractExtender *ContractExtenderCaller) CheckIfVoted(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "checkIfVoted") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "checkIfVoted") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckIfVoted is a free data retrieval call binding the contract method 0xcb2805ec. // -// Solidity: function checkIfVoted() constant returns(bool) +// Solidity: function checkIfVoted() view returns(bool) func (_ContractExtender *ContractExtenderSession) CheckIfVoted() (bool, error) { return _ContractExtender.Contract.CheckIfVoted(&_ContractExtender.CallOpts) } // CheckIfVoted is a free data retrieval call binding the contract method 0xcb2805ec. // -// Solidity: function checkIfVoted() constant returns(bool) +// Solidity: function checkIfVoted() view returns(bool) func (_ContractExtender *ContractExtenderCallerSession) CheckIfVoted() (bool, error) { return _ContractExtender.Contract.CheckIfVoted(&_ContractExtender.CallOpts) } // ContractToExtend is a free data retrieval call binding the contract method 0x15e56a6a. // -// Solidity: function contractToExtend() constant returns(address) +// Solidity: function contractToExtend() view returns(address) func (_ContractExtender *ContractExtenderCaller) ContractToExtend(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "contractToExtend") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "contractToExtend") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // ContractToExtend is a free data retrieval call binding the contract method 0x15e56a6a. // -// Solidity: function contractToExtend() constant returns(address) +// Solidity: function contractToExtend() view returns(address) func (_ContractExtender *ContractExtenderSession) ContractToExtend() (common.Address, error) { return _ContractExtender.Contract.ContractToExtend(&_ContractExtender.CallOpts) } // ContractToExtend is a free data retrieval call binding the contract method 0x15e56a6a. // -// Solidity: function contractToExtend() constant returns(address) +// Solidity: function contractToExtend() view returns(address) func (_ContractExtender *ContractExtenderCallerSession) ContractToExtend() (common.Address, error) { return _ContractExtender.Contract.ContractToExtend(&_ContractExtender.CallOpts) } // Creator is a free data retrieval call binding the contract method 0x02d05d3f. // -// Solidity: function creator() constant returns(address) +// Solidity: function creator() view returns(address) func (_ContractExtender *ContractExtenderCaller) Creator(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "creator") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "creator") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // Creator is a free data retrieval call binding the contract method 0x02d05d3f. // -// Solidity: function creator() constant returns(address) +// Solidity: function creator() view returns(address) func (_ContractExtender *ContractExtenderSession) Creator() (common.Address, error) { return _ContractExtender.Contract.Creator(&_ContractExtender.CallOpts) } // Creator is a free data retrieval call binding the contract method 0x02d05d3f. // -// Solidity: function creator() constant returns(address) +// Solidity: function creator() view returns(address) func (_ContractExtender *ContractExtenderCallerSession) Creator() (common.Address, error) { return _ContractExtender.Contract.Creator(&_ContractExtender.CallOpts) } // HaveAllNodesVoted is a free data retrieval call binding the contract method 0xf57077d8. // -// Solidity: function haveAllNodesVoted() constant returns(bool) +// Solidity: function haveAllNodesVoted() view returns(bool) func (_ContractExtender *ContractExtenderCaller) HaveAllNodesVoted(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "haveAllNodesVoted") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "haveAllNodesVoted") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // HaveAllNodesVoted is a free data retrieval call binding the contract method 0xf57077d8. // -// Solidity: function haveAllNodesVoted() constant returns(bool) +// Solidity: function haveAllNodesVoted() view returns(bool) func (_ContractExtender *ContractExtenderSession) HaveAllNodesVoted() (bool, error) { return _ContractExtender.Contract.HaveAllNodesVoted(&_ContractExtender.CallOpts) } // HaveAllNodesVoted is a free data retrieval call binding the contract method 0xf57077d8. // -// Solidity: function haveAllNodesVoted() constant returns(bool) +// Solidity: function haveAllNodesVoted() view returns(bool) func (_ContractExtender *ContractExtenderCallerSession) HaveAllNodesVoted() (bool, error) { return _ContractExtender.Contract.HaveAllNodesVoted(&_ContractExtender.CallOpts) } // IsFinished is a free data retrieval call binding the contract method 0x7b352962. // -// Solidity: function isFinished() constant returns(bool) +// Solidity: function isFinished() view returns(bool) func (_ContractExtender *ContractExtenderCaller) IsFinished(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "isFinished") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "isFinished") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsFinished is a free data retrieval call binding the contract method 0x7b352962. // -// Solidity: function isFinished() constant returns(bool) +// Solidity: function isFinished() view returns(bool) func (_ContractExtender *ContractExtenderSession) IsFinished() (bool, error) { return _ContractExtender.Contract.IsFinished(&_ContractExtender.CallOpts) } // IsFinished is a free data retrieval call binding the contract method 0x7b352962. // -// Solidity: function isFinished() constant returns(bool) +// Solidity: function isFinished() view returns(bool) func (_ContractExtender *ContractExtenderCallerSession) IsFinished() (bool, error) { return _ContractExtender.Contract.IsFinished(&_ContractExtender.CallOpts) } // SharedDataHash is a free data retrieval call binding the contract method 0x88f520a0. // -// Solidity: function sharedDataHash() constant returns(string) +// Solidity: function sharedDataHash() view returns(string) func (_ContractExtender *ContractExtenderCaller) SharedDataHash(opts *bind.CallOpts) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "sharedDataHash") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "sharedDataHash") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // SharedDataHash is a free data retrieval call binding the contract method 0x88f520a0. // -// Solidity: function sharedDataHash() constant returns(string) +// Solidity: function sharedDataHash() view returns(string) func (_ContractExtender *ContractExtenderSession) SharedDataHash() (string, error) { return _ContractExtender.Contract.SharedDataHash(&_ContractExtender.CallOpts) } // SharedDataHash is a free data retrieval call binding the contract method 0x88f520a0. // -// Solidity: function sharedDataHash() constant returns(string) +// Solidity: function sharedDataHash() view returns(string) func (_ContractExtender *ContractExtenderCallerSession) SharedDataHash() (string, error) { return _ContractExtender.Contract.SharedDataHash(&_ContractExtender.CallOpts) } // TargetRecipientPTMKey is a free data retrieval call binding the contract method 0xe5af0f30. // -// Solidity: function targetRecipientPTMKey() constant returns(string) +// Solidity: function targetRecipientPTMKey() view returns(string) func (_ContractExtender *ContractExtenderCaller) TargetRecipientPTMKey(opts *bind.CallOpts) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "targetRecipientPTMKey") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "targetRecipientPTMKey") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // TargetRecipientPTMKey is a free data retrieval call binding the contract method 0xe5af0f30. // -// Solidity: function targetRecipientPTMKey() constant returns(string) +// Solidity: function targetRecipientPTMKey() view returns(string) func (_ContractExtender *ContractExtenderSession) TargetRecipientPTMKey() (string, error) { return _ContractExtender.Contract.TargetRecipientPTMKey(&_ContractExtender.CallOpts) } // TargetRecipientPTMKey is a free data retrieval call binding the contract method 0xe5af0f30. // -// Solidity: function targetRecipientPTMKey() constant returns(string) +// Solidity: function targetRecipientPTMKey() view returns(string) func (_ContractExtender *ContractExtenderCallerSession) TargetRecipientPTMKey() (string, error) { return _ContractExtender.Contract.TargetRecipientPTMKey(&_ContractExtender.CallOpts) } // TotalNumberOfVoters is a free data retrieval call binding the contract method 0x38527727. // -// Solidity: function totalNumberOfVoters() constant returns(uint256) +// Solidity: function totalNumberOfVoters() view returns(uint256) func (_ContractExtender *ContractExtenderCaller) TotalNumberOfVoters(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "totalNumberOfVoters") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "totalNumberOfVoters") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // TotalNumberOfVoters is a free data retrieval call binding the contract method 0x38527727. // -// Solidity: function totalNumberOfVoters() constant returns(uint256) +// Solidity: function totalNumberOfVoters() view returns(uint256) func (_ContractExtender *ContractExtenderSession) TotalNumberOfVoters() (*big.Int, error) { return _ContractExtender.Contract.TotalNumberOfVoters(&_ContractExtender.CallOpts) } // TotalNumberOfVoters is a free data retrieval call binding the contract method 0x38527727. // -// Solidity: function totalNumberOfVoters() constant returns(uint256) +// Solidity: function totalNumberOfVoters() view returns(uint256) func (_ContractExtender *ContractExtenderCallerSession) TotalNumberOfVoters() (*big.Int, error) { return _ContractExtender.Contract.TotalNumberOfVoters(&_ContractExtender.CallOpts) } // VoteOutcome is a free data retrieval call binding the contract method 0xb5da45bb. // -// Solidity: function voteOutcome() constant returns(bool) +// Solidity: function voteOutcome() view returns(bool) func (_ContractExtender *ContractExtenderCaller) VoteOutcome(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "voteOutcome") - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "voteOutcome") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // VoteOutcome is a free data retrieval call binding the contract method 0xb5da45bb. // -// Solidity: function voteOutcome() constant returns(bool) +// Solidity: function voteOutcome() view returns(bool) func (_ContractExtender *ContractExtenderSession) VoteOutcome() (bool, error) { return _ContractExtender.Contract.VoteOutcome(&_ContractExtender.CallOpts) } // VoteOutcome is a free data retrieval call binding the contract method 0xb5da45bb. // -// Solidity: function voteOutcome() constant returns(bool) +// Solidity: function voteOutcome() view returns(bool) func (_ContractExtender *ContractExtenderCallerSession) VoteOutcome() (bool, error) { return _ContractExtender.Contract.VoteOutcome(&_ContractExtender.CallOpts) } // Votes is a free data retrieval call binding the contract method 0xd8bff5a5. // -// Solidity: function votes(address ) constant returns(bool) +// Solidity: function votes(address ) view returns(bool) func (_ContractExtender *ContractExtenderCaller) Votes(opts *bind.CallOpts, arg0 common.Address) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "votes", arg0) - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "votes", arg0) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // Votes is a free data retrieval call binding the contract method 0xd8bff5a5. // -// Solidity: function votes(address ) constant returns(bool) +// Solidity: function votes(address ) view returns(bool) func (_ContractExtender *ContractExtenderSession) Votes(arg0 common.Address) (bool, error) { return _ContractExtender.Contract.Votes(&_ContractExtender.CallOpts, arg0) } // Votes is a free data retrieval call binding the contract method 0xd8bff5a5. // -// Solidity: function votes(address ) constant returns(bool) +// Solidity: function votes(address ) view returns(bool) func (_ContractExtender *ContractExtenderCallerSession) Votes(arg0 common.Address) (bool, error) { return _ContractExtender.Contract.Votes(&_ContractExtender.CallOpts, arg0) } // WalletAddressesToVote is a free data retrieval call binding the contract method 0x79d41b8f. // -// Solidity: function walletAddressesToVote(uint256 ) constant returns(address) +// Solidity: function walletAddressesToVote(uint256 ) view returns(address) func (_ContractExtender *ContractExtenderCaller) WalletAddressesToVote(opts *bind.CallOpts, arg0 *big.Int) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _ContractExtender.contract.Call(opts, out, "walletAddressesToVote", arg0) - return *ret0, err + var out []interface{} + err := _ContractExtender.contract.Call(opts, &out, "walletAddressesToVote", arg0) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // WalletAddressesToVote is a free data retrieval call binding the contract method 0x79d41b8f. // -// Solidity: function walletAddressesToVote(uint256 ) constant returns(address) +// Solidity: function walletAddressesToVote(uint256 ) view returns(address) func (_ContractExtender *ContractExtenderSession) WalletAddressesToVote(arg0 *big.Int) (common.Address, error) { return _ContractExtender.Contract.WalletAddressesToVote(&_ContractExtender.CallOpts, arg0) } // WalletAddressesToVote is a free data retrieval call binding the contract method 0x79d41b8f. // -// Solidity: function walletAddressesToVote(uint256 ) constant returns(address) +// Solidity: function walletAddressesToVote(uint256 ) view returns(address) func (_ContractExtender *ContractExtenderCallerSession) WalletAddressesToVote(arg0 *big.Int) (common.Address, error) { return _ContractExtender.Contract.WalletAddressesToVote(&_ContractExtender.CallOpts, arg0) } diff --git a/extension/extensionContracts/extensionHandler.go b/extension/extensionContracts/extensionHandler.go index 39f43aaf8b..e80945b87e 100644 --- a/extension/extensionContracts/extensionHandler.go +++ b/extension/extensionContracts/extensionHandler.go @@ -4,7 +4,7 @@ import "github.com/ethereum/go-ethereum/common" func UnpackStateSharedLog(logData []byte) (common.Address, string, string, error) { decodedLog := new(ContractExtenderStateShared) - if err := ContractExtenderParsedABI.Unpack(decodedLog, "StateShared", logData); err != nil { + if err := ContractExtenderParsedABI.UnpackIntoInterface(decodedLog, "StateShared", logData); err != nil { return common.Address{}, "", "", err } return decodedLog.ToExtend, decodedLog.Tesserahash, decodedLog.Uuid, nil @@ -12,7 +12,7 @@ func UnpackStateSharedLog(logData []byte) (common.Address, string, string, error func UnpackNewExtensionCreatedLog(data []byte) (*ContractExtenderNewContractExtensionContractCreated, error) { newExtensionEvent := new(ContractExtenderNewContractExtensionContractCreated) - err := ContractExtenderParsedABI.Unpack(newExtensionEvent, "NewContractExtensionContractCreated", data) + err := ContractExtenderParsedABI.UnpackIntoInterface(newExtensionEvent, "NewContractExtensionContractCreated", data) return newExtensionEvent, err } diff --git a/extension/extensionContracts/gen.go b/extension/extensionContracts/gen.go new file mode 100644 index 0000000000..23044a0e53 --- /dev/null +++ b/extension/extensionContracts/gen.go @@ -0,0 +1,5 @@ +package extensionContracts + +//go:generate solc --abi --bin -o . contract_extender.sol +//go:generate abigen -pkg extensionContract -abi ./ContractExtender.abi -bin ./ContractExtender.bin -type ContractExtender -out ./contract_extender.go +//go:generate rm ContractExtender.abi ContractExtender.bin diff --git a/go.mod b/go.mod index 0ee17436c0..17eb613f9d 100755 --- a/go.mod +++ b/go.mod @@ -95,5 +95,4 @@ require ( gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 gopkg.in/oleiade/lane.v1 v1.0.0 gopkg.in/urfave/cli.v1 v1.20.0 - gotest.tools v2.2.0+incompatible // indirect ) diff --git a/internal/debug/api.go b/internal/debug/api.go index 86a4218f6a..efd8626776 100644 --- a/internal/debug/api.go +++ b/internal/debug/api.go @@ -196,7 +196,7 @@ func (*HandlerT) Stacks() string { return buf.String() } -// FreeOSMemory returns unused memory to the OS. +// FreeOSMemory forces a garbage collection. func (*HandlerT) FreeOSMemory() { debug.FreeOSMemory() } diff --git a/internal/utesting/utesting.go b/internal/utesting/utesting.go index 23c748cae9..4de0ecf99a 100644 --- a/internal/utesting/utesting.go +++ b/internal/utesting/utesting.go @@ -65,10 +65,17 @@ func MatchTests(tests []Test, expr string) []Test { func RunTests(tests []Test, report io.Writer) []Result { results := make([]Result, len(tests)) for i, test := range tests { + var output io.Writer + buffer := new(bytes.Buffer) + output = buffer + if report != nil { + output = io.MultiWriter(buffer, report) + } start := time.Now() results[i].Name = test.Name - results[i].Failed, results[i].Output = Run(test) + results[i].Failed = run(test, output) results[i].Duration = time.Since(start) + results[i].Output = buffer.String() if report != nil { printResult(results[i], report) } @@ -80,7 +87,6 @@ func printResult(r Result, w io.Writer) { pd := r.Duration.Truncate(100 * time.Microsecond) if r.Failed { fmt.Fprintf(w, "-- FAIL %s (%v)\n", r.Name, pd) - fmt.Fprintln(w, r.Output) } else { fmt.Fprintf(w, "-- OK %s (%v)\n", r.Name, pd) } @@ -99,7 +105,13 @@ func CountFailures(rr []Result) int { // Run executes a single test. func Run(test Test) (bool, string) { - t := new(T) + output := new(bytes.Buffer) + failed := run(test, output) + return failed, output.String() +} + +func run(test Test, output io.Writer) bool { + t := &T{output: output} done := make(chan struct{}) go func() { defer close(done) @@ -114,7 +126,7 @@ func Run(test Test) (bool, string) { test.Fn(t) }() <-done - return t.failed, t.output.String() + return t.failed } // T is the value given to the test function. The test can signal failures @@ -122,7 +134,7 @@ func Run(test Test) (bool, string) { type T struct { mu sync.Mutex failed bool - output bytes.Buffer + output io.Writer } // FailNow marks the test as having failed and stops its execution by calling @@ -151,7 +163,7 @@ func (t *T) Failed() bool { func (t *T) Log(vs ...interface{}) { t.mu.Lock() defer t.mu.Unlock() - fmt.Fprintln(&t.output, vs...) + fmt.Fprintln(t.output, vs...) } // Logf formats its arguments according to the format, analogous to Printf, and records @@ -162,7 +174,7 @@ func (t *T) Logf(format string, vs ...interface{}) { if len(format) == 0 || format[len(format)-1] != '\n' { format += "\n" } - fmt.Fprintf(&t.output, format, vs...) + fmt.Fprintf(t.output, format, vs...) } // Error is equivalent to Log followed by Fail. diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 0f6592fc72..09df41dde8 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -248,7 +248,8 @@ web3._extend({ new web3._extend.Method({ name: 'printBlock', call: 'debug_printBlock', - params: 1 + params: 1, + outputFormatter: console.log }), new web3._extend.Method({ name: 'getBlockRlp', @@ -259,7 +260,7 @@ web3._extend({ name: 'testSignCliqueBlock', call: 'debug_testSignCliqueBlock', params: 2, - inputFormatters: [web3._extend.formatters.inputAddressFormatter, null], + inputFormatter: [web3._extend.formatters.inputAddressFormatter, null], }), new web3._extend.Method({ name: 'setHead', @@ -274,8 +275,8 @@ web3._extend({ new web3._extend.Method({ name: 'dumpBlock', call: 'debug_dumpBlock', - params: 2, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, ""] + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), new web3._extend.Method({ name: 'privateStateRoot', @@ -439,7 +440,7 @@ web3._extend({ name: 'traceBlockByNumber', call: 'debug_traceBlockByNumber', params: 2, - inputFormatter: [null, null] + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, null] }), new web3._extend.Method({ name: 'traceBlockByHash', @@ -557,7 +558,8 @@ web3._extend({ new web3._extend.Method({ name: 'getHeaderByNumber', call: 'eth_getHeaderByNumber', - params: 1 + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), new web3._extend.Method({ name: 'getHeaderByHash', @@ -567,12 +569,14 @@ web3._extend({ new web3._extend.Method({ name: 'getBlockByNumber', call: 'eth_getBlockByNumber', - params: 2 + params: 2, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, function (val) { return !!val; }] }), new web3._extend.Method({ name: 'getBlockByHash', call: 'eth_getBlockByHash', - params: 2 + params: 2, + inputFormatter: [null, function (val) { return !!val; }] }), new web3._extend.Method({ name: 'getRawTransaction', diff --git a/les/txrelay.go b/les/txrelay.go index 4f6c15025e..57f2412eba 100644 --- a/les/txrelay.go +++ b/les/txrelay.go @@ -35,7 +35,7 @@ type lesTxRelay struct { txPending map[common.Hash]struct{} peerList []*serverPeer peerStartPos int - lock sync.RWMutex + lock sync.Mutex stop chan struct{} retriever *retrieveManager diff --git a/light/odr.go b/light/odr.go index 0b854b0b6c..7016ef8ef2 100644 --- a/light/odr.go +++ b/light/odr.go @@ -133,7 +133,7 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { } } -// ChtRequest is the ODR request type for state/storage trie entries +// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie type ChtRequest struct { Untrusted bool // Indicator whether the result retrieved is trusted or not PeerId string // The specified peer id from which to retrieve data. diff --git a/miner/miner.go b/miner/miner.go index 6c46f08d51..a4840e60e8 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -88,15 +88,22 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even // and halt your mining operation for as long as the DOS continues. func (miner *Miner) update() { events := miner.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{}) - defer events.Unsubscribe() + defer func() { + if !events.Closed() { + events.Unsubscribe() + } + }() shouldStart := false canStart := true + dlEventCh := events.Chan() for { select { - case ev := <-events.Chan(): + case ev := <-dlEventCh: if ev == nil { - return + // Unsubscription done, stop listening + dlEventCh = nil + continue } switch ev.Data.(type) { case downloader.StartEvent: @@ -108,16 +115,24 @@ func (miner *Miner) update() { shouldStart = true log.Info("Mining aborted due to sync") } - case downloader.DoneEvent, downloader.FailedEvent: + case downloader.FailedEvent: + canStart = true + if shouldStart { + miner.SetEtherbase(miner.coinbase) + miner.worker.start() + } + case downloader.DoneEvent: canStart = true if shouldStart { miner.SetEtherbase(miner.coinbase) miner.worker.start() } + // Stop reacting to downloader events + events.Unsubscribe() } case addr := <-miner.startCh: + miner.SetEtherbase(addr) if canStart { - miner.SetEtherbase(addr) miner.worker.start() } shouldStart = true diff --git a/miner/miner_test.go b/miner/miner_test.go index cccc9a1201..690dbaca6b 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -99,12 +99,75 @@ func TestMiner(t *testing.T) { // Stop the downloader and wait for the update loop to run mux.Post(downloader.DoneEvent{}) waitForMiningState(t, miner, true) - // Start the downloader and wait for the update loop to run + + // Subsequent downloader events after a successful DoneEvent should not cause the + // miner to start or stop. This prevents a security vulnerability + // that would allow entities to present fake high blocks that would + // stop mining operations by causing a downloader sync + // until it was discovered they were invalid, whereon mining would resume. + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, true) + + mux.Post(downloader.FailedEvent{}) + waitForMiningState(t, miner, true) +} + +// TestMinerDownloaderFirstFails tests that mining is only +// permitted to run indefinitely once the downloader sees a DoneEvent (success). +// An initial FailedEvent should allow mining to stop on a subsequent +// downloader StartEvent. +func TestMinerDownloaderFirstFails(t *testing.T) { + miner, mux := createMiner(t) + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + // Start the downloader mux.Post(downloader.StartEvent{}) waitForMiningState(t, miner, false) + // Stop the downloader and wait for the update loop to run mux.Post(downloader.FailedEvent{}) waitForMiningState(t, miner, true) + + // Since the downloader hasn't yet emitted a successful DoneEvent, + // we expect the miner to stop on next StartEvent. + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + + // Downloader finally succeeds. + mux.Post(downloader.DoneEvent{}) + waitForMiningState(t, miner, true) + + // Downloader starts again. + // Since it has achieved a DoneEvent once, we expect miner + // state to be unchanged. + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, true) + + mux.Post(downloader.FailedEvent{}) + waitForMiningState(t, miner, true) +} + +func TestMinerStartStopAfterDownloaderEvents(t *testing.T) { + miner, mux := createMiner(t) + + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + // Start the downloader + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + + // Downloader finally succeeds. + mux.Post(downloader.DoneEvent{}) + waitForMiningState(t, miner, true) + + miner.Stop() + waitForMiningState(t, miner, false) + + miner.Start(common.HexToAddress("0x678910")) + waitForMiningState(t, miner, true) + + miner.Stop() + waitForMiningState(t, miner, false) } func TestStartWhileDownload(t *testing.T) { @@ -139,6 +202,28 @@ func TestCloseMiner(t *testing.T) { waitForMiningState(t, miner, false) } +// TestMinerSetEtherbase checks that etherbase becomes set even if mining isn't +// possible at the moment +func TestMinerSetEtherbase(t *testing.T) { + miner, mux := createMiner(t) + // Start with a 'bad' mining address + miner.Start(common.HexToAddress("0xdead")) + waitForMiningState(t, miner, true) + // Start the downloader + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + // Now user tries to configure proper mining address + miner.Start(common.HexToAddress("0x1337")) + // Stop the downloader and wait for the update loop to run + mux.Post(downloader.DoneEvent{}) + + waitForMiningState(t, miner, true) + // The miner should now be using the good address + if got, exp := miner.coinbase, common.HexToAddress("0x1337"); got != exp { + t.Fatalf("Wrong coinbase, got %x expected %x", got, exp) + } +} + // waitForMiningState waits until either // * the desired mining state was reached // * a timeout was reached which fails the test @@ -147,10 +232,10 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) { var state bool for i := 0; i < 100; i++ { + time.Sleep(10 * time.Millisecond) if state = m.Mining(); state == mining { return } - time.Sleep(10 * time.Millisecond) } t.Fatalf("Mining() == %t, want %t", state, mining) } diff --git a/miner/unconfirmed.go b/miner/unconfirmed.go index 3a176e8bd6..0489f1ea4a 100644 --- a/miner/unconfirmed.go +++ b/miner/unconfirmed.go @@ -50,7 +50,7 @@ type unconfirmedBlocks struct { chain chainRetriever // Blockchain to verify canonical status through depth uint // Depth after which to discard previous blocks blocks *ring.Ring // Block infos to allow canonical chain cross checks - lock sync.RWMutex // Protects the fields from concurrent access + lock sync.Mutex // Protects the fields from concurrent access } // newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks. diff --git a/mobile/big.go b/mobile/big.go index 86ea93245a..c08bcf93f2 100644 --- a/mobile/big.go +++ b/mobile/big.go @@ -35,6 +35,16 @@ func NewBigInt(x int64) *BigInt { return &BigInt{big.NewInt(x)} } +// NewBigIntFromString allocates and returns a new BigInt set to x +// interpreted in the provided base. +func NewBigIntFromString(x string, base int) *BigInt { + b, success := new(big.Int).SetString(x, base) + if !success { + return nil + } + return &BigInt{b} +} + // GetBytes returns the absolute value of x as a big-endian byte slice. func (bi *BigInt) GetBytes() []byte { return bi.bigint.Bytes() diff --git a/mobile/bind.go b/mobile/bind.go index f64b37ec15..afa97b5382 100644 --- a/mobile/bind.go +++ b/mobile/bind.go @@ -171,20 +171,12 @@ func (c *BoundContract) GetDeployer() *Transaction { // Call invokes the (constant) contract method with params as input values and // sets the output to result. func (c *BoundContract) Call(opts *CallOpts, out *Interfaces, method string, args *Interfaces) error { - if len(out.objects) == 1 { - result := out.objects[0] - if err := c.contract.Call(&opts.opts, result, method, args.objects...); err != nil { - return err - } - out.objects[0] = result - } else { - results := make([]interface{}, len(out.objects)) - copy(results, out.objects) - if err := c.contract.Call(&opts.opts, &results, method, args.objects...); err != nil { - return err - } - copy(out.objects, results) + results := make([]interface{}, len(out.objects)) + copy(results, out.objects) + if err := c.contract.Call(&opts.opts, &results, method, args.objects...); err != nil { + return err } + copy(out.objects, results) return nil } diff --git a/node/rpcstack.go b/node/rpcstack.go index 1c032f6e5d..71ad367248 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -330,7 +330,7 @@ func (h *httpServer) wsAllowed() bool { // isWebsocket checks the header of an http request for a websocket upgrade request. func isWebsocket(r *http.Request) bool { return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && - strings.ToLower(r.Header.Get("Connection")) == "upgrade" + strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade") } // NewHTTPHandlerStack returns wrapped http-related handlers diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 7f7889e81c..a56383d8c5 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -73,6 +73,21 @@ func TestWebsocketOrigins(t *testing.T) { assert.Error(t, err) } +// TestIsWebsocket tests if an incoming websocket upgrade request is handled properly. +func TestIsWebsocket(t *testing.T) { + r, _ := http.NewRequest("GET", "/", nil) + + assert.False(t, isWebsocket(r)) + r.Header.Set("upgrade", "websocket") + assert.False(t, isWebsocket(r)) + r.Header.Set("connection", "upgrade") + assert.True(t, isWebsocket(r)) + r.Header.Set("connection", "upgrade,keep-alive") + assert.True(t, isWebsocket(r)) + r.Header.Set("connection", " UPGRADE,keep-alive") + assert.True(t, isWebsocket(r)) +} + func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfig) *httpServer { t.Helper() diff --git a/p2p/discover/node.go b/p2p/discover/node.go index e635c64ac9..9ffe101ccf 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -46,7 +46,10 @@ func encodePubkey(key *ecdsa.PublicKey) encPubkey { return e } -func decodePubkey(curve elliptic.Curve, e encPubkey) (*ecdsa.PublicKey, error) { +func decodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { + if len(e) != len(encPubkey{}) { + return nil, errors.New("wrong size public key data") + } p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)} half := len(e) / 2 p.X.SetBytes(e[:half]) diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 44b62e751b..47a2e7ac3c 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -146,7 +146,6 @@ func (t *pingRecorder) updateRecord(n *enode.Node) { func (t *pingRecorder) Self() *enode.Node { return nullNode } func (t *pingRecorder) lookupSelf() []*enode.Node { return nil } func (t *pingRecorder) lookupRandom() []*enode.Node { return nil } -func (t *pingRecorder) close() {} // ping simulates a ping request. func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) { @@ -188,15 +187,16 @@ func hasDuplicates(slice []*node) bool { return false } +// checkNodesEqual checks whether the two given node lists contain the same nodes. func checkNodesEqual(got, want []*enode.Node) error { if len(got) == len(want) { for i := range got { if !nodeEqual(got[i], want[i]) { goto NotEqual } - return nil } } + return nil NotEqual: output := new(bytes.Buffer) @@ -227,6 +227,7 @@ func sortedByDistanceTo(distbase enode.ID, slice []*node) bool { }) } +// hexEncPrivkey decodes h as a private key. func hexEncPrivkey(h string) *ecdsa.PrivateKey { b, err := hex.DecodeString(h) if err != nil { @@ -239,6 +240,7 @@ func hexEncPrivkey(h string) *ecdsa.PrivateKey { return key } +// hexEncPubkey decodes h as a public key. func hexEncPubkey(h string) (ret encPubkey) { b, err := hex.DecodeString(h) if err != nil { diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index 2009385262..a00de9ca18 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -34,7 +34,7 @@ func TestUDPv4_Lookup(t *testing.T) { test := newUDPTest(t) // Lookup on empty table returns no nodes. - targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target) + targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target[:]) if results := test.udp.LookupPubkey(targetKey); len(results) > 0 { t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) } @@ -279,17 +279,21 @@ func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node { return result } -func (tn *preminedTestnet) neighborsAtDistance(base *enode.Node, distance uint, elems int) []*enode.Node { - nodes := nodesByDistance{target: base.ID()} +func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []uint, elems int) []*enode.Node { + var result []*enode.Node for d := range lookupTestnet.dists { for i := range lookupTestnet.dists[d] { n := lookupTestnet.node(d, i) - if uint(enode.LogDist(n.ID(), base.ID())) == distance { - nodes.push(wrapNode(n), elems) + d := enode.LogDist(base.ID(), n.ID()) + if containsUint(uint(d), distances) { + result = append(result, n) + if len(result) >= elems { + return result + } } } } - return unwrapNodes(nodes.entries) + return result } func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) { diff --git a/p2p/discover/v5_encoding.go b/p2p/discover/v5_encoding.go deleted file mode 100644 index 842234e790..0000000000 --- a/p2p/discover/v5_encoding.go +++ /dev/null @@ -1,659 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discover - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/ecdsa" - "crypto/elliptic" - crand "crypto/rand" - "crypto/sha256" - "errors" - "fmt" - "hash" - "net" - "time" - - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/hkdf" -) - -// TODO concurrent WHOAREYOU tie-breaker -// TODO deal with WHOAREYOU amplification factor (min packet size?) -// TODO add counter to nonce -// TODO rehandshake after X packets - -// Discovery v5 packet types. -const ( - p_pingV5 byte = iota + 1 - p_pongV5 - p_findnodeV5 - p_nodesV5 - p_requestTicketV5 - p_ticketV5 - p_regtopicV5 - p_regconfirmationV5 - p_topicqueryV5 - p_unknownV5 = byte(255) // any non-decryptable packet - p_whoareyouV5 = byte(254) // the WHOAREYOU packet -) - -// Discovery v5 packet structures. -type ( - // unknownV5 represents any packet that can't be decrypted. - unknownV5 struct { - AuthTag []byte - } - - // WHOAREYOU contains the handshake challenge. - whoareyouV5 struct { - AuthTag []byte - IDNonce [32]byte // To be signed by recipient. - RecordSeq uint64 // ENR sequence number of recipient - - node *enode.Node - sent mclock.AbsTime - } - - // PING is sent during liveness checks. - pingV5 struct { - ReqID []byte - ENRSeq uint64 - } - - // PONG is the reply to PING. - pongV5 struct { - ReqID []byte - ENRSeq uint64 - ToIP net.IP // These fields should mirror the UDP envelope address of the ping - ToPort uint16 // packet, which provides a way to discover the the external address (after NAT). - } - - // FINDNODE is a query for nodes in the given bucket. - findnodeV5 struct { - ReqID []byte - Distance uint - } - - // NODES is the reply to FINDNODE and TOPICQUERY. - nodesV5 struct { - ReqID []byte - Total uint8 - Nodes []*enr.Record - } - - // REQUESTTICKET requests a ticket for a topic queue. - requestTicketV5 struct { - ReqID []byte - Topic []byte - } - - // TICKET is the response to REQUESTTICKET. - ticketV5 struct { - ReqID []byte - Ticket []byte - } - - // REGTOPIC registers the sender in a topic queue using a ticket. - regtopicV5 struct { - ReqID []byte - Ticket []byte - ENR *enr.Record - } - - // REGCONFIRMATION is the reply to REGTOPIC. - regconfirmationV5 struct { - ReqID []byte - Registered bool - } - - // TOPICQUERY asks for nodes with the given topic. - topicqueryV5 struct { - ReqID []byte - Topic []byte - } -) - -const ( - // Encryption/authentication parameters. - authSchemeName = "gcm" - aesKeySize = 16 - gcmNonceSize = 12 - idNoncePrefix = "discovery-id-nonce" - handshakeTimeout = time.Second -) - -var ( - errTooShort = errors.New("packet too short") - errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake") - errHandshakeNonceMismatch = errors.New("wrong nonce in auth response") - errInvalidAuthKey = errors.New("invalid ephemeral pubkey") - errUnknownAuthScheme = errors.New("unknown auth scheme in handshake") - errNoRecord = errors.New("expected ENR in handshake but none sent") - errInvalidNonceSig = errors.New("invalid ID nonce signature") - zeroNonce = make([]byte, gcmNonceSize) -) - -// wireCodec encodes and decodes discovery v5 packets. -type wireCodec struct { - sha256 hash.Hash - localnode *enode.LocalNode - privkey *ecdsa.PrivateKey - myChtagHash enode.ID - myWhoareyouMagic []byte - - sc *sessionCache -} - -type handshakeSecrets struct { - writeKey, readKey, authRespKey []byte -} - -type authHeader struct { - authHeaderList - isHandshake bool -} - -type authHeaderList struct { - Auth []byte // authentication info of packet - IDNonce [32]byte // IDNonce of WHOAREYOU - Scheme string // name of encryption/authentication scheme - EphemeralKey []byte // ephemeral public key - Response []byte // encrypted authResponse -} - -type authResponse struct { - Version uint - Signature []byte - Record *enr.Record `rlp:"nil"` // sender's record -} - -func (h *authHeader) DecodeRLP(r *rlp.Stream) error { - k, _, err := r.Kind() - if err != nil { - return err - } - if k == rlp.Byte || k == rlp.String { - return r.Decode(&h.Auth) - } - h.isHandshake = true - return r.Decode(&h.authHeaderList) -} - -// ephemeralKey decodes the ephemeral public key in the header. -func (h *authHeaderList) ephemeralKey(curve elliptic.Curve) *ecdsa.PublicKey { - var key encPubkey - copy(key[:], h.EphemeralKey) - pubkey, _ := decodePubkey(curve, key) - return pubkey -} - -// newWireCodec creates a wire codec. -func newWireCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *wireCodec { - c := &wireCodec{ - sha256: sha256.New(), - localnode: ln, - privkey: key, - sc: newSessionCache(1024, clock), - } - // Create magic strings for packet matching. - self := ln.ID() - c.myWhoareyouMagic = c.sha256sum(self[:], []byte("WHOAREYOU")) - copy(c.myChtagHash[:], c.sha256sum(self[:])) - return c -} - -// encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The -// 'challenge' parameter should be the most recently received WHOAREYOU packet from that -// node. -func (c *wireCodec) encode(id enode.ID, addr string, packet packetV5, challenge *whoareyouV5) ([]byte, []byte, error) { - if packet.kind() == p_whoareyouV5 { - p := packet.(*whoareyouV5) - enc, err := c.encodeWhoareyou(id, p) - if err == nil { - c.sc.storeSentHandshake(id, addr, p) - } - return enc, nil, err - } - // Ensure calling code sets node if needed. - if challenge != nil && challenge.node == nil { - panic("BUG: missing challenge.node in encode") - } - writeKey := c.sc.writeKey(id, addr) - if writeKey != nil || challenge != nil { - return c.encodeEncrypted(id, addr, packet, writeKey, challenge) - } - return c.encodeRandom(id) -} - -// encodeRandom encodes a random packet. -func (c *wireCodec) encodeRandom(toID enode.ID) ([]byte, []byte, error) { - tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID()) - r := make([]byte, 44) // TODO randomize size - if _, err := crand.Read(r); err != nil { - return nil, nil, err - } - nonce := make([]byte, gcmNonceSize) - if _, err := crand.Read(nonce); err != nil { - return nil, nil, fmt.Errorf("can't get random data: %v", err) - } - b := new(bytes.Buffer) - b.Write(tag[:]) - rlp.Encode(b, nonce) - b.Write(r) - return b.Bytes(), nonce, nil -} - -// encodeWhoareyou encodes WHOAREYOU. -func (c *wireCodec) encodeWhoareyou(toID enode.ID, packet *whoareyouV5) ([]byte, error) { - // Sanity check node field to catch misbehaving callers. - if packet.RecordSeq > 0 && packet.node == nil { - panic("BUG: missing node in whoareyouV5 with non-zero seq") - } - b := new(bytes.Buffer) - b.Write(c.sha256sum(toID[:], []byte("WHOAREYOU"))) - err := rlp.Encode(b, packet) - return b.Bytes(), err -} - -// encodeEncrypted encodes an encrypted packet. -func (c *wireCodec) encodeEncrypted(toID enode.ID, toAddr string, packet packetV5, writeKey []byte, challenge *whoareyouV5) (enc []byte, authTag []byte, err error) { - nonce := make([]byte, gcmNonceSize) - if _, err := crand.Read(nonce); err != nil { - return nil, nil, fmt.Errorf("can't get random data: %v", err) - } - - var headEnc []byte - if challenge == nil { - // Regular packet, use existing key and simply encode nonce. - headEnc, _ = rlp.EncodeToBytes(nonce) - } else { - // We're answering WHOAREYOU, generate new keys and encrypt with those. - header, sec, err := c.makeAuthHeader(nonce, challenge) - if err != nil { - return nil, nil, err - } - if headEnc, err = rlp.EncodeToBytes(header); err != nil { - return nil, nil, err - } - c.sc.storeNewSession(toID, toAddr, sec.readKey, sec.writeKey) - writeKey = sec.writeKey - } - - // Encode the packet. - body := new(bytes.Buffer) - body.WriteByte(packet.kind()) - if err := rlp.Encode(body, packet); err != nil { - return nil, nil, err - } - tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID()) - headsize := len(tag) + len(headEnc) - headbuf := make([]byte, headsize) - copy(headbuf[:], tag[:]) - copy(headbuf[len(tag):], headEnc) - - // Encrypt the body. - enc, err = encryptGCM(headbuf, writeKey, nonce, body.Bytes(), tag[:]) - return enc, nonce, err -} - -// encodeAuthHeader creates the auth header on a call packet following WHOAREYOU. -func (c *wireCodec) makeAuthHeader(nonce []byte, challenge *whoareyouV5) (*authHeaderList, *handshakeSecrets, error) { - resp := &authResponse{Version: 5} - - // Add our record to response if it's newer than what remote - // side has. - ln := c.localnode.Node() - if challenge.RecordSeq < ln.Seq() { - resp.Record = ln.Record() - } - - // Create the ephemeral key. This needs to be first because the - // key is part of the ID nonce signature. - var remotePubkey = new(ecdsa.PublicKey) - if err := challenge.node.Load((*enode.Secp256k1)(remotePubkey)); err != nil { - return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient") - } - ephkey, err := crypto.GenerateKey() - if err != nil { - return nil, nil, fmt.Errorf("can't generate ephemeral key") - } - ephpubkey := encodePubkey(&ephkey.PublicKey) - - // Add ID nonce signature to response. - idsig, err := c.signIDNonce(challenge.IDNonce[:], ephpubkey[:]) - if err != nil { - return nil, nil, fmt.Errorf("can't sign: %v", err) - } - resp.Signature = idsig - - // Create session keys. - sec := c.deriveKeys(c.localnode.ID(), challenge.node.ID(), ephkey, remotePubkey, challenge) - if sec == nil { - return nil, nil, fmt.Errorf("key derivation failed") - } - - // Encrypt the authentication response and assemble the auth header. - respRLP, err := rlp.EncodeToBytes(resp) - if err != nil { - return nil, nil, fmt.Errorf("can't encode auth response: %v", err) - } - respEnc, err := encryptGCM(nil, sec.authRespKey, zeroNonce, respRLP, nil) - if err != nil { - return nil, nil, fmt.Errorf("can't encrypt auth response: %v", err) - } - head := &authHeaderList{ - Auth: nonce, - Scheme: authSchemeName, - IDNonce: challenge.IDNonce, - EphemeralKey: ephpubkey[:], - Response: respEnc, - } - return head, sec, err -} - -// deriveKeys generates session keys using elliptic-curve Diffie-Hellman key agreement. -func (c *wireCodec) deriveKeys(n1, n2 enode.ID, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, challenge *whoareyouV5) *handshakeSecrets { - eph := ecdh(priv, pub) - if eph == nil { - return nil - } - - info := []byte("discovery v5 key agreement") - info = append(info, n1[:]...) - info = append(info, n2[:]...) - kdf := hkdf.New(c.sha256reset, eph, challenge.IDNonce[:], info) - sec := handshakeSecrets{ - writeKey: make([]byte, aesKeySize), - readKey: make([]byte, aesKeySize), - authRespKey: make([]byte, aesKeySize), - } - kdf.Read(sec.writeKey) - kdf.Read(sec.readKey) - kdf.Read(sec.authRespKey) - for i := range eph { - eph[i] = 0 - } - return &sec -} - -// signIDNonce creates the ID nonce signature. -func (c *wireCodec) signIDNonce(nonce, ephkey []byte) ([]byte, error) { - idsig, err := crypto.Sign(c.idNonceHash(nonce, ephkey), c.privkey) - if err != nil { - return nil, fmt.Errorf("can't sign: %v", err) - } - return idsig[:len(idsig)-1], nil // remove recovery ID -} - -// idNonceHash computes the hash of id nonce with prefix. -func (c *wireCodec) idNonceHash(nonce, ephkey []byte) []byte { - h := c.sha256reset() - h.Write([]byte(idNoncePrefix)) - h.Write(nonce) - h.Write(ephkey) - return h.Sum(nil) -} - -// decode decodes a discovery packet. -func (c *wireCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) { - // Delete timed-out handshakes. This must happen before decoding to avoid - // processing the same handshake twice. - c.sc.handshakeGC() - - if len(input) < 32 { - return enode.ID{}, nil, nil, errTooShort - } - if bytes.HasPrefix(input, c.myWhoareyouMagic) { - p, err := c.decodeWhoareyou(input) - return enode.ID{}, nil, p, err - } - sender := xorTag(input[:32], c.myChtagHash) - p, n, err := c.decodeEncrypted(sender, addr, input) - return sender, n, p, err -} - -// decodeWhoareyou decode a WHOAREYOU packet. -func (c *wireCodec) decodeWhoareyou(input []byte) (packetV5, error) { - packet := new(whoareyouV5) - err := rlp.DecodeBytes(input[32:], packet) - return packet, err -} - -// decodeEncrypted decodes an encrypted discovery packet. -func (c *wireCodec) decodeEncrypted(fromID enode.ID, fromAddr string, input []byte) (packetV5, *enode.Node, error) { - // Decode packet header. - var head authHeader - r := bytes.NewReader(input[32:]) - err := rlp.Decode(r, &head) - if err != nil { - return nil, nil, err - } - - // Decrypt and process auth response. - readKey, node, err := c.decodeAuth(fromID, fromAddr, &head) - if err != nil { - return nil, nil, err - } - - // Decrypt and decode the packet body. - headsize := len(input) - r.Len() - bodyEnc := input[headsize:] - body, err := decryptGCM(readKey, head.Auth, bodyEnc, input[:32]) - if err != nil { - if !head.isHandshake { - // Can't decrypt, start handshake. - return &unknownV5{AuthTag: head.Auth}, nil, nil - } - return nil, nil, fmt.Errorf("handshake failed: %v", err) - } - if len(body) == 0 { - return nil, nil, errTooShort - } - p, err := decodePacketBodyV5(body[0], body[1:]) - return p, node, err -} - -// decodeAuth processes an auth header. -func (c *wireCodec) decodeAuth(fromID enode.ID, fromAddr string, head *authHeader) ([]byte, *enode.Node, error) { - if !head.isHandshake { - return c.sc.readKey(fromID, fromAddr), nil, nil - } - - // Remote is attempting handshake. Verify against our last WHOAREYOU. - challenge := c.sc.getHandshake(fromID, fromAddr) - if challenge == nil { - return nil, nil, errUnexpectedHandshake - } - if head.IDNonce != challenge.IDNonce { - return nil, nil, errHandshakeNonceMismatch - } - sec, n, err := c.decodeAuthResp(fromID, fromAddr, &head.authHeaderList, challenge) - if err != nil { - return nil, n, err - } - // Swap keys to match remote. - sec.readKey, sec.writeKey = sec.writeKey, sec.readKey - c.sc.storeNewSession(fromID, fromAddr, sec.readKey, sec.writeKey) - c.sc.deleteHandshake(fromID, fromAddr) - return sec.readKey, n, err -} - -// decodeAuthResp decodes and verifies an authentication response. -func (c *wireCodec) decodeAuthResp(fromID enode.ID, fromAddr string, head *authHeaderList, challenge *whoareyouV5) (*handshakeSecrets, *enode.Node, error) { - // Decrypt / decode the response. - if head.Scheme != authSchemeName { - return nil, nil, errUnknownAuthScheme - } - ephkey := head.ephemeralKey(c.privkey.Curve) - if ephkey == nil { - return nil, nil, errInvalidAuthKey - } - sec := c.deriveKeys(fromID, c.localnode.ID(), c.privkey, ephkey, challenge) - respPT, err := decryptGCM(sec.authRespKey, zeroNonce, head.Response, nil) - if err != nil { - return nil, nil, fmt.Errorf("can't decrypt auth response header: %v", err) - } - var resp authResponse - if err := rlp.DecodeBytes(respPT, &resp); err != nil { - return nil, nil, fmt.Errorf("invalid auth response: %v", err) - } - - // Verify response node record. The remote node should include the record - // if we don't have one or if ours is older than the latest version. - node := challenge.node - if resp.Record != nil { - if node == nil || node.Seq() < resp.Record.Seq() { - n, err := enode.New(enode.ValidSchemes, resp.Record) - if err != nil { - return nil, nil, fmt.Errorf("invalid node record: %v", err) - } - if n.ID() != fromID { - return nil, nil, fmt.Errorf("record in auth respose has wrong ID: %v", n.ID()) - } - node = n - } - } - if node == nil { - return nil, nil, errNoRecord - } - - // Verify ID nonce signature. - err = c.verifyIDSignature(challenge.IDNonce[:], head.EphemeralKey, resp.Signature, node) - if err != nil { - return nil, nil, err - } - return sec, node, nil -} - -// verifyIDSignature checks that signature over idnonce was made by the node with given record. -func (c *wireCodec) verifyIDSignature(nonce, ephkey, sig []byte, n *enode.Node) error { - switch idscheme := n.Record().IdentityScheme(); idscheme { - case "v4": - var pk ecdsa.PublicKey - n.Load((*enode.Secp256k1)(&pk)) // cannot fail because record is valid - if !crypto.VerifySignature(crypto.FromECDSAPub(&pk), c.idNonceHash(nonce, ephkey), sig) { - return errInvalidNonceSig - } - return nil - default: - return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme) - } -} - -// decodePacketBody decodes the body of an encrypted discovery packet. -func decodePacketBodyV5(ptype byte, body []byte) (packetV5, error) { - var dec packetV5 - switch ptype { - case p_pingV5: - dec = new(pingV5) - case p_pongV5: - dec = new(pongV5) - case p_findnodeV5: - dec = new(findnodeV5) - case p_nodesV5: - dec = new(nodesV5) - case p_requestTicketV5: - dec = new(requestTicketV5) - case p_ticketV5: - dec = new(ticketV5) - case p_regtopicV5: - dec = new(regtopicV5) - case p_regconfirmationV5: - dec = new(regconfirmationV5) - case p_topicqueryV5: - dec = new(topicqueryV5) - default: - return nil, fmt.Errorf("unknown packet type %d", ptype) - } - if err := rlp.DecodeBytes(body, dec); err != nil { - return nil, err - } - return dec, nil -} - -// sha256reset returns the shared hash instance. -func (c *wireCodec) sha256reset() hash.Hash { - c.sha256.Reset() - return c.sha256 -} - -// sha256sum computes sha256 on the concatenation of inputs. -func (c *wireCodec) sha256sum(inputs ...[]byte) []byte { - c.sha256.Reset() - for _, b := range inputs { - c.sha256.Write(b) - } - return c.sha256.Sum(nil) -} - -func xorTag(a []byte, b enode.ID) enode.ID { - var r enode.ID - for i := range r { - r[i] = a[i] ^ b[i] - } - return r -} - -// ecdh creates a shared secret. -func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte { - secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) - if secX == nil { - return nil - } - sec := make([]byte, 33) - sec[0] = 0x02 | byte(secY.Bit(0)) - math.ReadBits(secX, sec[1:]) - return sec -} - -// encryptGCM encrypts pt using AES-GCM with the given key and nonce. -func encryptGCM(dest, key, nonce, pt, authData []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - panic(fmt.Errorf("can't create block cipher: %v", err)) - } - aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) - if err != nil { - panic(fmt.Errorf("can't create GCM: %v", err)) - } - return aesgcm.Seal(dest, nonce, pt, authData), nil -} - -// decryptGCM decrypts ct using AES-GCM with the given key and nonce. -func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, fmt.Errorf("can't create block cipher: %v", err) - } - if len(nonce) != gcmNonceSize { - return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce)) - } - aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) - if err != nil { - return nil, fmt.Errorf("can't create GCM: %v", err) - } - pt := make([]byte, 0, len(ct)) - return aesgcm.Open(pt, nonce, ct, authData) -} diff --git a/p2p/discover/v5_encoding_test.go b/p2p/discover/v5_encoding_test.go deleted file mode 100644 index 77e6bae6ae..0000000000 --- a/p2p/discover/v5_encoding_test.go +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discover - -import ( - "bytes" - "crypto/ecdsa" - "encoding/hex" - "fmt" - "net" - "reflect" - "testing" - - "github.com/davecgh/go-spew/spew" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -var ( - testKeyA, _ = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f") - testKeyB, _ = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628") - testIDnonce = [32]byte{5, 6, 7, 8, 9, 10, 11, 12} -) - -func TestDeriveKeysV5(t *testing.T) { - t.Parallel() - - var ( - n1 = enode.ID{1} - n2 = enode.ID{2} - challenge = &whoareyouV5{} - db, _ = enode.OpenDB("") - ln = enode.NewLocalNode(db, testKeyA) - c = newWireCodec(ln, testKeyA, mclock.System{}) - ) - defer db.Close() - - sec1 := c.deriveKeys(n1, n2, testKeyA, &testKeyB.PublicKey, challenge) - sec2 := c.deriveKeys(n1, n2, testKeyB, &testKeyA.PublicKey, challenge) - if sec1 == nil || sec2 == nil { - t.Fatal("key agreement failed") - } - if !reflect.DeepEqual(sec1, sec2) { - t.Fatalf("keys not equal:\n %+v\n %+v", sec1, sec2) - } -} - -// This test checks the basic handshake flow where A talks to B and A has no secrets. -func TestHandshakeV5(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - // A -> B RANDOM PACKET - packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) - resp := net.nodeB.expectDecode(t, p_unknownV5, packet) - - // A <- B WHOAREYOU - challenge := &whoareyouV5{ - AuthTag: resp.(*unknownV5).AuthTag, - IDNonce: testIDnonce, - RecordSeq: 0, - } - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // A -> B FINDNODE - findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecode(t, p_findnodeV5, findnode) - if len(net.nodeB.c.sc.handshakes) > 0 { - t.Fatalf("node B didn't remove handshake from challenge map") - } - - // A <- B NODES - nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) - net.nodeA.expectDecode(t, p_nodesV5, nodes) -} - -// This test checks that handshake attempts are removed within the timeout. -func TestHandshakeV5_timeout(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - // A -> B RANDOM PACKET - packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) - resp := net.nodeB.expectDecode(t, p_unknownV5, packet) - - // A <- B WHOAREYOU - challenge := &whoareyouV5{ - AuthTag: resp.(*unknownV5).AuthTag, - IDNonce: testIDnonce, - RecordSeq: 0, - } - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // A -> B FINDNODE after timeout - net.clock.Run(handshakeTimeout + 1) - findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) -} - -// This test checks handshake behavior when no record is sent in the auth response. -func TestHandshakeV5_norecord(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - // A -> B RANDOM PACKET - packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) - resp := net.nodeB.expectDecode(t, p_unknownV5, packet) - - // A <- B WHOAREYOU - nodeA := net.nodeA.n() - if nodeA.Seq() == 0 { - t.Fatal("need non-zero sequence number") - } - challenge := &whoareyouV5{ - AuthTag: resp.(*unknownV5).AuthTag, - IDNonce: testIDnonce, - RecordSeq: nodeA.Seq(), - node: nodeA, - } - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // A -> B FINDNODE - findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecode(t, p_findnodeV5, findnode) - - // A <- B NODES - nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) - net.nodeA.expectDecode(t, p_nodesV5, nodes) -} - -// In this test, A tries to send FINDNODE with existing secrets but B doesn't know -// anything about A. -func TestHandshakeV5_rekey(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - initKeys := &handshakeSecrets{ - readKey: []byte("BBBBBBBBBBBBBBBB"), - writeKey: []byte("AAAAAAAAAAAAAAAA"), - } - net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeys.readKey, initKeys.writeKey) - - // A -> B FINDNODE (encrypted with zero keys) - findnode, authTag := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) - net.nodeB.expectDecode(t, p_unknownV5, findnode) - - // A <- B WHOAREYOU - challenge := &whoareyouV5{AuthTag: authTag, IDNonce: testIDnonce} - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // Check that new keys haven't been stored yet. - if s := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()); !bytes.Equal(s.writeKey, initKeys.writeKey) || !bytes.Equal(s.readKey, initKeys.readKey) { - t.Fatal("node A stored keys too early") - } - if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil { - t.Fatal("node B stored keys too early") - } - - // A -> B FINDNODE encrypted with new keys - findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecode(t, p_findnodeV5, findnode) - - // A <- B NODES - nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) - net.nodeA.expectDecode(t, p_nodesV5, nodes) -} - -// In this test A and B have different keys before the handshake. -func TestHandshakeV5_rekey2(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - initKeysA := &handshakeSecrets{ - readKey: []byte("BBBBBBBBBBBBBBBB"), - writeKey: []byte("AAAAAAAAAAAAAAAA"), - } - initKeysB := &handshakeSecrets{ - readKey: []byte("CCCCCCCCCCCCCCCC"), - writeKey: []byte("DDDDDDDDDDDDDDDD"), - } - net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA.readKey, initKeysA.writeKey) - net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB.readKey, initKeysA.writeKey) - - // A -> B FINDNODE encrypted with initKeysA - findnode, authTag := net.nodeA.encode(t, net.nodeB, &findnodeV5{Distance: 3}) - net.nodeB.expectDecode(t, p_unknownV5, findnode) - - // A <- B WHOAREYOU - challenge := &whoareyouV5{AuthTag: authTag, IDNonce: testIDnonce} - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // A -> B FINDNODE encrypted with new keys - findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecode(t, p_findnodeV5, findnode) - - // A <- B NODES - nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) - net.nodeA.expectDecode(t, p_nodesV5, nodes) -} - -// This test checks some malformed packets. -func TestDecodeErrorsV5(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - net.nodeA.expectDecodeErr(t, errTooShort, []byte{}) - // TODO some more tests would be nice :) -} - -// This benchmark checks performance of authHeader decoding, verification and key derivation. -func BenchmarkV5_DecodeAuthSecp256k1(b *testing.B) { - net := newHandshakeTest() - defer net.close() - - var ( - idA = net.nodeA.id() - addrA = net.nodeA.addr() - challenge = &whoareyouV5{AuthTag: []byte("authresp"), RecordSeq: 0, node: net.nodeB.n()} - nonce = make([]byte, gcmNonceSize) - ) - header, _, _ := net.nodeA.c.makeAuthHeader(nonce, challenge) - challenge.node = nil // force ENR signature verification in decoder - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _, _, err := net.nodeB.c.decodeAuthResp(idA, addrA, header, challenge) - if err != nil { - b.Fatal(err) - } - } -} - -// This benchmark checks how long it takes to decode an encrypted ping packet. -func BenchmarkV5_DecodePing(b *testing.B) { - net := newHandshakeTest() - defer net.close() - - r := []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17} - w := []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134} - net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), r, w) - net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), w, r) - addrB := net.nodeA.addr() - ping := &pingV5{ReqID: []byte("reqid"), ENRSeq: 5} - enc, _, err := net.nodeA.c.encode(net.nodeB.id(), addrB, ping, nil) - if err != nil { - b.Fatalf("can't encode: %v", err) - } - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _, _, p, _ := net.nodeB.c.decode(enc, addrB) - if _, ok := p.(*pingV5); !ok { - b.Fatalf("wrong packet type %T", p) - } - } -} - -var pp = spew.NewDefaultConfig() - -type handshakeTest struct { - nodeA, nodeB handshakeTestNode - clock mclock.Simulated -} - -type handshakeTestNode struct { - ln *enode.LocalNode - c *wireCodec -} - -func newHandshakeTest() *handshakeTest { - t := new(handshakeTest) - t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock) - t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock) - return t -} - -func (t *handshakeTest) close() { - t.nodeA.ln.Database().Close() - t.nodeB.ln.Database().Close() -} - -func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock) { - db, _ := enode.OpenDB("") - n.ln = enode.NewLocalNode(db, key) - n.ln.SetStaticIP(ip) - n.c = newWireCodec(n.ln, key, clock) -} - -func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p packetV5) ([]byte, []byte) { - t.Helper() - return n.encodeWithChallenge(t, to, nil, p) -} - -func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *whoareyouV5, p packetV5) ([]byte, []byte) { - t.Helper() - // Copy challenge and add destination node. This avoids sharing 'c' among the two codecs. - var challenge *whoareyouV5 - if c != nil { - challengeCopy := *c - challenge = &challengeCopy - challenge.node = to.n() - } - // Encode to destination. - enc, authTag, err := n.c.encode(to.id(), to.addr(), p, challenge) - if err != nil { - t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) - } - t.Logf("(%s) -> (%s) %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.name(), hex.Dump(enc)) - return enc, authTag -} - -func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) packetV5 { - t.Helper() - dec, err := n.decode(p) - if err != nil { - t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) - } - t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec)) - if dec.kind() != ptype { - t.Fatalf("expected packet type %d, got %d", ptype, dec.kind()) - } - return dec -} - -func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) { - t.Helper() - if _, err := n.decode(p); !reflect.DeepEqual(err, wantErr) { - t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr)) - } -} - -func (n *handshakeTestNode) decode(input []byte) (packetV5, error) { - _, _, p, err := n.c.decode(input, "127.0.0.1") - return p, err -} - -func (n *handshakeTestNode) n() *enode.Node { - return n.ln.Node() -} - -func (n *handshakeTestNode) addr() string { - return n.ln.Node().IP().String() -} - -func (n *handshakeTestNode) id() enode.ID { - return n.ln.ID() -} diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index d53375b48b..c95317a005 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/netutil" @@ -38,36 +39,24 @@ import ( const ( lookupRequestLimit = 3 // max requests against a single node during lookup - findnodeResultLimit = 15 // applies in FINDNODE handler + findnodeResultLimit = 16 // applies in FINDNODE handler totalNodesResponseLimit = 5 // applies in waitForNodes nodesResponseItemLimit = 3 // applies in sendNodes respTimeoutV5 = 700 * time.Millisecond ) -// codecV5 is implemented by wireCodec (and testCodec). +// codecV5 is implemented by v5wire.Codec (and testCodec). // // The UDPv5 transport is split into two objects: the codec object deals with // encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns. type codecV5 interface { - // encode encodes a packet. The 'challenge' parameter is non-nil for calls which got a - // WHOAREYOU response. - encode(fromID enode.ID, fromAddr string, p packetV5, challenge *whoareyouV5) (enc []byte, authTag []byte, err error) + // Encode encodes a packet. + Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) - // decode decodes a packet. It returns an *unknownV5 packet if decryption fails. - // The fromNode return value is non-nil when the input contains a handshake response. - decode(input []byte, fromAddr string) (fromID enode.ID, fromNode *enode.Node, p packetV5, err error) -} - -// packetV5 is implemented by all discv5 packet type structs. -type packetV5 interface { - // These methods provide information and set the request ID. - name() string - kind() byte - setreqid([]byte) - // handle should perform the appropriate action to handle the packet, i.e. this is the - // place to send the response. - handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) + // decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails. + // The *enode.Node return value is non-nil when the input contains a handshake response. + Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error) } // UDPv5 is the implementation of protocol version 5. @@ -83,6 +72,10 @@ type UDPv5 struct { clock mclock.Clock validSchemes enr.IdentityScheme + // talkreq handler registry + trlock sync.Mutex + trhandlers map[string]func([]byte) []byte + // channels into dispatch packetInCh chan ReadPacket readNextCh chan struct{} @@ -93,7 +86,7 @@ type UDPv5 struct { // state of dispatch codec codecV5 activeCallByNode map[enode.ID]*callV5 - activeCallByAuth map[string]*callV5 + activeCallByAuth map[v5wire.Nonce]*callV5 callQueue map[enode.ID][]*callV5 // shutdown stuff @@ -106,16 +99,16 @@ type UDPv5 struct { // callV5 represents a remote procedure call against another node. type callV5 struct { node *enode.Node - packet packetV5 + packet v5wire.Packet responseType byte // expected packet type of response reqid []byte - ch chan packetV5 // responses sent here - err chan error // errors sent here + ch chan v5wire.Packet // responses sent here + err chan error // errors sent here // Valid for active calls only: - authTag []byte // authTag of request packet - handshakeCount int // # times we attempted handshake for this call - challenge *whoareyouV5 // last sent handshake challenge + nonce v5wire.Nonce // nonce of request packet + handshakeCount int // # times we attempted handshake for this call + challenge *v5wire.Whoareyou // last sent handshake challenge timeout mclock.Timer } @@ -152,6 +145,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { log: cfg.Log, validSchemes: cfg.ValidSchemes, clock: cfg.Clock, + trhandlers: make(map[string]func([]byte) []byte), // channels into dispatch packetInCh: make(chan ReadPacket, 1), readNextCh: make(chan struct{}, 1), @@ -159,9 +153,9 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { callDoneCh: make(chan *callV5), respTimeoutCh: make(chan *callTimeout), // state of dispatch - codec: newWireCodec(ln, cfg.PrivateKey, cfg.Clock), + codec: v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock), activeCallByNode: make(map[enode.ID]*callV5), - activeCallByAuth: make(map[string]*callV5), + activeCallByAuth: make(map[v5wire.Nonce]*callV5), callQueue: make(map[enode.ID][]*callV5), // shutdown closeCtx: closeCtx, @@ -236,6 +230,29 @@ func (t *UDPv5) LocalNode() *enode.LocalNode { return t.localNode } +// RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called +// whenever a request for the given protocol is received and should return the response +// data or nil. +func (t *UDPv5) RegisterTalkHandler(protocol string, handler func([]byte) []byte) { + t.trlock.Lock() + defer t.trlock.Unlock() + t.trhandlers[protocol] = handler +} + +// TalkRequest sends a talk request to n and waits for a response. +func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]byte, error) { + req := &v5wire.TalkRequest{Protocol: protocol, Message: request} + resp := t.call(n, v5wire.TalkResponseMsg, req) + defer t.callDone(resp) + select { + case respMsg := <-resp.ch: + return respMsg.(*v5wire.TalkResponse).Message, nil + case err := <-resp.err: + return nil, err + } +} + +// RandomNodes returns an iterator that finds random nodes in the DHT. func (t *UDPv5) RandomNodes() enode.Iterator { if t.tab.len() == 0 { // All nodes were dropped, refresh. The very first query will hit this @@ -283,16 +300,14 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { nodes = nodesByDistance{target: target} err error ) - for i := 0; i < lookupRequestLimit && len(nodes.entries) < findnodeResultLimit; i++ { - var r []*enode.Node - r, err = t.findnode(unwrapNode(destNode), dists[i]) - if err == errClosed { - return nil, err - } - for _, n := range r { - if n.ID() != t.Self().ID() { - nodes.push(wrapNode(n), findnodeResultLimit) - } + var r []*enode.Node + r, err = t.findnode(unwrapNode(destNode), dists) + if err == errClosed { + return nil, err + } + for _, n := range r { + if n.ID() != t.Self().ID() { + nodes.push(wrapNode(n), findnodeResultLimit) } } return nodes.entries, err @@ -301,15 +316,15 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { // lookupDistances computes the distance parameter for FINDNODE calls to dest. // It chooses distances adjacent to logdist(target, dest), e.g. for a target // with logdist(target, dest) = 255 the result is [255, 256, 254]. -func lookupDistances(target, dest enode.ID) (dists []int) { +func lookupDistances(target, dest enode.ID) (dists []uint) { td := enode.LogDist(target, dest) - dists = append(dists, td) + dists = append(dists, uint(td)) for i := 1; len(dists) < lookupRequestLimit; i++ { if td+i < 256 { - dists = append(dists, td+i) + dists = append(dists, uint(td+i)) } if td-i > 0 { - dists = append(dists, td-i) + dists = append(dists, uint(td-i)) } } return dists @@ -317,11 +332,13 @@ func lookupDistances(target, dest enode.ID) (dists []int) { // ping calls PING on a node and waits for a PONG response. func (t *UDPv5) ping(n *enode.Node) (uint64, error) { - resp := t.call(n, p_pongV5, &pingV5{ENRSeq: t.localNode.Node().Seq()}) + req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()} + resp := t.call(n, v5wire.PongMsg, req) defer t.callDone(resp) + select { case pong := <-resp.ch: - return pong.(*pongV5).ENRSeq, nil + return pong.(*v5wire.Pong).ENRSeq, nil case err := <-resp.err: return 0, err } @@ -329,7 +346,7 @@ func (t *UDPv5) ping(n *enode.Node) (uint64, error) { // requestENR requests n's record. func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) { - nodes, err := t.findnode(n, 0) + nodes, err := t.findnode(n, []uint{0}) if err != nil { return nil, err } @@ -339,26 +356,14 @@ func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) { return nodes[0], nil } -// requestTicket calls REQUESTTICKET on a node and waits for a TICKET response. -func (t *UDPv5) requestTicket(n *enode.Node) ([]byte, error) { - resp := t.call(n, p_ticketV5, &pingV5{}) - defer t.callDone(resp) - select { - case response := <-resp.ch: - return response.(*ticketV5).Ticket, nil - case err := <-resp.err: - return nil, err - } -} - // findnode calls FINDNODE on a node and waits for responses. -func (t *UDPv5) findnode(n *enode.Node, distance int) ([]*enode.Node, error) { - resp := t.call(n, p_nodesV5, &findnodeV5{Distance: uint(distance)}) - return t.waitForNodes(resp, distance) +func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) { + resp := t.call(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances}) + return t.waitForNodes(resp, distances) } // waitForNodes waits for NODES responses to the given call. -func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) { +func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) { defer t.callDone(c) var ( @@ -369,11 +374,11 @@ func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) { for { select { case responseP := <-c.ch: - response := responseP.(*nodesV5) + response := responseP.(*v5wire.Nodes) for _, record := range response.Nodes { - node, err := t.verifyResponseNode(c, record, distance, seen) + node, err := t.verifyResponseNode(c, record, distances, seen) if err != nil { - t.log.Debug("Invalid record in "+response.name(), "id", c.node.ID(), "err", err) + t.log.Debug("Invalid record in "+response.Name(), "id", c.node.ID(), "err", err) continue } nodes = append(nodes, node) @@ -391,7 +396,7 @@ func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) { } // verifyResponseNode checks validity of a record in a NODES response. -func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen map[enode.ID]struct{}) (*enode.Node, error) { +func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) { node, err := enode.New(t.validSchemes, r) if err != nil { return nil, err @@ -402,9 +407,10 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen if c.node.UDP() <= 1024 { return nil, errLowPort } - if distance != -1 { - if d := enode.LogDist(c.node.ID(), node.ID()); d != distance { - return nil, fmt.Errorf("wrong distance %d", d) + if distances != nil { + nd := enode.LogDist(c.node.ID(), node.ID()) + if !containsUint(uint(nd), distances) { + return nil, errors.New("does not match any requested distance") } } if _, ok := seen[node.ID()]; ok { @@ -414,20 +420,29 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen return node, nil } -// call sends the given call and sets up a handler for response packets (of type c.responseType). -// Responses are dispatched to the call's response channel. -func (t *UDPv5) call(node *enode.Node, responseType byte, packet packetV5) *callV5 { +func containsUint(x uint, xs []uint) bool { + for _, v := range xs { + if x == v { + return true + } + } + return false +} + +// call sends the given call and sets up a handler for response packets (of message type +// responseType). Responses are dispatched to the call's response channel. +func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) *callV5 { c := &callV5{ node: node, packet: packet, responseType: responseType, reqid: make([]byte, 8), - ch: make(chan packetV5, 1), + ch: make(chan v5wire.Packet, 1), err: make(chan error, 1), } // Assign request ID. crand.Read(c.reqid) - packet.setreqid(c.reqid) + packet.SetRequestID(c.reqid) // Send call to dispatch. select { case t.callCh <- c: @@ -482,7 +497,7 @@ func (t *UDPv5) dispatch() { panic("BUG: callDone for inactive call") } c.timeout.Stop() - delete(t.activeCallByAuth, string(c.authTag)) + delete(t.activeCallByAuth, c.nonce) delete(t.activeCallByNode, id) t.sendNextCall(id) @@ -502,7 +517,7 @@ func (t *UDPv5) dispatch() { for id, c := range t.activeCallByNode { c.err <- errClosed delete(t.activeCallByNode, id) - delete(t.activeCallByAuth, string(c.authTag)) + delete(t.activeCallByAuth, c.nonce) } return } @@ -548,38 +563,37 @@ func (t *UDPv5) sendNextCall(id enode.ID) { // sendCall encodes and sends a request packet to the call's recipient node. // This performs a handshake if needed. func (t *UDPv5) sendCall(c *callV5) { - if len(c.authTag) > 0 { - // The call already has an authTag from a previous handshake attempt. Remove the - // entry for the authTag because we're about to generate a new authTag for this - // call. - delete(t.activeCallByAuth, string(c.authTag)) + // The call might have a nonce from a previous handshake attempt. Remove the entry for + // the old nonce because we're about to generate a new nonce for this call. + if c.nonce != (v5wire.Nonce{}) { + delete(t.activeCallByAuth, c.nonce) } addr := &net.UDPAddr{IP: c.node.IP(), Port: c.node.UDP()} - newTag, _ := t.send(c.node.ID(), addr, c.packet, c.challenge) - c.authTag = newTag - t.activeCallByAuth[string(c.authTag)] = c + newNonce, _ := t.send(c.node.ID(), addr, c.packet, c.challenge) + c.nonce = newNonce + t.activeCallByAuth[newNonce] = c t.startResponseTimeout(c) } // sendResponse sends a response packet to the given node. // This doesn't trigger a handshake even if no keys are available. -func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet packetV5) error { +func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error { _, err := t.send(toID, toAddr, packet, nil) return err } // send sends a packet to the given node. -func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet packetV5, c *whoareyouV5) ([]byte, error) { +func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) { addr := toAddr.String() - enc, authTag, err := t.codec.encode(toID, addr, packet, c) + enc, nonce, err := t.codec.Encode(toID, addr, packet, c) if err != nil { - t.log.Warn(">> "+packet.name(), "id", toID, "addr", addr, "err", err) - return authTag, err + t.log.Warn(">> "+packet.Name(), "id", toID, "addr", addr, "err", err) + return nonce, err } _, err = t.conn.WriteToUDP(enc, toAddr) - t.log.Trace(">> "+packet.name(), "id", toID, "addr", addr) - return authTag, err + t.log.Trace(">> "+packet.Name(), "id", toID, "addr", addr) + return nonce, err } // readLoop runs in its own goroutine and reads packets from the network. @@ -617,7 +631,7 @@ func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool { // handlePacket decodes and processes an incoming packet from the network. func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error { addr := fromAddr.String() - fromID, fromNode, packet, err := t.codec.decode(rawpacket, addr) + fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr) if err != nil { t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err) return err @@ -626,31 +640,32 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error { // Handshake succeeded, add to table. t.tab.addSeenNode(wrapNode(fromNode)) } - if packet.kind() != p_whoareyouV5 { - // WHOAREYOU logged separately to report the sender ID. - t.log.Trace("<< "+packet.name(), "id", fromID, "addr", addr) + if packet.Kind() != v5wire.WhoareyouPacket { + // WHOAREYOU logged separately to report errors. + t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", addr) } - packet.handle(t, fromID, fromAddr) + t.handle(packet, fromID, fromAddr) return nil } // handleCallResponse dispatches a response packet to the call waiting for it. -func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, reqid []byte, p packetV5) { +func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool { ac := t.activeCallByNode[fromID] - if ac == nil || !bytes.Equal(reqid, ac.reqid) { - t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.name()), "id", fromID, "addr", fromAddr) - return + if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) { + t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr) + return false } if !fromAddr.IP.Equal(ac.node.IP()) || fromAddr.Port != ac.node.UDP() { - t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.name()), "id", fromID, "addr", fromAddr) - return + t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr) + return false } - if p.kind() != ac.responseType { - t.log.Debug(fmt.Sprintf("Wrong disv5 response type %s", p.name()), "id", fromID, "addr", fromAddr) - return + if p.Kind() != ac.responseType { + t.log.Debug(fmt.Sprintf("Wrong discv5 response type %s", p.Name()), "id", fromID, "addr", fromAddr) + return false } t.startResponseTimeout(ac) ac.ch <- p + return true } // getNode looks for a node record in table and database. @@ -664,50 +679,65 @@ func (t *UDPv5) getNode(id enode.ID) *enode.Node { return nil } -// UNKNOWN - -func (p *unknownV5) name() string { return "UNKNOWN/v5" } -func (p *unknownV5) kind() byte { return p_unknownV5 } -func (p *unknownV5) setreqid(id []byte) {} +// handle processes incoming packets according to their message type. +func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) { + switch p := p.(type) { + case *v5wire.Unknown: + t.handleUnknown(p, fromID, fromAddr) + case *v5wire.Whoareyou: + t.handleWhoareyou(p, fromID, fromAddr) + case *v5wire.Ping: + t.handlePing(p, fromID, fromAddr) + case *v5wire.Pong: + if t.handleCallResponse(fromID, fromAddr, p) { + t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}) + } + case *v5wire.Findnode: + t.handleFindnode(p, fromID, fromAddr) + case *v5wire.Nodes: + t.handleCallResponse(fromID, fromAddr, p) + case *v5wire.TalkRequest: + t.handleTalkRequest(p, fromID, fromAddr) + case *v5wire.TalkResponse: + t.handleCallResponse(fromID, fromAddr, p) + } +} -func (p *unknownV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - challenge := &whoareyouV5{AuthTag: p.AuthTag} +// handleUnknown initiates a handshake by responding with WHOAREYOU. +func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) { + challenge := &v5wire.Whoareyou{Nonce: p.Nonce} crand.Read(challenge.IDNonce[:]) if n := t.getNode(fromID); n != nil { - challenge.node = n + challenge.Node = n challenge.RecordSeq = n.Seq() } t.sendResponse(fromID, fromAddr, challenge) } -// WHOAREYOU - -func (p *whoareyouV5) name() string { return "WHOAREYOU/v5" } -func (p *whoareyouV5) kind() byte { return p_whoareyouV5 } -func (p *whoareyouV5) setreqid(id []byte) {} +var ( + errChallengeNoCall = errors.New("no matching call") + errChallengeTwice = errors.New("second handshake") +) -func (p *whoareyouV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - c, err := p.matchWithCall(t, p.AuthTag) +// handleWhoareyou resends the active call as a handshake packet. +func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) { + c, err := t.matchWithCall(fromID, p.Nonce) if err != nil { - t.log.Debug("Invalid WHOAREYOU/v5", "addr", fromAddr, "err", err) + t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err) return } + // Resend the call that was answered by WHOAREYOU. - t.log.Trace("<< "+p.name(), "id", c.node.ID(), "addr", fromAddr) + t.log.Trace("<< "+p.Name(), "id", c.node.ID(), "addr", fromAddr) c.handshakeCount++ c.challenge = p - p.node = c.node + p.Node = c.node t.sendCall(c) } -var ( - errChallengeNoCall = errors.New("no matching call") - errChallengeTwice = errors.New("second handshake") -) - -// matchWithCall checks whether the handshake attempt matches the active call. -func (p *whoareyouV5) matchWithCall(t *UDPv5, authTag []byte) (*callV5, error) { - c := t.activeCallByAuth[string(authTag)] +// matchWithCall checks whether a handshake attempt matches the active call. +func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, error) { + c := t.activeCallByAuth[nonce] if c == nil { return nil, errChallengeNoCall } @@ -717,14 +747,9 @@ func (p *whoareyouV5) matchWithCall(t *UDPv5, authTag []byte) (*callV5, error) { return c, nil } -// PING - -func (p *pingV5) name() string { return "PING/v5" } -func (p *pingV5) kind() byte { return p_pingV5 } -func (p *pingV5) setreqid(id []byte) { p.ReqID = id } - -func (p *pingV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.sendResponse(fromID, fromAddr, &pongV5{ +// handlePing sends a PONG response. +func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) { + t.sendResponse(fromID, fromAddr, &v5wire.Pong{ ReqID: p.ReqID, ToIP: fromAddr.IP, ToPort: uint16(fromAddr.Port), @@ -732,121 +757,81 @@ func (p *pingV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { }) } -// PONG - -func (p *pongV5) name() string { return "PONG/v5" } -func (p *pongV5) kind() byte { return p_pongV5 } -func (p *pongV5) setreqid(id []byte) { p.ReqID = id } - -func (p *pongV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}) - t.handleCallResponse(fromID, fromAddr, p.ReqID, p) +// handleFindnode returns nodes to the requester. +func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) { + nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit) + for _, resp := range packNodes(p.ReqID, nodes) { + t.sendResponse(fromID, fromAddr, resp) + } } -// FINDNODE +// collectTableNodes creates a FINDNODE result set for the given distances. +func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node { + var nodes []*enode.Node + var processed = make(map[uint]struct{}) + for _, dist := range distances { + // Reject duplicate / invalid distances. + _, seen := processed[dist] + if seen || dist > 256 { + continue + } -func (p *findnodeV5) name() string { return "FINDNODE/v5" } -func (p *findnodeV5) kind() byte { return p_findnodeV5 } -func (p *findnodeV5) setreqid(id []byte) { p.ReqID = id } + // Get the nodes. + var bn []*enode.Node + if dist == 0 { + bn = []*enode.Node{t.Self()} + } else if dist <= 256 { + t.tab.mutex.Lock() + bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries) + t.tab.mutex.Unlock() + } + processed[dist] = struct{}{} -func (p *findnodeV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - if p.Distance == 0 { - t.sendNodes(fromID, fromAddr, p.ReqID, []*enode.Node{t.Self()}) - return - } - if p.Distance > 256 { - p.Distance = 256 - } - // Get bucket entries. - t.tab.mutex.Lock() - nodes := unwrapNodes(t.tab.bucketAtDistance(int(p.Distance)).entries) - t.tab.mutex.Unlock() - if len(nodes) > findnodeResultLimit { - nodes = nodes[:findnodeResultLimit] + // Apply some pre-checks to avoid sending invalid nodes. + for _, n := range bn { + // TODO livenessChecks > 1 + if netutil.CheckRelayIP(rip, n.IP()) != nil { + continue + } + nodes = append(nodes, n) + if len(nodes) >= limit { + return nodes + } + } } - t.sendNodes(fromID, fromAddr, p.ReqID, nodes) + return nodes } -// sendNodes sends the given records in one or more NODES packets. -func (t *UDPv5) sendNodes(toID enode.ID, toAddr *net.UDPAddr, reqid []byte, nodes []*enode.Node) { - // TODO livenessChecks > 1 - // TODO CheckRelayIP +// packNodes creates NODES response packets for the given node list. +func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes { + if len(nodes) == 0 { + return []*v5wire.Nodes{{ReqID: reqid, Total: 1}} + } + total := uint8(math.Ceil(float64(len(nodes)) / 3)) - resp := &nodesV5{ReqID: reqid, Total: total, Nodes: make([]*enr.Record, 3)} - sent := false + var resp []*v5wire.Nodes for len(nodes) > 0 { + p := &v5wire.Nodes{ReqID: reqid, Total: total} items := min(nodesResponseItemLimit, len(nodes)) - resp.Nodes = resp.Nodes[:items] for i := 0; i < items; i++ { - resp.Nodes[i] = nodes[i].Record() + p.Nodes = append(p.Nodes, nodes[i].Record()) } - t.sendResponse(toID, toAddr, resp) nodes = nodes[items:] - sent = true - } - // Ensure at least one response is sent. - if !sent { - resp.Total = 1 - resp.Nodes = nil - t.sendResponse(toID, toAddr, resp) + resp = append(resp, p) } + return resp } -// NODES +// handleTalkRequest runs the talk request handler of the requested protocol. +func (t *UDPv5) handleTalkRequest(p *v5wire.TalkRequest, fromID enode.ID, fromAddr *net.UDPAddr) { + t.trlock.Lock() + handler := t.trhandlers[p.Protocol] + t.trlock.Unlock() -func (p *nodesV5) name() string { return "NODES/v5" } -func (p *nodesV5) kind() byte { return p_nodesV5 } -func (p *nodesV5) setreqid(id []byte) { p.ReqID = id } - -func (p *nodesV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.handleCallResponse(fromID, fromAddr, p.ReqID, p) -} - -// REQUESTTICKET - -func (p *requestTicketV5) name() string { return "REQUESTTICKET/v5" } -func (p *requestTicketV5) kind() byte { return p_requestTicketV5 } -func (p *requestTicketV5) setreqid(id []byte) { p.ReqID = id } - -func (p *requestTicketV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.sendResponse(fromID, fromAddr, &ticketV5{ReqID: p.ReqID}) -} - -// TICKET - -func (p *ticketV5) name() string { return "TICKET/v5" } -func (p *ticketV5) kind() byte { return p_ticketV5 } -func (p *ticketV5) setreqid(id []byte) { p.ReqID = id } - -func (p *ticketV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.handleCallResponse(fromID, fromAddr, p.ReqID, p) -} - -// REGTOPIC - -func (p *regtopicV5) name() string { return "REGTOPIC/v5" } -func (p *regtopicV5) kind() byte { return p_regtopicV5 } -func (p *regtopicV5) setreqid(id []byte) { p.ReqID = id } - -func (p *regtopicV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.sendResponse(fromID, fromAddr, ®confirmationV5{ReqID: p.ReqID, Registered: false}) -} - -// REGCONFIRMATION - -func (p *regconfirmationV5) name() string { return "REGCONFIRMATION/v5" } -func (p *regconfirmationV5) kind() byte { return p_regconfirmationV5 } -func (p *regconfirmationV5) setreqid(id []byte) { p.ReqID = id } - -func (p *regconfirmationV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.handleCallResponse(fromID, fromAddr, p.ReqID, p) -} - -// TOPICQUERY - -func (p *topicqueryV5) name() string { return "TOPICQUERY/v5" } -func (p *topicqueryV5) kind() byte { return p_topicqueryV5 } -func (p *topicqueryV5) setreqid(id []byte) { p.ReqID = id } - -func (p *topicqueryV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + var response []byte + if handler != nil { + response = handler(p.Message) + } + resp := &v5wire.TalkResponse{ReqID: p.ReqID, Message: response} + t.sendResponse(fromID, fromAddr, resp) } diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go index 7d3915e2dc..d91a2097db 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -24,22 +24,25 @@ import ( "math/rand" "net" "reflect" + "sort" "testing" "time" "github.com/ethereum/go-ethereum/internal/testlog" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rlp" ) // Real sockets, real crypto: this test checks end-to-end connectivity for UDPv5. -func TestEndToEndV5(t *testing.T) { +func TestUDPv5_lookupE2E(t *testing.T) { t.Parallel() + const N = 5 var nodes []*UDPv5 - for i := 0; i < 5; i++ { + for i := 0; i < N; i++ { var cfg Config if len(nodes) > 0 { bn := nodes[0].Self() @@ -49,12 +52,22 @@ func TestEndToEndV5(t *testing.T) { nodes = append(nodes, node) defer node.Close() } + last := nodes[N-1] + target := nodes[rand.Intn(N-2)].Self() - last := nodes[len(nodes)-1] - target := nodes[rand.Intn(len(nodes)-2)].Self() + // It is expected that all nodes can be found. + expectedResult := make([]*enode.Node, len(nodes)) + for i := range nodes { + expectedResult[i] = nodes[i].Self() + } + sort.Slice(expectedResult, func(i, j int) bool { + return enode.DistCmp(target.ID(), expectedResult[i].ID(), expectedResult[j].ID()) < 0 + }) + + // Do the lookup. results := last.Lookup(target.ID()) - if len(results) == 0 || results[0].ID() != target.ID() { - t.Fatalf("lookup returned wrong results: %v", results) + if err := checkNodesEqual(results, expectedResult); err != nil { + t.Fatalf("lookup returned wrong results: %v", err) } } @@ -93,8 +106,8 @@ func TestUDPv5_pingHandling(t *testing.T) { test := newUDPV5Test(t) defer test.close() - test.packetIn(&pingV5{ReqID: []byte("foo")}) - test.waitPacketOut(func(p *pongV5, addr *net.UDPAddr, authTag []byte) { + test.packetIn(&v5wire.Ping{ReqID: []byte("foo")}) + test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) { if !bytes.Equal(p.ReqID, []byte("foo")) { t.Error("wrong request ID in response:", p.ReqID) } @@ -110,13 +123,13 @@ func TestUDPv5_unknownPacket(t *testing.T) { test := newUDPV5Test(t) defer test.close() - authTag := [12]byte{1, 2, 3} - check := func(p *whoareyouV5, wantSeq uint64) { + nonce := v5wire.Nonce{1, 2, 3} + check := func(p *v5wire.Whoareyou, wantSeq uint64) { t.Helper() - if !bytes.Equal(p.AuthTag, authTag[:]) { - t.Error("wrong token in WHOAREYOU:", p.AuthTag, authTag[:]) + if p.Nonce != nonce { + t.Error("wrong nonce in WHOAREYOU:", p.Nonce, nonce) } - if p.IDNonce == ([32]byte{}) { + if p.IDNonce == ([16]byte{}) { t.Error("all zero ID nonce") } if p.RecordSeq != wantSeq { @@ -125,8 +138,8 @@ func TestUDPv5_unknownPacket(t *testing.T) { } // Unknown packet from unknown node. - test.packetIn(&unknownV5{AuthTag: authTag[:]}) - test.waitPacketOut(func(p *whoareyouV5, addr *net.UDPAddr, _ []byte) { + test.packetIn(&v5wire.Unknown{Nonce: nonce}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) { check(p, 0) }) @@ -134,8 +147,8 @@ func TestUDPv5_unknownPacket(t *testing.T) { n := test.getNode(test.remotekey, test.remoteaddr).Node() test.table.addSeenNode(wrapNode(n)) - test.packetIn(&unknownV5{AuthTag: authTag[:]}) - test.waitPacketOut(func(p *whoareyouV5, addr *net.UDPAddr, _ []byte) { + test.packetIn(&v5wire.Unknown{Nonce: nonce}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) { check(p, n.Seq()) }) } @@ -147,24 +160,40 @@ func TestUDPv5_findnodeHandling(t *testing.T) { defer test.close() // Create test nodes and insert them into the table. - nodes := nodesAtDistance(test.table.self().ID(), 253, 10) - fillTable(test.table, wrapNodes(nodes)) + nodes253 := nodesAtDistance(test.table.self().ID(), 253, 10) + nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4) + nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10) + fillTable(test.table, wrapNodes(nodes253)) + fillTable(test.table, wrapNodes(nodes249)) + fillTable(test.table, wrapNodes(nodes248)) // Requesting with distance zero should return the node's own record. - test.packetIn(&findnodeV5{ReqID: []byte{0}, Distance: 0}) + test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}}) test.expectNodes([]byte{0}, 1, []*enode.Node{test.udp.Self()}) - // Requesting with distance > 256 caps it at 256. - test.packetIn(&findnodeV5{ReqID: []byte{1}, Distance: 4234098}) + // Requesting with distance > 256 shouldn't crash. + test.packetIn(&v5wire.Findnode{ReqID: []byte{1}, Distances: []uint{4234098}}) test.expectNodes([]byte{1}, 1, nil) - // This request gets no nodes because the corresponding bucket is empty. - test.packetIn(&findnodeV5{ReqID: []byte{2}, Distance: 254}) + // Requesting with empty distance list shouldn't crash either. + test.packetIn(&v5wire.Findnode{ReqID: []byte{2}, Distances: []uint{}}) test.expectNodes([]byte{2}, 1, nil) - // This request gets all test nodes. - test.packetIn(&findnodeV5{ReqID: []byte{3}, Distance: 253}) - test.expectNodes([]byte{3}, 4, nodes) + // This request gets no nodes because the corresponding bucket is empty. + test.packetIn(&v5wire.Findnode{ReqID: []byte{3}, Distances: []uint{254}}) + test.expectNodes([]byte{3}, 1, nil) + + // This request gets all the distance-253 nodes. + test.packetIn(&v5wire.Findnode{ReqID: []byte{4}, Distances: []uint{253}}) + test.expectNodes([]byte{4}, 4, nodes253) + + // This request gets all the distance-249 nodes and some more at 248 because + // the bucket at 249 is not full. + test.packetIn(&v5wire.Findnode{ReqID: []byte{5}, Distances: []uint{249, 248}}) + var nodes []*enode.Node + nodes = append(nodes, nodes249...) + nodes = append(nodes, nodes248[:10]...) + test.expectNodes([]byte{5}, 5, nodes) } func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes []*enode.Node) { @@ -172,16 +201,17 @@ func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes for _, n := range wantNodes { nodeSet[n.ID()] = n.Record() } + for { - test.waitPacketOut(func(p *nodesV5, addr *net.UDPAddr, authTag []byte) { + test.waitPacketOut(func(p *v5wire.Nodes, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, wantReqID) { + test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID) + } if len(p.Nodes) > 3 { test.t.Fatalf("too many nodes in response") } if p.Total != wantTotal { - test.t.Fatalf("wrong total response count %d", p.Total) - } - if !bytes.Equal(p.ReqID, wantReqID) { - test.t.Fatalf("wrong request ID in response: %v", p.ReqID) + test.t.Fatalf("wrong total response count %d, want %d", p.Total, wantTotal) } for _, record := range p.Nodes { n, _ := enode.New(enode.ValidSchemesForTesting, record) @@ -215,7 +245,7 @@ func TestUDPv5_pingCall(t *testing.T) { _, err := test.udp.ping(remote) done <- err }() - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {}) if err := <-done; err != errTimeout { t.Fatalf("want errTimeout, got %q", err) } @@ -225,8 +255,8 @@ func TestUDPv5_pingCall(t *testing.T) { _, err := test.udp.ping(remote) done <- err }() - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetInFrom(test.remotekey, test.remoteaddr, &pongV5{ReqID: p.ReqID}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID}) }) if err := <-done; err != nil { t.Fatal(err) @@ -237,9 +267,9 @@ func TestUDPv5_pingCall(t *testing.T) { _, err := test.udp.ping(remote) done <- err }() - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101} - test.packetInFrom(test.remotekey, wrongAddr, &pongV5{ReqID: p.ReqID}) + test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID}) }) if err := <-done; err != errTimeout { t.Fatalf("want errTimeout for reply from wrong IP, got %q", err) @@ -255,29 +285,29 @@ func TestUDPv5_findnodeCall(t *testing.T) { // Launch the request: var ( - distance = 230 - remote = test.getNode(test.remotekey, test.remoteaddr).Node() - nodes = nodesAtDistance(remote.ID(), distance, 8) - done = make(chan error, 1) - response []*enode.Node + distances = []uint{230} + remote = test.getNode(test.remotekey, test.remoteaddr).Node() + nodes = nodesAtDistance(remote.ID(), int(distances[0]), 8) + done = make(chan error, 1) + response []*enode.Node ) go func() { var err error - response, err = test.udp.findnode(remote, distance) + response, err = test.udp.findnode(remote, distances) done <- err }() // Serve the responses: - test.waitPacketOut(func(p *findnodeV5, addr *net.UDPAddr, authTag []byte) { - if p.Distance != uint(distance) { - t.Fatalf("wrong bucket: %d", p.Distance) + test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) { + if !reflect.DeepEqual(p.Distances, distances) { + t.Fatalf("wrong distances in request: %v", p.Distances) } - test.packetIn(&nodesV5{ + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, Total: 2, Nodes: nodesToRecords(nodes[:4]), }) - test.packetIn(&nodesV5{ + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, Total: 2, Nodes: nodesToRecords(nodes[4:]), @@ -314,16 +344,16 @@ func TestUDPv5_callResend(t *testing.T) { }() // Ping answered by WHOAREYOU. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&whoareyouV5{AuthTag: authTag}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) }) // Ping should be re-sent. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&pongV5{ReqID: p.ReqID}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetIn(&v5wire.Pong{ReqID: p.ReqID}) }) // Answer the other ping. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&pongV5{ReqID: p.ReqID}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetIn(&v5wire.Pong{ReqID: p.ReqID}) }) if err := <-done; err != nil { t.Fatalf("unexpected ping error: %v", err) @@ -347,12 +377,12 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) { }() // Ping answered by WHOAREYOU. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&whoareyouV5{AuthTag: authTag}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) }) // Ping answered by WHOAREYOU again. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&whoareyouV5{AuthTag: authTag}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) }) if err := <-done; err != errTimeout { t.Fatalf("unexpected ping error: %q", err) @@ -367,27 +397,27 @@ func TestUDPv5_callTimeoutReset(t *testing.T) { // Launch the request: var ( - distance = 230 + distance = uint(230) remote = test.getNode(test.remotekey, test.remoteaddr).Node() - nodes = nodesAtDistance(remote.ID(), distance, 8) + nodes = nodesAtDistance(remote.ID(), int(distance), 8) done = make(chan error, 1) ) go func() { - _, err := test.udp.findnode(remote, distance) + _, err := test.udp.findnode(remote, []uint{distance}) done <- err }() // Serve two responses, slowly. - test.waitPacketOut(func(p *findnodeV5, addr *net.UDPAddr, authTag []byte) { + test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) { time.Sleep(respTimeout - 50*time.Millisecond) - test.packetIn(&nodesV5{ + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, Total: 2, Nodes: nodesToRecords(nodes[:4]), }) time.Sleep(respTimeout - 50*time.Millisecond) - test.packetIn(&nodesV5{ + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, Total: 2, Nodes: nodesToRecords(nodes[4:]), @@ -398,6 +428,97 @@ func TestUDPv5_callTimeoutReset(t *testing.T) { } } +// This test checks that TALKREQ calls the registered handler function. +func TestUDPv5_talkHandling(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + var recvMessage []byte + test.udp.RegisterTalkHandler("test", func(message []byte) []byte { + recvMessage = message + return []byte("test response") + }) + + // Successful case: + test.packetIn(&v5wire.TalkRequest{ + ReqID: []byte("foo"), + Protocol: "test", + Message: []byte("test request"), + }) + test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, []byte("foo")) { + t.Error("wrong request ID in response:", p.ReqID) + } + if string(p.Message) != "test response" { + t.Errorf("wrong talk response message: %q", p.Message) + } + if string(recvMessage) != "test request" { + t.Errorf("wrong message received in handler: %q", recvMessage) + } + }) + + // Check that empty response is returned for unregistered protocols. + recvMessage = nil + test.packetIn(&v5wire.TalkRequest{ + ReqID: []byte("2"), + Protocol: "wrong", + Message: []byte("test request"), + }) + test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, []byte("2")) { + t.Error("wrong request ID in response:", p.ReqID) + } + if string(p.Message) != "" { + t.Errorf("wrong talk response message: %q", p.Message) + } + if recvMessage != nil { + t.Errorf("handler was called for wrong protocol: %q", recvMessage) + } + }) +} + +// This test checks that outgoing TALKREQ calls work. +func TestUDPv5_talkRequest(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 1) + + // This request times out. + go func() { + _, err := test.udp.TalkRequest(remote, "test", []byte("test request")) + done <- err + }() + test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {}) + if err := <-done; err != errTimeout { + t.Fatalf("want errTimeout, got %q", err) + } + + // This request works. + go func() { + _, err := test.udp.TalkRequest(remote, "test", []byte("test request")) + done <- err + }() + test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) { + if p.Protocol != "test" { + t.Errorf("wrong protocol ID in talk request: %q", p.Protocol) + } + if string(p.Message) != "test request" { + t.Errorf("wrong message talk request: %q", p.Message) + } + test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.TalkResponse{ + ReqID: p.ReqID, + Message: []byte("test response"), + }) + }) + if err := <-done; err != nil { + t.Fatal(err) + } +} + // This test checks that lookup works. func TestUDPv5_lookup(t *testing.T) { t.Parallel() @@ -417,7 +538,8 @@ func TestUDPv5_lookup(t *testing.T) { } // Seed table with initial node. - fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}) + initialNode := lookupTestnet.node(256, 0) + fillTable(test.table, []*node{wrapNode(initialNode)}) // Start the lookup. resultC := make(chan []*enode.Node, 1) @@ -427,22 +549,30 @@ func TestUDPv5_lookup(t *testing.T) { }() // Answer lookup packets. + asked := make(map[enode.ID]bool) for done := false; !done; { - done = test.waitPacketOut(func(p packetV5, to *net.UDPAddr, authTag []byte) { + done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) { recipient, key := lookupTestnet.nodeByAddr(to) switch p := p.(type) { - case *pingV5: - test.packetInFrom(key, to, &pongV5{ReqID: p.ReqID}) - case *findnodeV5: - nodes := lookupTestnet.neighborsAtDistance(recipient, p.Distance, 3) - response := &nodesV5{ReqID: p.ReqID, Total: 1, Nodes: nodesToRecords(nodes)} - test.packetInFrom(key, to, response) + case *v5wire.Ping: + test.packetInFrom(key, to, &v5wire.Pong{ReqID: p.ReqID}) + case *v5wire.Findnode: + if asked[recipient.ID()] { + t.Error("Asked node", recipient.ID(), "twice") + } + asked[recipient.ID()] = true + nodes := lookupTestnet.neighborsAtDistances(recipient, p.Distances, 16) + t.Logf("Got FINDNODE for %v, returning %d nodes", p.Distances, len(nodes)) + for _, resp := range packNodes(p.ReqID, nodes) { + test.packetInFrom(key, to, resp) + } } }) } // Verify result nodes. - checkLookupResults(t, lookupTestnet, <-resultC) + results := <-resultC + checkLookupResults(t, lookupTestnet, results) } // This test checks the local node can be utilised to set key-values. @@ -481,6 +611,7 @@ type udpV5Test struct { nodesByIP map[string]*enode.LocalNode } +// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption. type testCodec struct { test *udpV5Test id enode.ID @@ -489,46 +620,44 @@ type testCodec struct { type testCodecFrame struct { NodeID enode.ID - AuthTag []byte + AuthTag v5wire.Nonce Ptype byte Packet rlp.RawValue } -func (c *testCodec) encode(toID enode.ID, addr string, p packetV5, _ *whoareyouV5) ([]byte, []byte, error) { +func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) { c.ctr++ - authTag := make([]byte, 8) - binary.BigEndian.PutUint64(authTag, c.ctr) + var authTag v5wire.Nonce + binary.BigEndian.PutUint64(authTag[:], c.ctr) + penc, _ := rlp.EncodeToBytes(p) - frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.kind(), penc}) + frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc}) return frame, authTag, err } -func (c *testCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) { +func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) { frame, p, err := c.decodeFrame(input) if err != nil { return enode.ID{}, nil, nil, err } - if p.kind() == p_whoareyouV5 { - frame.NodeID = enode.ID{} // match wireCodec behavior - } return frame.NodeID, nil, p, nil } -func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p packetV5, err error) { +func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) { if err = rlp.DecodeBytes(input, &frame); err != nil { return frame, nil, fmt.Errorf("invalid frame: %v", err) } switch frame.Ptype { - case p_unknownV5: - dec := new(unknownV5) + case v5wire.UnknownPacket: + dec := new(v5wire.Unknown) err = rlp.DecodeBytes(frame.Packet, &dec) p = dec - case p_whoareyouV5: - dec := new(whoareyouV5) + case v5wire.WhoareyouPacket: + dec := new(v5wire.Whoareyou) err = rlp.DecodeBytes(frame.Packet, &dec) p = dec default: - p, err = decodePacketBodyV5(frame.Ptype, frame.Packet) + p, err = v5wire.DecodeMessage(frame.Ptype, frame.Packet) } return frame, p, err } @@ -561,20 +690,20 @@ func newUDPV5Test(t *testing.T) *udpV5Test { } // handles a packet as if it had been sent to the transport. -func (test *udpV5Test) packetIn(packet packetV5) { +func (test *udpV5Test) packetIn(packet v5wire.Packet) { test.t.Helper() test.packetInFrom(test.remotekey, test.remoteaddr, packet) } // handles a packet as if it had been sent to the transport by the key/endpoint. -func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet packetV5) { +func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet v5wire.Packet) { test.t.Helper() ln := test.getNode(key, addr) codec := &testCodec{test: test, id: ln.ID()} - enc, _, err := codec.encode(test.udp.Self().ID(), addr.String(), packet, nil) + enc, _, err := codec.Encode(test.udp.Self().ID(), addr.String(), packet, nil) if err != nil { - test.t.Errorf("%s encode error: %v", packet.name(), err) + test.t.Errorf("%s encode error: %v", packet.Name(), err) } if test.udp.dispatchReadPacket(addr, enc) { <-test.udp.readNextCh // unblock UDPv5.dispatch @@ -596,8 +725,12 @@ func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode. return ln } +// waitPacketOut waits for the next output packet and handles it using the given 'validate' +// function. The function must be of type func (X, *net.UDPAddr, v5wire.Nonce) where X is +// assignable to packetV5. func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) { test.t.Helper() + fn := reflect.ValueOf(validate) exptype := fn.Type().In(0) diff --git a/p2p/discover/v5wire/crypto.go b/p2p/discover/v5wire/crypto.go new file mode 100644 index 0000000000..fc0a0edef5 --- /dev/null +++ b/p2p/discover/v5wire/crypto.go @@ -0,0 +1,180 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + "errors" + "fmt" + "hash" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" + "golang.org/x/crypto/hkdf" +) + +const ( + // Encryption/authentication parameters. + aesKeySize = 16 + gcmNonceSize = 12 +) + +// Nonce represents a nonce used for AES/GCM. +type Nonce [gcmNonceSize]byte + +// EncodePubkey encodes a public key. +func EncodePubkey(key *ecdsa.PublicKey) []byte { + switch key.Curve { + case crypto.S256(): + return crypto.CompressPubkey(key) + default: + panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey") + } +} + +// DecodePubkey decodes a public key in compressed format. +func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { + switch curve { + case crypto.S256(): + if len(e) != 33 { + return nil, errors.New("wrong size public key data") + } + return crypto.DecompressPubkey(e) + default: + return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name) + } +} + +// idNonceHash computes the ID signature hash used in the handshake. +func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte { + h.Reset() + h.Write([]byte("discovery v5 identity proof")) + h.Write(challenge) + h.Write(ephkey) + h.Write(destID[:]) + return h.Sum(nil) +} + +// makeIDSignature creates the ID nonce signature. +func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) { + input := idNonceHash(hash, challenge, ephkey, destID) + switch key.Curve { + case crypto.S256(): + idsig, err := crypto.Sign(input, key) + if err != nil { + return nil, err + } + return idsig[:len(idsig)-1], nil // remove recovery ID + default: + return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name) + } +} + +// s256raw is an unparsed secp256k1 public key ENR entry. +type s256raw []byte + +func (s256raw) ENRKey() string { return "secp256k1" } + +// verifyIDSignature checks that signature over idnonce was made by the given node. +func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error { + switch idscheme := n.Record().IdentityScheme(); idscheme { + case "v4": + var pubkey s256raw + if n.Load(&pubkey) != nil { + return errors.New("no secp256k1 public key in record") + } + input := idNonceHash(hash, challenge, ephkey, destID) + if !crypto.VerifySignature(pubkey, input, sig) { + return errInvalidNonceSig + } + return nil + default: + return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme) + } +} + +type hashFn func() hash.Hash + +// deriveKeys creates the session keys. +func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session { + const text = "discovery v5 key agreement" + var info = make([]byte, 0, len(text)+len(n1)+len(n2)) + info = append(info, text...) + info = append(info, n1[:]...) + info = append(info, n2[:]...) + + eph := ecdh(priv, pub) + if eph == nil { + return nil + } + kdf := hkdf.New(hash, eph, challenge, info) + sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)} + kdf.Read(sec.writeKey) + kdf.Read(sec.readKey) + for i := range eph { + eph[i] = 0 + } + return &sec +} + +// ecdh creates a shared secret. +func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte { + secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) + if secX == nil { + return nil + } + sec := make([]byte, 33) + sec[0] = 0x02 | byte(secY.Bit(0)) + math.ReadBits(secX, sec[1:]) + return sec +} + +// encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is +// appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16 +// bytes longer than plaintext because it contains an authentication tag. +func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + panic(fmt.Errorf("can't create block cipher: %v", err)) + } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) + if err != nil { + panic(fmt.Errorf("can't create GCM: %v", err)) + } + return aesgcm.Seal(dest, nonce, plaintext, authData), nil +} + +// decryptGCM decrypts ct using AES-GCM with the given key and nonce. +func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("can't create block cipher: %v", err) + } + if len(nonce) != gcmNonceSize { + return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce)) + } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) + if err != nil { + return nil, fmt.Errorf("can't create GCM: %v", err) + } + pt := make([]byte, 0, len(ct)) + return aesgcm.Open(pt, nonce, ct, authData) +} diff --git a/p2p/discover/v5wire/crypto_test.go b/p2p/discover/v5wire/crypto_test.go new file mode 100644 index 0000000000..72169b4314 --- /dev/null +++ b/p2p/discover/v5wire/crypto_test.go @@ -0,0 +1,124 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +func TestVector_ECDH(t *testing.T) { + var ( + staticKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + publicKey = hexPubkey(crypto.S256(), "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231") + want = hexutil.MustDecode("0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e") + ) + result := ecdh(staticKey, publicKey) + check(t, "shared-secret", result, want) +} + +func TestVector_KDF(t *testing.T) { + var ( + ephKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000") + net = newHandshakeTest() + ) + defer net.close() + + destKey := &testKeyB.PublicKey + s := deriveKeys(sha256.New, ephKey, destKey, net.nodeA.id(), net.nodeB.id(), cdata) + t.Logf("ephemeral-key = %#x", ephKey.D) + t.Logf("dest-pubkey = %#x", EncodePubkey(destKey)) + t.Logf("node-id-a = %#x", net.nodeA.id().Bytes()) + t.Logf("node-id-b = %#x", net.nodeB.id().Bytes()) + t.Logf("challenge-data = %#x", cdata) + check(t, "initiator-key", s.writeKey, hexutil.MustDecode("0xdccc82d81bd610f4f76d3ebe97a40571")) + check(t, "recipient-key", s.readKey, hexutil.MustDecode("0xac74bb8773749920b0d3a8881c173ec5")) +} + +func TestVector_IDSignature(t *testing.T) { + var ( + key = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + destID = enode.HexID("0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9") + ephkey = hexutil.MustDecode("0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231") + cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000") + ) + + sig, err := makeIDSignature(sha256.New(), key, cdata, ephkey, destID) + if err != nil { + t.Fatal(err) + } + t.Logf("static-key = %#x", key.D) + t.Logf("challenge-data = %#x", cdata) + t.Logf("ephemeral-pubkey = %#x", ephkey) + t.Logf("node-id-B = %#x", destID.Bytes()) + expected := "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6" + check(t, "id-signature", sig, hexutil.MustDecode(expected)) +} + +func TestDeriveKeys(t *testing.T) { + t.Parallel() + + var ( + n1 = enode.ID{1} + n2 = enode.ID{2} + cdata = []byte{1, 2, 3, 4} + ) + sec1 := deriveKeys(sha256.New, testKeyA, &testKeyB.PublicKey, n1, n2, cdata) + sec2 := deriveKeys(sha256.New, testKeyB, &testKeyA.PublicKey, n1, n2, cdata) + if sec1 == nil || sec2 == nil { + t.Fatal("key agreement failed") + } + if !reflect.DeepEqual(sec1, sec2) { + t.Fatalf("keys not equal:\n %+v\n %+v", sec1, sec2) + } +} + +func check(t *testing.T, what string, x, y []byte) { + t.Helper() + + if !bytes.Equal(x, y) { + t.Errorf("wrong %s: %#x != %#x", what, x, y) + } else { + t.Logf("%s = %#x", what, x) + } +} + +func hexPrivkey(input string) *ecdsa.PrivateKey { + key, err := crypto.HexToECDSA(strings.TrimPrefix(input, "0x")) + if err != nil { + panic(err) + } + return key +} + +func hexPubkey(curve elliptic.Curve, input string) *ecdsa.PublicKey { + key, err := DecodePubkey(curve, hexutil.MustDecode(input)) + if err != nil { + panic(err) + } + return key +} diff --git a/p2p/discover/v5wire/encoding.go b/p2p/discover/v5wire/encoding.go new file mode 100644 index 0000000000..f502339e1e --- /dev/null +++ b/p2p/discover/v5wire/encoding.go @@ -0,0 +1,648 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + crand "crypto/rand" + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "hash" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +// TODO concurrent WHOAREYOU tie-breaker +// TODO rehandshake after X packets + +// Header represents a packet header. +type Header struct { + IV [sizeofMaskingIV]byte + StaticHeader + AuthData []byte + + src enode.ID // used by decoder +} + +// StaticHeader contains the static fields of a packet header. +type StaticHeader struct { + ProtocolID [6]byte + Version uint16 + Flag byte + Nonce Nonce + AuthSize uint16 +} + +// Authdata layouts. +type ( + whoareyouAuthData struct { + IDNonce [16]byte // ID proof data + RecordSeq uint64 // highest known ENR sequence of requester + } + + handshakeAuthData struct { + h struct { + SrcID enode.ID + SigSize byte // ignature data + PubkeySize byte // offset of + } + // Trailing variable-size data. + signature, pubkey, record []byte + } + + messageAuthData struct { + SrcID enode.ID + } +) + +// Packet header flag values. +const ( + flagMessage = iota + flagWhoareyou + flagHandshake +) + +// Protocol constants. +const ( + version = 1 + minVersion = 1 + sizeofMaskingIV = 16 + + minMessageSize = 48 // this refers to data after static headers + randomPacketMsgSize = 20 +) + +var protocolID = [6]byte{'d', 'i', 's', 'c', 'v', '5'} + +// Errors. +var ( + errTooShort = errors.New("packet too short") + errInvalidHeader = errors.New("invalid packet header") + errInvalidFlag = errors.New("invalid flag value in header") + errMinVersion = errors.New("version of packet header below minimum") + errMsgTooShort = errors.New("message/handshake packet below minimum size") + errAuthSize = errors.New("declared auth size is beyond packet length") + errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake") + errInvalidAuthKey = errors.New("invalid ephemeral pubkey") + errNoRecord = errors.New("expected ENR in handshake but none sent") + errInvalidNonceSig = errors.New("invalid ID nonce signature") + errMessageTooShort = errors.New("message contains no data") + errMessageDecrypt = errors.New("cannot decrypt message") +) + +// Public errors. +var ( + ErrInvalidReqID = errors.New("request ID larger than 8 bytes") +) + +// Packet sizes. +var ( + sizeofStaticHeader = binary.Size(StaticHeader{}) + sizeofWhoareyouAuthData = binary.Size(whoareyouAuthData{}) + sizeofHandshakeAuthData = binary.Size(handshakeAuthData{}.h) + sizeofMessageAuthData = binary.Size(messageAuthData{}) + sizeofStaticPacketData = sizeofMaskingIV + sizeofStaticHeader +) + +// Codec encodes and decodes Discovery v5 packets. +// This type is not safe for concurrent use. +type Codec struct { + sha256 hash.Hash + localnode *enode.LocalNode + privkey *ecdsa.PrivateKey + sc *SessionCache + + // encoder buffers + buf bytes.Buffer // whole packet + headbuf bytes.Buffer // packet header + msgbuf bytes.Buffer // message RLP plaintext + msgctbuf []byte // message data ciphertext + + // decoder buffer + reader bytes.Reader +} + +// NewCodec creates a wire codec. +func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *Codec { + c := &Codec{ + sha256: sha256.New(), + localnode: ln, + privkey: key, + sc: NewSessionCache(1024, clock), + } + return c +} + +// Encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The +// 'challenge' parameter should be the most recently received WHOAREYOU packet from that +// node. +func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoareyou) ([]byte, Nonce, error) { + // Create the packet header. + var ( + head Header + session *session + msgData []byte + err error + ) + switch { + case packet.Kind() == WhoareyouPacket: + head, err = c.encodeWhoareyou(id, packet.(*Whoareyou)) + case challenge != nil: + // We have an unanswered challenge, send handshake. + head, session, err = c.encodeHandshakeHeader(id, addr, challenge) + default: + session = c.sc.session(id, addr) + if session != nil { + // There is a session, use it. + head, err = c.encodeMessageHeader(id, session) + } else { + // No keys, send random data to kick off the handshake. + head, msgData, err = c.encodeRandom(id) + } + } + if err != nil { + return nil, Nonce{}, err + } + + // Generate masking IV. + if err := c.sc.maskingIVGen(head.IV[:]); err != nil { + return nil, Nonce{}, fmt.Errorf("can't generate masking IV: %v", err) + } + + // Encode header data. + c.writeHeaders(&head) + + // Store sent WHOAREYOU challenges. + if challenge, ok := packet.(*Whoareyou); ok { + challenge.ChallengeData = bytesCopy(&c.buf) + c.sc.storeSentHandshake(id, addr, challenge) + } else if msgData == nil { + headerData := c.buf.Bytes() + msgData, err = c.encryptMessage(session, packet, &head, headerData) + if err != nil { + return nil, Nonce{}, err + } + } + + enc, err := c.EncodeRaw(id, head, msgData) + return enc, head.Nonce, err +} + +// EncodeRaw encodes a packet with the given header. +func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, error) { + c.writeHeaders(&head) + + // Apply masking. + masked := c.buf.Bytes()[sizeofMaskingIV:] + mask := head.mask(id) + mask.XORKeyStream(masked[:], masked[:]) + + // Write message data. + c.buf.Write(msgdata) + return c.buf.Bytes(), nil +} + +func (c *Codec) writeHeaders(head *Header) { + c.buf.Reset() + c.buf.Write(head.IV[:]) + binary.Write(&c.buf, binary.BigEndian, &head.StaticHeader) + c.buf.Write(head.AuthData) +} + +// makeHeader creates a packet header. +func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header { + var authsize int + switch flag { + case flagMessage: + authsize = sizeofMessageAuthData + case flagWhoareyou: + authsize = sizeofWhoareyouAuthData + case flagHandshake: + authsize = sizeofHandshakeAuthData + default: + panic(fmt.Errorf("BUG: invalid packet header flag %x", flag)) + } + authsize += authsizeExtra + if authsize > int(^uint16(0)) { + panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize)) + } + return Header{ + StaticHeader: StaticHeader{ + ProtocolID: protocolID, + Version: version, + Flag: flag, + AuthSize: uint16(authsize), + }, + } +} + +// encodeRandom encodes a packet with random content. +func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) { + head := c.makeHeader(toID, flagMessage, 0) + + // Encode auth data. + auth := messageAuthData{SrcID: c.localnode.ID()} + if _, err := crand.Read(head.Nonce[:]); err != nil { + return head, nil, fmt.Errorf("can't get random data: %v", err) + } + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, auth) + head.AuthData = c.headbuf.Bytes() + + // Fill message ciphertext buffer with random bytes. + c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...) + crand.Read(c.msgctbuf) + return head, c.msgctbuf, nil +} + +// encodeWhoareyou encodes a WHOAREYOU packet. +func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error) { + // Sanity check node field to catch misbehaving callers. + if packet.RecordSeq > 0 && packet.Node == nil { + panic("BUG: missing node in whoareyou with non-zero seq") + } + + // Create header. + head := c.makeHeader(toID, flagWhoareyou, 0) + head.AuthData = bytesCopy(&c.buf) + head.Nonce = packet.Nonce + + // Encode auth data. + auth := &whoareyouAuthData{ + IDNonce: packet.IDNonce, + RecordSeq: packet.RecordSeq, + } + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, auth) + head.AuthData = c.headbuf.Bytes() + return head, nil +} + +// encodeHandshakeMessage encodes the handshake message packet header. +func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) { + // Ensure calling code sets challenge.node. + if challenge.Node == nil { + panic("BUG: missing challenge.Node in encode") + } + + // Generate new secrets. + auth, session, err := c.makeHandshakeAuth(toID, addr, challenge) + if err != nil { + return Header{}, nil, err + } + + // Generate nonce for message. + nonce, err := c.sc.nextNonce(session) + if err != nil { + return Header{}, nil, fmt.Errorf("can't generate nonce: %v", err) + } + + // TODO: this should happen when the first authenticated message is received + c.sc.storeNewSession(toID, addr, session) + + // Encode the auth header. + var ( + authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record) + head = c.makeHeader(toID, flagHandshake, authsizeExtra) + ) + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, &auth.h) + c.headbuf.Write(auth.signature) + c.headbuf.Write(auth.pubkey) + c.headbuf.Write(auth.record) + head.AuthData = c.headbuf.Bytes() + head.Nonce = nonce + return head, session, err +} + +// encodeAuthHeader creates the auth header on a request packet following WHOAREYOU. +func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) { + auth := new(handshakeAuthData) + auth.h.SrcID = c.localnode.ID() + + // Create the ephemeral key. This needs to be first because the + // key is part of the ID nonce signature. + var remotePubkey = new(ecdsa.PublicKey) + if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil { + return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient") + } + ephkey, err := c.sc.ephemeralKeyGen() + if err != nil { + return nil, nil, fmt.Errorf("can't generate ephemeral key") + } + ephpubkey := EncodePubkey(&ephkey.PublicKey) + auth.pubkey = ephpubkey[:] + auth.h.PubkeySize = byte(len(auth.pubkey)) + + // Add ID nonce signature to response. + cdata := challenge.ChallengeData + idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID) + if err != nil { + return nil, nil, fmt.Errorf("can't sign: %v", err) + } + auth.signature = idsig + auth.h.SigSize = byte(len(auth.signature)) + + // Add our record to response if it's newer than what remote side has. + ln := c.localnode.Node() + if challenge.RecordSeq < ln.Seq() { + auth.record, _ = rlp.EncodeToBytes(ln.Record()) + } + + // Create session keys. + sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata) + if sec == nil { + return nil, nil, fmt.Errorf("key derivation failed") + } + return auth, sec, err +} + +// encodeMessage encodes an encrypted message packet. +func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) { + head := c.makeHeader(toID, flagMessage, 0) + + // Create the header. + nonce, err := c.sc.nextNonce(s) + if err != nil { + return Header{}, fmt.Errorf("can't generate nonce: %v", err) + } + auth := messageAuthData{SrcID: c.localnode.ID()} + c.buf.Reset() + binary.Write(&c.buf, binary.BigEndian, &auth) + head.AuthData = bytesCopy(&c.buf) + head.Nonce = nonce + return head, err +} + +func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData []byte) ([]byte, error) { + // Encode message plaintext. + c.msgbuf.Reset() + c.msgbuf.WriteByte(p.Kind()) + if err := rlp.Encode(&c.msgbuf, p); err != nil { + return nil, err + } + messagePT := c.msgbuf.Bytes() + + // Encrypt into message ciphertext buffer. + messageCT, err := encryptGCM(c.msgctbuf[:0], s.writeKey, head.Nonce[:], messagePT, headerData) + if err == nil { + c.msgctbuf = messageCT + } + return messageCT, err +} + +// Decode decodes a discovery packet. +func (c *Codec) Decode(input []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) { + // Unmask the static header. + if len(input) < sizeofStaticPacketData { + return enode.ID{}, nil, nil, errTooShort + } + var head Header + copy(head.IV[:], input[:sizeofMaskingIV]) + mask := head.mask(c.localnode.ID()) + staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData] + mask.XORKeyStream(staticHeader, staticHeader) + + // Decode and verify the static header. + c.reader.Reset(staticHeader) + binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader) + remainingInput := len(input) - sizeofStaticPacketData + if err := head.checkValid(remainingInput); err != nil { + return enode.ID{}, nil, nil, err + } + + // Unmask auth data. + authDataEnd := sizeofStaticPacketData + int(head.AuthSize) + authData := input[sizeofStaticPacketData:authDataEnd] + mask.XORKeyStream(authData, authData) + head.AuthData = authData + + // Delete timed-out handshakes. This must happen before decoding to avoid + // processing the same handshake twice. + c.sc.handshakeGC() + + // Decode auth part and message. + headerData := input[:authDataEnd] + msgData := input[authDataEnd:] + switch head.Flag { + case flagWhoareyou: + p, err = c.decodeWhoareyou(&head, headerData) + case flagHandshake: + n, p, err = c.decodeHandshakeMessage(addr, &head, headerData, msgData) + case flagMessage: + p, err = c.decodeMessage(addr, &head, headerData, msgData) + default: + err = errInvalidFlag + } + return head.src, n, p, err +} + +// decodeWhoareyou reads packet data after the header as a WHOAREYOU packet. +func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) { + if len(head.AuthData) != sizeofWhoareyouAuthData { + return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData)) + } + var auth whoareyouAuthData + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth) + p := &Whoareyou{ + Nonce: head.Nonce, + IDNonce: auth.IDNonce, + RecordSeq: auth.RecordSeq, + ChallengeData: make([]byte, len(headerData)), + } + copy(p.ChallengeData, headerData) + return p, nil +} + +func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData, msgData []byte) (n *enode.Node, p Packet, err error) { + node, auth, session, err := c.decodeHandshake(fromAddr, head) + if err != nil { + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return nil, nil, err + } + + // Decrypt the message using the new session keys. + msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, session.readKey) + if err != nil { + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return node, msg, err + } + + // Handshake OK, drop the challenge and store the new session keys. + c.sc.storeNewSession(auth.h.SrcID, fromAddr, session) + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return node, msg, nil +} + +func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, auth handshakeAuthData, s *session, err error) { + if auth, err = c.decodeHandshakeAuthData(head); err != nil { + return nil, auth, nil, err + } + + // Verify against our last WHOAREYOU. + challenge := c.sc.getHandshake(auth.h.SrcID, fromAddr) + if challenge == nil { + return nil, auth, nil, errUnexpectedHandshake + } + // Get node record. + n, err = c.decodeHandshakeRecord(challenge.Node, auth.h.SrcID, auth.record) + if err != nil { + return nil, auth, nil, err + } + // Verify ID nonce signature. + sig := auth.signature + cdata := challenge.ChallengeData + err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID()) + if err != nil { + return nil, auth, nil, err + } + // Verify ephemeral key is on curve. + ephkey, err := DecodePubkey(c.privkey.Curve, auth.pubkey) + if err != nil { + return nil, auth, nil, errInvalidAuthKey + } + // Derive sesssion keys. + session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata) + session = session.keysFlipped() + return n, auth, session, nil +} + +// decodeHandshakeAuthData reads the authdata section of a handshake packet. +func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, err error) { + // Decode fixed size part. + if len(head.AuthData) < sizeofHandshakeAuthData { + return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize) + } + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth.h) + head.src = auth.h.SrcID + + // Decode variable-size part. + var ( + vardata = head.AuthData[sizeofHandshakeAuthData:] + sigAndKeySize = int(auth.h.SigSize) + int(auth.h.PubkeySize) + keyOffset = int(auth.h.SigSize) + recOffset = keyOffset + int(auth.h.PubkeySize) + ) + if len(vardata) < sigAndKeySize { + return auth, errTooShort + } + auth.signature = vardata[:keyOffset] + auth.pubkey = vardata[keyOffset:recOffset] + auth.record = vardata[recOffset:] + return auth, nil +} + +// decodeHandshakeRecord verifies the node record contained in a handshake packet. The +// remote node should include the record if we don't have one or if ours is older than the +// latest sequence number. +func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) { + node := local + if len(remote) > 0 { + var record enr.Record + if err := rlp.DecodeBytes(remote, &record); err != nil { + return nil, err + } + if local == nil || local.Seq() < record.Seq() { + n, err := enode.New(enode.ValidSchemes, &record) + if err != nil { + return nil, fmt.Errorf("invalid node record: %v", err) + } + if n.ID() != wantID { + return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID()) + } + node = n + } + } + if node == nil { + return nil, errNoRecord + } + return node, nil +} + +// decodeMessage reads packet data following the header as an ordinary message packet. +func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData []byte) (Packet, error) { + if len(head.AuthData) != sizeofMessageAuthData { + return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData)) + } + var auth messageAuthData + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth) + head.src = auth.SrcID + + // Try decrypting the message. + key := c.sc.readKey(auth.SrcID, fromAddr) + msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key) + if err == errMessageDecrypt { + // It didn't work. Start the handshake since this is an ordinary message packet. + return &Unknown{Nonce: head.Nonce}, nil + } + return msg, err +} + +func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet, error) { + msgdata, err := decryptGCM(readKey, nonce, input, headerData) + if err != nil { + return nil, errMessageDecrypt + } + if len(msgdata) == 0 { + return nil, errMessageTooShort + } + return DecodeMessage(msgdata[0], msgdata[1:]) +} + +// checkValid performs some basic validity checks on the header. +// The packetLen here is the length remaining after the static header. +func (h *StaticHeader) checkValid(packetLen int) error { + if h.ProtocolID != protocolID { + return errInvalidHeader + } + if h.Version < minVersion { + return errMinVersion + } + if h.Flag != flagWhoareyou && packetLen < minMessageSize { + return errMsgTooShort + } + if int(h.AuthSize) > packetLen { + return errAuthSize + } + return nil +} + +// headerMask returns a cipher for 'masking' / 'unmasking' packet headers. +func (h *Header) mask(destID enode.ID) cipher.Stream { + block, err := aes.NewCipher(destID[:16]) + if err != nil { + panic("can't create cipher") + } + return cipher.NewCTR(block, h.IV[:]) +} + +func bytesCopy(r *bytes.Buffer) []byte { + b := make([]byte, r.Len()) + copy(b, r.Bytes()) + return b +} diff --git a/p2p/discover/v5wire/encoding_test.go b/p2p/discover/v5wire/encoding_test.go new file mode 100644 index 0000000000..d9c807e0a8 --- /dev/null +++ b/p2p/discover/v5wire/encoding_test.go @@ -0,0 +1,636 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "flag" + "fmt" + "io/ioutil" + "net" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// To regenerate discv5 test vectors, run +// +// go test -run TestVectors -write-test-vectors +// +var writeTestVectorsFlag = flag.Bool("write-test-vectors", false, "Overwrite discv5 test vectors in testdata/") + +var ( + testKeyA, _ = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f") + testKeyB, _ = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628") + testEphKey, _ = crypto.HexToECDSA("0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6") + testIDnonce = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} +) + +// This test checks that the minPacketSize and randomPacketMsgSize constants are well-defined. +func TestMinSizes(t *testing.T) { + var ( + gcmTagSize = 16 + emptyMsg = sizeofMessageAuthData + gcmTagSize + ) + t.Log("static header size", sizeofStaticPacketData) + t.Log("whoareyou size", sizeofStaticPacketData+sizeofWhoareyouAuthData) + t.Log("empty msg size", sizeofStaticPacketData+emptyMsg) + if want := emptyMsg; minMessageSize != want { + t.Fatalf("wrong minMessageSize %d, want %d", minMessageSize, want) + } + if sizeofMessageAuthData+randomPacketMsgSize < minMessageSize { + t.Fatalf("randomPacketMsgSize %d too small", randomPacketMsgSize) + } +} + +// This test checks the basic handshake flow where A talks to B and A has no secrets. +func TestHandshake(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + if len(net.nodeB.c.sc.handshakes) > 0 { + t.Fatalf("node B didn't remove handshake from challenge map") + } + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// This test checks that handshake attempts are removed within the timeout. +func TestHandshake_timeout(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) after timeout + net.clock.Run(handshakeTimeout + 1) + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) +} + +// This test checks handshake behavior when no record is sent in the auth response. +func TestHandshake_norecord(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + nodeA := net.nodeA.n() + if nodeA.Seq() == 0 { + t.Fatal("need non-zero sequence number") + } + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: nodeA.Seq(), + Node: nodeA, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// In this test, A tries to send FINDNODE with existing secrets but B doesn't know +// anything about A. +func TestHandshake_rekey(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + session := &session{ + readKey: []byte("BBBBBBBBBBBBBBBB"), + writeKey: []byte("AAAAAAAAAAAAAAAA"), + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session) + + // A -> B FINDNODE (encrypted with zero keys) + findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{}) + net.nodeB.expectDecode(t, UnknownPacket, findnode) + + // A <- B WHOAREYOU + challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce} + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // Check that new keys haven't been stored yet. + sa := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()) + if !bytes.Equal(sa.writeKey, session.writeKey) || !bytes.Equal(sa.readKey, session.readKey) { + t.Fatal("node A stored keys too early") + } + if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil { + t.Fatal("node B stored keys too early") + } + + // A -> B FINDNODE encrypted with new keys + findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// In this test A and B have different keys before the handshake. +func TestHandshake_rekey2(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + initKeysA := &session{ + readKey: []byte("BBBBBBBBBBBBBBBB"), + writeKey: []byte("AAAAAAAAAAAAAAAA"), + } + initKeysB := &session{ + readKey: []byte("CCCCCCCCCCCCCCCC"), + writeKey: []byte("DDDDDDDDDDDDDDDD"), + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA) + net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB) + + // A -> B FINDNODE encrypted with initKeysA + findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}}) + net.nodeB.expectDecode(t, UnknownPacket, findnode) + + // A <- B WHOAREYOU + challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce} + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) + findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +func TestHandshake_BadHandshakeAttack(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE + incorrect_challenge := &Whoareyou{ + IDNonce: [16]byte{5, 6, 7, 8, 9, 6, 11, 12}, + RecordSeq: challenge.RecordSeq, + Node: challenge.Node, + sent: challenge.sent, + } + incorrect_findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, incorrect_challenge, &Findnode{}) + incorrect_findnode2 := make([]byte, len(incorrect_findnode)) + copy(incorrect_findnode2, incorrect_findnode) + + net.nodeB.expectDecodeErr(t, errInvalidNonceSig, incorrect_findnode) + + // Reject new findnode as previous handshake is now deleted. + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, incorrect_findnode2) + + // The findnode packet is again rejected even with a valid challenge this time. + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) +} + +// This test checks some malformed packets. +func TestDecodeErrorsV5(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + net.nodeA.expectDecodeErr(t, errTooShort, []byte{}) + // TODO some more tests would be nice :) + // - check invalid authdata sizes + // - check invalid handshake data sizes +} + +// This test checks that all test vectors can be decoded. +func TestTestVectorsV5(t *testing.T) { + var ( + idA = enode.PubkeyToIDV4(&testKeyA.PublicKey) + idB = enode.PubkeyToIDV4(&testKeyB.PublicKey) + addr = "127.0.0.1" + session = &session{ + writeKey: hexutil.MustDecode("0x00000000000000000000000000000000"), + readKey: hexutil.MustDecode("0x01010101010101010101010101010101"), + } + challenge0A, challenge1A, challenge0B Whoareyou + ) + + // Create challenge packets. + c := Whoareyou{ + Nonce: Nonce{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + IDNonce: testIDnonce, + } + challenge0A, challenge1A, challenge0B = c, c, c + challenge1A.RecordSeq = 1 + net := newHandshakeTest() + challenge0A.Node = net.nodeA.n() + challenge0B.Node = net.nodeB.n() + challenge1A.Node = net.nodeA.n() + net.close() + + type testVectorTest struct { + name string // test vector name + packet Packet // the packet to be encoded + challenge *Whoareyou // handshake challenge passed to encoder + prep func(*handshakeTest) // called before encode/decode + } + tests := []testVectorTest{ + { + name: "v5.1-whoareyou", + packet: &challenge0B, + }, + { + name: "v5.1-ping-message", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 2, + }, + prep: func(net *handshakeTest) { + net.nodeA.c.sc.storeNewSession(idB, addr, session) + net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped()) + }, + }, + { + name: "v5.1-ping-handshake-enr", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 1, + }, + challenge: &challenge0A, + prep: func(net *handshakeTest) { + // Update challenge.Header.AuthData. + net.nodeA.c.Encode(idB, "", &challenge0A, nil) + net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge0A) + }, + }, + { + name: "v5.1-ping-handshake", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 1, + }, + challenge: &challenge1A, + prep: func(net *handshakeTest) { + // Update challenge data. + net.nodeA.c.Encode(idB, "", &challenge1A, nil) + net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge1A) + }, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + net := newHandshakeTest() + defer net.close() + + // Override all random inputs. + net.nodeA.c.sc.nonceGen = func(counter uint32) (Nonce, error) { + return Nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, nil + } + net.nodeA.c.sc.maskingIVGen = func(buf []byte) error { + return nil // all zero + } + net.nodeA.c.sc.ephemeralKeyGen = func() (*ecdsa.PrivateKey, error) { + return testEphKey, nil + } + + // Prime the codec for encoding/decoding. + if test.prep != nil { + test.prep(net) + } + + file := filepath.Join("testdata", test.name+".txt") + if *writeTestVectorsFlag { + // Encode the packet. + d, nonce := net.nodeA.encodeWithChallenge(t, net.nodeB, test.challenge, test.packet) + comment := testVectorComment(net, test.packet, test.challenge, nonce) + writeTestVector(file, comment, d) + } + enc := hexFile(file) + net.nodeB.expectDecode(t, test.packet.Kind(), enc) + }) + } +} + +// testVectorComment creates the commentary for discv5 test vector files. +func testVectorComment(net *handshakeTest, p Packet, challenge *Whoareyou, nonce Nonce) string { + o := new(strings.Builder) + printWhoareyou := func(p *Whoareyou) { + fmt.Fprintf(o, "whoareyou.challenge-data = %#x\n", p.ChallengeData) + fmt.Fprintf(o, "whoareyou.request-nonce = %#x\n", p.Nonce[:]) + fmt.Fprintf(o, "whoareyou.id-nonce = %#x\n", p.IDNonce[:]) + fmt.Fprintf(o, "whoareyou.enr-seq = %d\n", p.RecordSeq) + } + + fmt.Fprintf(o, "src-node-id = %#x\n", net.nodeA.id().Bytes()) + fmt.Fprintf(o, "dest-node-id = %#x\n", net.nodeB.id().Bytes()) + switch p := p.(type) { + case *Whoareyou: + // WHOAREYOU packet. + printWhoareyou(p) + case *Ping: + fmt.Fprintf(o, "nonce = %#x\n", nonce[:]) + fmt.Fprintf(o, "read-key = %#x\n", net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()).writeKey) + fmt.Fprintf(o, "ping.req-id = %#x\n", p.ReqID) + fmt.Fprintf(o, "ping.enr-seq = %d\n", p.ENRSeq) + if challenge != nil { + // Handshake message packet. + fmt.Fprint(o, "\nhandshake inputs:\n\n") + printWhoareyou(challenge) + fmt.Fprintf(o, "ephemeral-key = %#x\n", testEphKey.D.Bytes()) + fmt.Fprintf(o, "ephemeral-pubkey = %#x\n", crypto.CompressPubkey(&testEphKey.PublicKey)) + } + default: + panic(fmt.Errorf("unhandled packet type %T", p)) + } + return o.String() +} + +// This benchmark checks performance of handshake packet decoding. +func BenchmarkV5_DecodeHandshakePingSecp256k1(b *testing.B) { + net := newHandshakeTest() + defer net.close() + + var ( + idA = net.nodeA.id() + challenge = &Whoareyou{Node: net.nodeB.n()} + message = &Ping{ReqID: []byte("reqid")} + ) + enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), "", message, challenge) + if err != nil { + b.Fatal("can't encode handshake packet") + } + challenge.Node = nil // force ENR signature verification in decoder + b.ResetTimer() + + input := make([]byte, len(enc)) + for i := 0; i < b.N; i++ { + copy(input, enc) + net.nodeB.c.sc.storeSentHandshake(idA, "", challenge) + _, _, _, err := net.nodeB.c.Decode(input, "") + if err != nil { + b.Fatal(err) + } + } +} + +// This benchmark checks how long it takes to decode an encrypted ping packet. +func BenchmarkV5_DecodePing(b *testing.B) { + net := newHandshakeTest() + defer net.close() + + session := &session{ + readKey: []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17}, + writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134}, + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session) + net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped()) + addrB := net.nodeA.addr() + ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5} + enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil) + if err != nil { + b.Fatalf("can't encode: %v", err) + } + b.ResetTimer() + + input := make([]byte, len(enc)) + for i := 0; i < b.N; i++ { + copy(input, enc) + _, _, packet, _ := net.nodeB.c.Decode(input, addrB) + if _, ok := packet.(*Ping); !ok { + b.Fatalf("wrong packet type %T", packet) + } + } +} + +var pp = spew.NewDefaultConfig() + +type handshakeTest struct { + nodeA, nodeB handshakeTestNode + clock mclock.Simulated +} + +type handshakeTestNode struct { + ln *enode.LocalNode + c *Codec +} + +func newHandshakeTest() *handshakeTest { + t := new(handshakeTest) + t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock) + t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock) + return t +} + +func (t *handshakeTest) close() { + t.nodeA.ln.Database().Close() + t.nodeB.ln.Database().Close() +} + +func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock) { + db, _ := enode.OpenDB("") + n.ln = enode.NewLocalNode(db, key) + n.ln.SetStaticIP(ip) + if n.ln.Node().Seq() != 1 { + panic(fmt.Errorf("unexpected seq %d", n.ln.Node().Seq())) + } + n.c = NewCodec(n.ln, key, clock) +} + +func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p Packet) ([]byte, Nonce) { + t.Helper() + return n.encodeWithChallenge(t, to, nil, p) +} + +func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *Whoareyou, p Packet) ([]byte, Nonce) { + t.Helper() + + // Copy challenge and add destination node. This avoids sharing 'c' among the two codecs. + var challenge *Whoareyou + if c != nil { + challengeCopy := *c + challenge = &challengeCopy + challenge.Node = to.n() + } + // Encode to destination. + enc, nonce, err := n.c.Encode(to.id(), to.addr(), p, challenge) + if err != nil { + t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) + } + t.Logf("(%s) -> (%s) %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.Name(), hex.Dump(enc)) + return enc, nonce +} + +func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) Packet { + t.Helper() + + dec, err := n.decode(p) + if err != nil { + t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) + } + t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec)) + if dec.Kind() != ptype { + t.Fatalf("expected packet type %d, got %d", ptype, dec.Kind()) + } + return dec +} + +func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) { + t.Helper() + if _, err := n.decode(p); !reflect.DeepEqual(err, wantErr) { + t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr)) + } +} + +func (n *handshakeTestNode) decode(input []byte) (Packet, error) { + _, _, p, err := n.c.Decode(input, "127.0.0.1") + return p, err +} + +func (n *handshakeTestNode) n() *enode.Node { + return n.ln.Node() +} + +func (n *handshakeTestNode) addr() string { + return n.ln.Node().IP().String() +} + +func (n *handshakeTestNode) id() enode.ID { + return n.ln.ID() +} + +// hexFile reads the given file and decodes the hex data contained in it. +// Whitespace and any lines beginning with the # character are ignored. +func hexFile(file string) []byte { + fileContent, err := ioutil.ReadFile(file) + if err != nil { + panic(err) + } + + // Gather hex data, ignore comments. + var text []byte + for _, line := range bytes.Split(fileContent, []byte("\n")) { + line = bytes.TrimSpace(line) + if len(line) > 0 && line[0] == '#' { + continue + } + text = append(text, line...) + } + + // Parse the hex. + if bytes.HasPrefix(text, []byte("0x")) { + text = text[2:] + } + data := make([]byte, hex.DecodedLen(len(text))) + if _, err := hex.Decode(data, text); err != nil { + panic("invalid hex in " + file) + } + return data +} + +// writeTestVector writes a test vector file with the given commentary and binary data. +func writeTestVector(file, comment string, data []byte) { + fd, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + panic(err) + } + defer fd.Close() + + if len(comment) > 0 { + for _, line := range strings.Split(strings.TrimSpace(comment), "\n") { + fmt.Fprintf(fd, "# %s\n", line) + } + fmt.Fprintln(fd) + } + for len(data) > 0 { + var chunk []byte + if len(data) < 32 { + chunk = data + } else { + chunk = data[:32] + } + data = data[len(chunk):] + fmt.Fprintf(fd, "%x\n", chunk) + } +} diff --git a/p2p/discover/v5wire/msg.go b/p2p/discover/v5wire/msg.go new file mode 100644 index 0000000000..7c3686111b --- /dev/null +++ b/p2p/discover/v5wire/msg.go @@ -0,0 +1,249 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "fmt" + "net" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +// Packet is implemented by all message types. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. + RequestID() []byte // Returns the request ID. + SetRequestID([]byte) // Sets the request ID. +} + +// Message types. +const ( + PingMsg byte = iota + 1 + PongMsg + FindnodeMsg + NodesMsg + TalkRequestMsg + TalkResponseMsg + RequestTicketMsg + TicketMsg + RegtopicMsg + RegconfirmationMsg + TopicQueryMsg + + UnknownPacket = byte(255) // any non-decryptable packet + WhoareyouPacket = byte(254) // the WHOAREYOU packet +) + +// Protocol messages. +type ( + // Unknown represents any packet that can't be decrypted. + Unknown struct { + Nonce Nonce + } + + // WHOAREYOU contains the handshake challenge. + Whoareyou struct { + ChallengeData []byte // Encoded challenge + Nonce Nonce // Nonce of request packet + IDNonce [16]byte // Identity proof data + RecordSeq uint64 // ENR sequence number of recipient + + // Node is the locally known node record of recipient. + // This must be set by the caller of Encode. + Node *enode.Node + + sent mclock.AbsTime // for handshake GC. + } + + // PING is sent during liveness checks. + Ping struct { + ReqID []byte + ENRSeq uint64 + } + + // PONG is the reply to PING. + Pong struct { + ReqID []byte + ENRSeq uint64 + ToIP net.IP // These fields should mirror the UDP envelope address of the ping + ToPort uint16 // packet, which provides a way to discover the the external address (after NAT). + } + + // FINDNODE is a query for nodes in the given bucket. + Findnode struct { + ReqID []byte + Distances []uint + } + + // NODES is the reply to FINDNODE and TOPICQUERY. + Nodes struct { + ReqID []byte + Total uint8 + Nodes []*enr.Record + } + + // TALKREQ is an application-level request. + TalkRequest struct { + ReqID []byte + Protocol string + Message []byte + } + + // TALKRESP is the reply to TALKREQ. + TalkResponse struct { + ReqID []byte + Message []byte + } + + // REQUESTTICKET requests a ticket for a topic queue. + RequestTicket struct { + ReqID []byte + Topic []byte + } + + // TICKET is the response to REQUESTTICKET. + Ticket struct { + ReqID []byte + Ticket []byte + } + + // REGTOPIC registers the sender in a topic queue using a ticket. + Regtopic struct { + ReqID []byte + Ticket []byte + ENR *enr.Record + } + + // REGCONFIRMATION is the reply to REGTOPIC. + Regconfirmation struct { + ReqID []byte + Registered bool + } + + // TOPICQUERY asks for nodes with the given topic. + TopicQuery struct { + ReqID []byte + Topic []byte + } +) + +// DecodeMessage decodes the message body of a packet. +func DecodeMessage(ptype byte, body []byte) (Packet, error) { + var dec Packet + switch ptype { + case PingMsg: + dec = new(Ping) + case PongMsg: + dec = new(Pong) + case FindnodeMsg: + dec = new(Findnode) + case NodesMsg: + dec = new(Nodes) + case TalkRequestMsg: + dec = new(TalkRequest) + case TalkResponseMsg: + dec = new(TalkResponse) + case RequestTicketMsg: + dec = new(RequestTicket) + case TicketMsg: + dec = new(Ticket) + case RegtopicMsg: + dec = new(Regtopic) + case RegconfirmationMsg: + dec = new(Regconfirmation) + case TopicQueryMsg: + dec = new(TopicQuery) + default: + return nil, fmt.Errorf("unknown packet type %d", ptype) + } + if err := rlp.DecodeBytes(body, dec); err != nil { + return nil, err + } + if dec.RequestID() != nil && len(dec.RequestID()) > 8 { + return nil, ErrInvalidReqID + } + return dec, nil +} + +func (*Whoareyou) Name() string { return "WHOAREYOU/v5" } +func (*Whoareyou) Kind() byte { return WhoareyouPacket } +func (*Whoareyou) RequestID() []byte { return nil } +func (*Whoareyou) SetRequestID([]byte) {} + +func (*Unknown) Name() string { return "UNKNOWN/v5" } +func (*Unknown) Kind() byte { return UnknownPacket } +func (*Unknown) RequestID() []byte { return nil } +func (*Unknown) SetRequestID([]byte) {} + +func (*Ping) Name() string { return "PING/v5" } +func (*Ping) Kind() byte { return PingMsg } +func (p *Ping) RequestID() []byte { return p.ReqID } +func (p *Ping) SetRequestID(id []byte) { p.ReqID = id } + +func (*Pong) Name() string { return "PONG/v5" } +func (*Pong) Kind() byte { return PongMsg } +func (p *Pong) RequestID() []byte { return p.ReqID } +func (p *Pong) SetRequestID(id []byte) { p.ReqID = id } + +func (*Findnode) Name() string { return "FINDNODE/v5" } +func (*Findnode) Kind() byte { return FindnodeMsg } +func (p *Findnode) RequestID() []byte { return p.ReqID } +func (p *Findnode) SetRequestID(id []byte) { p.ReqID = id } + +func (*Nodes) Name() string { return "NODES/v5" } +func (*Nodes) Kind() byte { return NodesMsg } +func (p *Nodes) RequestID() []byte { return p.ReqID } +func (p *Nodes) SetRequestID(id []byte) { p.ReqID = id } + +func (*TalkRequest) Name() string { return "TALKREQ/v5" } +func (*TalkRequest) Kind() byte { return TalkRequestMsg } +func (p *TalkRequest) RequestID() []byte { return p.ReqID } +func (p *TalkRequest) SetRequestID(id []byte) { p.ReqID = id } + +func (*TalkResponse) Name() string { return "TALKRESP/v5" } +func (*TalkResponse) Kind() byte { return TalkResponseMsg } +func (p *TalkResponse) RequestID() []byte { return p.ReqID } +func (p *TalkResponse) SetRequestID(id []byte) { p.ReqID = id } + +func (*RequestTicket) Name() string { return "REQTICKET/v5" } +func (*RequestTicket) Kind() byte { return RequestTicketMsg } +func (p *RequestTicket) RequestID() []byte { return p.ReqID } +func (p *RequestTicket) SetRequestID(id []byte) { p.ReqID = id } + +func (*Regtopic) Name() string { return "REGTOPIC/v5" } +func (*Regtopic) Kind() byte { return RegtopicMsg } +func (p *Regtopic) RequestID() []byte { return p.ReqID } +func (p *Regtopic) SetRequestID(id []byte) { p.ReqID = id } + +func (*Ticket) Name() string { return "TICKET/v5" } +func (*Ticket) Kind() byte { return TicketMsg } +func (p *Ticket) RequestID() []byte { return p.ReqID } +func (p *Ticket) SetRequestID(id []byte) { p.ReqID = id } + +func (*Regconfirmation) Name() string { return "REGCONFIRMATION/v5" } +func (*Regconfirmation) Kind() byte { return RegconfirmationMsg } +func (p *Regconfirmation) RequestID() []byte { return p.ReqID } +func (p *Regconfirmation) SetRequestID(id []byte) { p.ReqID = id } + +func (*TopicQuery) Name() string { return "TOPICQUERY/v5" } +func (*TopicQuery) Kind() byte { return TopicQueryMsg } +func (p *TopicQuery) RequestID() []byte { return p.ReqID } +func (p *TopicQuery) SetRequestID(id []byte) { p.ReqID = id } diff --git a/p2p/discover/v5_session.go b/p2p/discover/v5wire/session.go similarity index 58% rename from p2p/discover/v5_session.go rename to p2p/discover/v5wire/session.go index 8a0eeb6977..d52b5c1181 100644 --- a/p2p/discover/v5_session.go +++ b/p2p/discover/v5wire/session.go @@ -14,22 +14,33 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package discover +package v5wire import ( + "crypto/ecdsa" crand "crypto/rand" + "encoding/binary" + "time" "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/hashicorp/golang-lru/simplelru" ) -// The sessionCache keeps negotiated encryption keys and +const handshakeTimeout = time.Second + +// The SessionCache keeps negotiated encryption keys and // state for in-progress handshakes in the Discovery v5 wire protocol. -type sessionCache struct { +type SessionCache struct { sessions *simplelru.LRU - handshakes map[sessionID]*whoareyouV5 + handshakes map[sessionID]*Whoareyou clock mclock.Clock + + // hooks for overriding randomness. + nonceGen func(uint32) (Nonce, error) + maskingIVGen func([]byte) error + ephemeralKeyGen func() (*ecdsa.PrivateKey, error) } // sessionID identifies a session or handshake. @@ -45,27 +56,45 @@ type session struct { nonceCounter uint32 } -func newSessionCache(maxItems int, clock mclock.Clock) *sessionCache { +// keysFlipped returns a copy of s with the read and write keys flipped. +func (s *session) keysFlipped() *session { + return &session{s.readKey, s.writeKey, s.nonceCounter} +} + +func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache { cache, err := simplelru.NewLRU(maxItems, nil) if err != nil { panic("can't create session cache") } - return &sessionCache{ - sessions: cache, - handshakes: make(map[sessionID]*whoareyouV5), - clock: clock, + return &SessionCache{ + sessions: cache, + handshakes: make(map[sessionID]*Whoareyou), + clock: clock, + nonceGen: generateNonce, + maskingIVGen: generateMaskingIV, + ephemeralKeyGen: crypto.GenerateKey, } } +func generateNonce(counter uint32) (n Nonce, err error) { + binary.BigEndian.PutUint32(n[:4], counter) + _, err = crand.Read(n[4:]) + return n, err +} + +func generateMaskingIV(buf []byte) error { + _, err := crand.Read(buf) + return err +} + // nextNonce creates a nonce for encrypting a message to the given session. -func (sc *sessionCache) nextNonce(id enode.ID, addr string) []byte { - n := make([]byte, gcmNonceSize) - crand.Read(n) - return n +func (sc *SessionCache) nextNonce(s *session) (Nonce, error) { + s.nonceCounter++ + return sc.nonceGen(s.nonceCounter) } // session returns the current session for the given node, if any. -func (sc *sessionCache) session(id enode.ID, addr string) *session { +func (sc *SessionCache) session(id enode.ID, addr string) *session { item, ok := sc.sessions.Get(sessionID{id, addr}) if !ok { return nil @@ -74,46 +103,36 @@ func (sc *sessionCache) session(id enode.ID, addr string) *session { } // readKey returns the current read key for the given node. -func (sc *sessionCache) readKey(id enode.ID, addr string) []byte { +func (sc *SessionCache) readKey(id enode.ID, addr string) []byte { if s := sc.session(id, addr); s != nil { return s.readKey } return nil } -// writeKey returns the current read key for the given node. -func (sc *sessionCache) writeKey(id enode.ID, addr string) []byte { - if s := sc.session(id, addr); s != nil { - return s.writeKey - } - return nil -} - // storeNewSession stores new encryption keys in the cache. -func (sc *sessionCache) storeNewSession(id enode.ID, addr string, r, w []byte) { - sc.sessions.Add(sessionID{id, addr}, &session{ - readKey: r, writeKey: w, - }) +func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) { + sc.sessions.Add(sessionID{id, addr}, s) } // getHandshake gets the handshake challenge we previously sent to the given remote node. -func (sc *sessionCache) getHandshake(id enode.ID, addr string) *whoareyouV5 { +func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou { return sc.handshakes[sessionID{id, addr}] } // storeSentHandshake stores the handshake challenge sent to the given remote node. -func (sc *sessionCache) storeSentHandshake(id enode.ID, addr string, challenge *whoareyouV5) { +func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) { challenge.sent = sc.clock.Now() sc.handshakes[sessionID{id, addr}] = challenge } // deleteHandshake deletes handshake data for the given node. -func (sc *sessionCache) deleteHandshake(id enode.ID, addr string) { +func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) { delete(sc.handshakes, sessionID{id, addr}) } // handshakeGC deletes timed-out handshakes. -func (sc *sessionCache) handshakeGC() { +func (sc *SessionCache) handshakeGC() { deadline := sc.clock.Now().Add(-handshakeTimeout) for key, challenge := range sc.handshakes { if challenge.sent < deadline { diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt new file mode 100644 index 0000000000..477f9e15a8 --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt @@ -0,0 +1,27 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x53b1c075f41876423154e157470c2f48 +# ping.req-id = 0x00000001 +# ping.enr-seq = 1 +# +# handshake inputs: +# +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 0 +# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6 +# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5 + +00000000000000000000000000000000088b3d4342774649305f313964a39e55 +ea96c005ad539c8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08da4bb23698868350aaad22e3ab8dd034f548a1c43cd246be9856 +2fafa0a1fa86d8e7a3b95ae78cc2b988ded6a5b59eb83ad58097252188b902b2 +1481e30e5e285f19735796706adff216ab862a9186875f9494150c4ae06fa4d1 +f0396c93f215fa4ef524e0ed04c3c21e39b1868e1ca8105e585ec17315e755e6 +cfc4dd6cb7fd8e1a1f55e49b4b5eb024221482105346f3c82b15fdaae36a3bb1 +2a494683b4a3c7f2ae41306252fed84785e2bbff3b022812d0882f06978df84a +80d443972213342d04b9048fc3b1d5fcb1df0f822152eced6da4d3f6df27e70e +4539717307a0208cd208d65093ccab5aa596a34d7511401987662d8cf62b1394 +71 diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt new file mode 100644 index 0000000000..b3f304766c --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt @@ -0,0 +1,23 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x4f9fac6de7567d1e3b1241dffe90f662 +# ping.req-id = 0x00000001 +# ping.enr-seq = 1 +# +# handshake inputs: +# +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000001 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 1 +# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6 +# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5 + +00000000000000000000000000000000088b3d4342774649305f313964a39e55 +ea96c005ad521d8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08da4bb252012b2cba3f4f374a90a75cff91f142fa9be3e0a5f3ef +268ccb9065aeecfd67a999e7fdc137e062b2ec4a0eb92947f0d9a74bfbf44dfb +a776b21301f8b65efd5796706adff216ab862a9186875f9494150c4ae06fa4d1 +f0396c93f215fa4ef524f1eadf5f0f4126b79336671cbcf7a885b1f8bd2a5d83 +9cf8 diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-message.txt b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt new file mode 100644 index 0000000000..f82b99c3bc --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt @@ -0,0 +1,10 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x00000000000000000000000000000000 +# ping.req-id = 0x00000001 +# ping.enr-seq = 2 + +00000000000000000000000000000000088b3d4342774649325f313964a39e55 +ea96c005ad52be8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08dab84102ed931f66d1492acb308fa1c6715b9d139b81acbdcc diff --git a/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt new file mode 100644 index 0000000000..1a75f525ee --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt @@ -0,0 +1,9 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 0 + +00000000000000000000000000000000088b3d434277464933a1ccc59f5967ad +1d6035f15e528627dde75cd68292f9e6c27d6b66c8100a873fcbaed4e16b8d diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 5380f12a9b..c557e68e70 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -23,7 +23,6 @@ import ( "errors" "fmt" "math/bits" - "math/rand" "net" "strings" @@ -367,23 +366,3 @@ func LogDist(a, b ID) int { } return len(a)*8 - lz } - -// RandomID returns a random ID b such that logdist(a, b) == n. -func RandomID(a ID, n int) (b ID) { - if n == 0 { - return a - } - // flip bit at position n, fill the rest with random bits - b = a - pos := len(a) - n/8 - 1 - bit := byte(0x01) << (byte(n%8) - 1) - if bit == 0 { - pos++ - bit = 0x80 - } - b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits - for i := pos + 1; i < len(a); i++ { - b[i] = byte(rand.Intn(255)) - } - return b -} diff --git a/p2p/netutil/error.go b/p2p/netutil/error.go index cb21b9cd4c..5d3d9bfd65 100644 --- a/p2p/netutil/error.go +++ b/p2p/netutil/error.go @@ -23,3 +23,11 @@ func IsTemporaryError(err error) bool { }) return ok && tempErr.Temporary() || isPacketTooBig(err) } + +// IsTimeout checks whether the given error is a timeout. +func IsTimeout(err error) bool { + timeoutErr, ok := err.(interface { + Timeout() bool + }) + return ok && timeoutErr.Timeout() +} diff --git a/p2p/peer.go b/p2p/peer.go index c6f8fec14f..00d44fd228 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -142,8 +142,17 @@ func (p *Peer) Node() *enode.Node { return p.rw.node } -// Name returns the node name that the remote node advertised. +// Name returns an abbreviated form of the name func (p *Peer) Name() string { + s := p.rw.name + if len(s) > 20 { + return s[:20] + "..." + } + return s +} + +// Fullname returns the node name that the remote node advertised. +func (p *Peer) Fullname() string { return p.rw.name } @@ -478,7 +487,7 @@ func (p *Peer) Info() *PeerInfo { info := &PeerInfo{ Enode: p.Node().URLv4(), ID: p.ID().String(), - Name: p.Name(), + Name: p.Fullname(), Caps: caps, Protocols: make(map[string]interface{}), } diff --git a/p2p/server.go b/p2p/server.go index 8c8e95987b..1067a12520 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -767,7 +767,7 @@ running: // The handshakes are done and it passed all checks. p := srv.launchPeer(c) peers[c.node.ID()] = p - srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", truncateName(c.name)) + srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", p.Name()) srv.dialsched.peerAdded(c) if p.Inbound() { inboundCount++ @@ -1053,13 +1053,6 @@ func nodeFromConn(pubkey *ecdsa.PublicKey, conn net.Conn) *enode.Node { return enode.NewV4(pubkey, ip, port, port) } -func truncateName(s string) string { - if len(s) > 20 { - return s[:20] + "..." - } - return s -} - // checkpoint sends the conn to run, which performs the // post-handshake checks for the stage (posthandshake, addpeer). func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error { diff --git a/params/bootnodes.go b/params/bootnodes.go index c8736b8ae8..42a6e2ec7c 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -56,11 +56,15 @@ var GoerliBootnodes = []string{ "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", - "enode://c1f8b7c2ac4453271fa07d8e9ecf9a2e8285aa0bd0c07df0131f47153306b0736fd3db8924e7a9bf0bed6b1d8d4f87362a71b033dc7c64547728d953e43e59b2@52.64.155.147:30303", - "enode://f4a9c6ee28586009fb5a96c8af13a58ed6d8315a9eee4772212c1d4d9cebe5a8b8a78ea4434f318726317d04a3f531a1ef0420cf9752605a562cfe858c46e263@213.186.16.82:30303", + "enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303", // Ethereum Foundation bootnode "enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303", + + // Goerli Initiative bootnodes + "enode://a869b02cec167211fb4815a82941db2e7ed2936fd90e78619c53eb17753fcf0207463e3419c264e2a1dd8786de0df7e68cf99571ab8aeb7c4e51367ef186b1dd@51.15.116.226:30303", + "enode://807b37ee4816ecf407e9112224494b74dd5933625f655962d892f2f0f02d7fbbb3e2a94cf87a96609526f30c998fd71e93e2f53015c558ffc8b03eceaf30ee33@51.15.119.157:30303", + "enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303", } // YoloV1Bootnodes are the enode URLs of the P2P bootstrap nodes running on the diff --git a/params/config.go b/params/config.go index 9b5612f465..2e22bc3008 100644 --- a/params/config.go +++ b/params/config.go @@ -74,10 +74,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 333, - SectionHead: common.HexToHash("0xb80784cbe88077e5911b446765edc814dd67ca3f6bdd33b6ec72d66058df4a11"), - CHTRoot: common.HexToHash("0x4da9cde840dd3de39916620f7a97674c5747a89a9359e6b918e134d199a8dd45"), - BloomRoot: common.HexToHash("0xdd0f4fef7fa2a5cc05d49568e38f15dab24098ffc7677a2e35d1a8d67f5458af"), + SectionIndex: 336, + SectionHead: common.HexToHash("0xd42b78902b6527a80337bf1bc372a3ccc3db97e9cc7cf421ca047ae9076c716b"), + CHTRoot: common.HexToHash("0xd97f3b30f7e0cb958e4c67c53ec27745e5a165e33e56821b86523dfee62b783a"), + BloomRoot: common.HexToHash("0xf3cbfd070fababfe2adc9b23fc02c731f6ca2cce6646b3ede4ef2db06092ccce"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -113,10 +113,10 @@ var ( // RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. RopstenTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 262, - SectionHead: common.HexToHash("0x12b068f285789b966a983b632266484f1bc93803df6c78773538a5777f57a236"), - CHTRoot: common.HexToHash("0x14000a1407e866f174f3a20fe9f271acd704bcf929b5205d83b70a1bba8c82c2"), - BloomRoot: common.HexToHash("0x2f4f4a34a55e35d0691c79a79e39b6f661259345080fb880da5195c11c2413be"), + SectionIndex: 269, + SectionHead: common.HexToHash("0x290a9eb65e65c64601d1b05522533ed502098a246736b348502a170818a33d64"), + CHTRoot: common.HexToHash("0x530ebac02264227277d0a16b0819ef96a2011a6e1e66523ebff8040f4a3437ca"), + BloomRoot: common.HexToHash("0x480cd5b3198a0767022902130546854a2e8867cce573c1cf0ce54e67a7bf5efb"), } // RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle. @@ -155,10 +155,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 220, - SectionHead: common.HexToHash("0x9513befa126a83c96a6408ee8b34502699094a49b2bf1064b2de31b010a03798"), - CHTRoot: common.HexToHash("0x490a17d3bfbfc9bca9de087c5ee9c9f69dc2359cad9c1fe68cab639fdbcfccee"), - BloomRoot: common.HexToHash("0x56bf5fda940ca4ca8346e42ef86f9092c82268c304c03c4093b21c1aa07190fc"), + SectionIndex: 223, + SectionHead: common.HexToHash("0x03ca0d5e3a931c77cd7a97bbaa2d9e4edc4549c621dc1d223a29f10c86a4a16a"), + CHTRoot: common.HexToHash("0x6573dbdd91b2958b446bd04d67c23e5f14b2510ac96e8df1b6a894dc49e37c6c"), + BloomRoot: common.HexToHash("0x28a35042a4e88efbac55fe566faf7fce000dc436f17fd4cb4b081c9cd793e1a7"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -195,10 +195,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 105, - SectionHead: common.HexToHash("0x695f5b67d1985fb13d177c56d20ded0622d7f63a1623959fb4b5c5e38dc6bbee"), - CHTRoot: common.HexToHash("0x4c281ef1ca63e6f9bb4ce8e46e80e478787c91da95c3727550ee418886dd6415"), - BloomRoot: common.HexToHash("0xa02463cc6ee54f12990e9adb019e34696ad1efe2694cf07187d7ce0802cd653d"), + SectionIndex: 107, + SectionHead: common.HexToHash("0xff3ae39199fa191894de419e7f673c8627aa8cc7af924b90f36635b6add375f2"), + CHTRoot: common.HexToHash("0x27d59d60c652425b6b593a882f55a4ff57f24e470a810a6e3c8ba71833a20220"), + BloomRoot: common.HexToHash("0x3c14066d8bb3733780c06b8165768dbb9dd23b75f56012fe5f2fb3c2fb70cadb"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. diff --git a/params/protocol_params.go b/params/protocol_params.go index 1a9863d317..9f8c225657 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -57,14 +57,10 @@ const ( NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value - SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed - SstoreNoopGasEIP2200 uint64 = 800 // Once per SSTORE operation if the value doesn't change. - SstoreDirtyGasEIP2200 uint64 = 800 // Once per SSTORE operation if a dirty value is changed. - SstoreInitGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero - SstoreInitRefundEIP2200 uint64 = 19200 // Once per SSTORE operation for resetting to the original zero value - SstoreCleanGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else - SstoreCleanRefundEIP2200 uint64 = 4200 // Once per SSTORE operation for resetting to the original non-zero value - SstoreClearRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot + SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed + SstoreSetGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero + SstoreResetGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else + SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot JumpdestGas uint64 = 1 // Once per JUMPDEST operation. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. diff --git a/params/version.go b/params/version.go index 37ee260fef..bce4de426e 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 1 // Major version component of the current release VersionMinor = 9 // Minor version component of the current release - VersionPatch = 22 // Patch version component of the current release + VersionPatch = 23 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string QuorumVersionMajor = 21 diff --git a/permission/v1/bind/accounts.go b/permission/v1/bind/accounts.go index 02940eae44..d57a3d5a24 100644 --- a/permission/v1/bind/accounts.go +++ b/permission/v1/bind/accounts.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindAcctManager(address common.Address, caller bind.ContractCaller, transac // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_AcctManager *AcctManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_AcctManager *AcctManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _AcctManager.Contract.AcctManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_AcctManager *AcctManagerRaw) Transact(opts *bind.TransactOpts, method str // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_AcctManager *AcctManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_AcctManager *AcctManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _AcctManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,202 +192,225 @@ func (_AcctManager *AcctManagerTransactorRaw) Transact(opts *bind.TransactOpts, // CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. // -// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) view returns(bool) func (_AcctManager *AcctManagerCaller) CheckOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string, _ultParent string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "checkOrgAdmin", _account, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "checkOrgAdmin", _account, _orgId, _ultParent) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. // -// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) view returns(bool) func (_AcctManager *AcctManagerSession) CheckOrgAdmin(_account common.Address, _orgId string, _ultParent string) (bool, error) { return _AcctManager.Contract.CheckOrgAdmin(&_AcctManager.CallOpts, _account, _orgId, _ultParent) } // CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. // -// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) view returns(bool) func (_AcctManager *AcctManagerCallerSession) CheckOrgAdmin(_account common.Address, _orgId string, _ultParent string) (bool, error) { return _AcctManager.Contract.CheckOrgAdmin(&_AcctManager.CallOpts, _account, _orgId, _ultParent) } // GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. // -// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetails(address _account) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerCaller) GetAccountDetails(opts *bind.CallOpts, _account common.Address) (common.Address, string, string, *big.Int, bool, error) { - var ( - ret0 = new(common.Address) - ret1 = new(string) - ret2 = new(string) - ret3 = new(*big.Int) - ret4 = new(bool) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - ret4, - } - err := _AcctManager.contract.Call(opts, out, "getAccountDetails", _account) - return *ret0, *ret1, *ret2, *ret3, *ret4, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getAccountDetails", _account) + + if err != nil { + return *new(common.Address), *new(string), *new(string), *new(*big.Int), *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(bool)).(*bool) + + return out0, out1, out2, out3, out4, err + } // GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. // -// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetails(address _account) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerSession) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { return _AcctManager.Contract.GetAccountDetails(&_AcctManager.CallOpts, _account) } // GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. // -// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetails(address _account) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerCallerSession) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { return _AcctManager.Contract.GetAccountDetails(&_AcctManager.CallOpts, _account) } // GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. // -// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerCaller) GetAccountDetailsFromIndex(opts *bind.CallOpts, _aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { - var ( - ret0 = new(common.Address) - ret1 = new(string) - ret2 = new(string) - ret3 = new(*big.Int) - ret4 = new(bool) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - ret4, - } - err := _AcctManager.contract.Call(opts, out, "getAccountDetailsFromIndex", _aIndex) - return *ret0, *ret1, *ret2, *ret3, *ret4, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getAccountDetailsFromIndex", _aIndex) + + if err != nil { + return *new(common.Address), *new(string), *new(string), *new(*big.Int), *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(bool)).(*bool) + + return out0, out1, out2, out3, out4, err + } // GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. // -// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerSession) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { return _AcctManager.Contract.GetAccountDetailsFromIndex(&_AcctManager.CallOpts, _aIndex) } // GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. // -// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerCallerSession) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { return _AcctManager.Contract.GetAccountDetailsFromIndex(&_AcctManager.CallOpts, _aIndex) } // GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. // -// Solidity: function getAccountRole(address _account) constant returns(string) +// Solidity: function getAccountRole(address _account) view returns(string) func (_AcctManager *AcctManagerCaller) GetAccountRole(opts *bind.CallOpts, _account common.Address) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "getAccountRole", _account) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getAccountRole", _account) + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. // -// Solidity: function getAccountRole(address _account) constant returns(string) +// Solidity: function getAccountRole(address _account) view returns(string) func (_AcctManager *AcctManagerSession) GetAccountRole(_account common.Address) (string, error) { return _AcctManager.Contract.GetAccountRole(&_AcctManager.CallOpts, _account) } // GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. // -// Solidity: function getAccountRole(address _account) constant returns(string) +// Solidity: function getAccountRole(address _account) view returns(string) func (_AcctManager *AcctManagerCallerSession) GetAccountRole(_account common.Address) (string, error) { return _AcctManager.Contract.GetAccountRole(&_AcctManager.CallOpts, _account) } // GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. // -// Solidity: function getNumberOfAccounts() constant returns(uint256) +// Solidity: function getNumberOfAccounts() view returns(uint256) func (_AcctManager *AcctManagerCaller) GetNumberOfAccounts(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "getNumberOfAccounts") - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getNumberOfAccounts") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. // -// Solidity: function getNumberOfAccounts() constant returns(uint256) +// Solidity: function getNumberOfAccounts() view returns(uint256) func (_AcctManager *AcctManagerSession) GetNumberOfAccounts() (*big.Int, error) { return _AcctManager.Contract.GetNumberOfAccounts(&_AcctManager.CallOpts) } // GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. // -// Solidity: function getNumberOfAccounts() constant returns(uint256) +// Solidity: function getNumberOfAccounts() view returns(uint256) func (_AcctManager *AcctManagerCallerSession) GetNumberOfAccounts() (*big.Int, error) { return _AcctManager.Contract.GetNumberOfAccounts(&_AcctManager.CallOpts) } // OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. // -// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +// Solidity: function orgAdminExists(string _orgId) view returns(bool) func (_AcctManager *AcctManagerCaller) OrgAdminExists(opts *bind.CallOpts, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "orgAdminExists", _orgId) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "orgAdminExists", _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. // -// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +// Solidity: function orgAdminExists(string _orgId) view returns(bool) func (_AcctManager *AcctManagerSession) OrgAdminExists(_orgId string) (bool, error) { return _AcctManager.Contract.OrgAdminExists(&_AcctManager.CallOpts, _orgId) } // OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. // -// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +// Solidity: function orgAdminExists(string _orgId) view returns(bool) func (_AcctManager *AcctManagerCallerSession) OrgAdminExists(_orgId string) (bool, error) { return _AcctManager.Contract.OrgAdminExists(&_AcctManager.CallOpts, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_AcctManager *AcctManagerCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "validateAccount", _account, _orgId) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "validateAccount", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_AcctManager *AcctManagerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _AcctManager.Contract.ValidateAccount(&_AcctManager.CallOpts, _account, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_AcctManager *AcctManagerCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _AcctManager.Contract.ValidateAccount(&_AcctManager.CallOpts, _account, _orgId) } diff --git a/permission/v1/bind/nodes.go b/permission/v1/bind/nodes.go index 11e607eab1..b06861db5c 100644 --- a/permission/v1/bind/nodes.go +++ b/permission/v1/bind/nodes.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindNodeManager(address common.Address, caller bind.ContractCaller, transac // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_NodeManager *NodeManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_NodeManager *NodeManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _NodeManager.Contract.NodeManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_NodeManager *NodeManagerRaw) Transact(opts *bind.TransactOpts, method str // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_NodeManager *NodeManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_NodeManager *NodeManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _NodeManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,25 +192,32 @@ func (_NodeManager *NodeManagerTransactorRaw) Transact(opts *bind.TransactOpts, // GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. // -// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +// Solidity: function getNodeDetails(string enodeId) view returns(string _orgId, string _enodeId, uint256 _nodeStatus) func (_NodeManager *NodeManagerCaller) GetNodeDetails(opts *bind.CallOpts, enodeId string) (struct { OrgId string EnodeId string NodeStatus *big.Int }, error) { - ret := new(struct { + var out []interface{} + err := _NodeManager.contract.Call(opts, &out, "getNodeDetails", enodeId) + + outstruct := new(struct { OrgId string EnodeId string NodeStatus *big.Int }) - out := ret - err := _NodeManager.contract.Call(opts, out, "getNodeDetails", enodeId) - return *ret, err + + outstruct.OrgId = out[0].(string) + outstruct.EnodeId = out[1].(string) + outstruct.NodeStatus = out[2].(*big.Int) + + return *outstruct, err + } // GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. // -// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +// Solidity: function getNodeDetails(string enodeId) view returns(string _orgId, string _enodeId, uint256 _nodeStatus) func (_NodeManager *NodeManagerSession) GetNodeDetails(enodeId string) (struct { OrgId string EnodeId string @@ -223,7 +228,7 @@ func (_NodeManager *NodeManagerSession) GetNodeDetails(enodeId string) (struct { // GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. // -// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +// Solidity: function getNodeDetails(string enodeId) view returns(string _orgId, string _enodeId, uint256 _nodeStatus) func (_NodeManager *NodeManagerCallerSession) GetNodeDetails(enodeId string) (struct { OrgId string EnodeId string @@ -234,25 +239,32 @@ func (_NodeManager *NodeManagerCallerSession) GetNodeDetails(enodeId string) (st // GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. // -// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) view returns(string _orgId, string _enodeId, uint256 _nodeStatus) func (_NodeManager *NodeManagerCaller) GetNodeDetailsFromIndex(opts *bind.CallOpts, _nodeIndex *big.Int) (struct { OrgId string EnodeId string NodeStatus *big.Int }, error) { - ret := new(struct { + var out []interface{} + err := _NodeManager.contract.Call(opts, &out, "getNodeDetailsFromIndex", _nodeIndex) + + outstruct := new(struct { OrgId string EnodeId string NodeStatus *big.Int }) - out := ret - err := _NodeManager.contract.Call(opts, out, "getNodeDetailsFromIndex", _nodeIndex) - return *ret, err + + outstruct.OrgId = out[0].(string) + outstruct.EnodeId = out[1].(string) + outstruct.NodeStatus = out[2].(*big.Int) + + return *outstruct, err + } // GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. // -// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) view returns(string _orgId, string _enodeId, uint256 _nodeStatus) func (_NodeManager *NodeManagerSession) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (struct { OrgId string EnodeId string @@ -263,7 +275,7 @@ func (_NodeManager *NodeManagerSession) GetNodeDetailsFromIndex(_nodeIndex *big. // GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. // -// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, uint256 _nodeStatus) +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) view returns(string _orgId, string _enodeId, uint256 _nodeStatus) func (_NodeManager *NodeManagerCallerSession) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (struct { OrgId string EnodeId string @@ -274,26 +286,31 @@ func (_NodeManager *NodeManagerCallerSession) GetNodeDetailsFromIndex(_nodeIndex // GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. // -// Solidity: function getNumberOfNodes() constant returns(uint256) +// Solidity: function getNumberOfNodes() view returns(uint256) func (_NodeManager *NodeManagerCaller) GetNumberOfNodes(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _NodeManager.contract.Call(opts, out, "getNumberOfNodes") - return *ret0, err + var out []interface{} + err := _NodeManager.contract.Call(opts, &out, "getNumberOfNodes") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. // -// Solidity: function getNumberOfNodes() constant returns(uint256) +// Solidity: function getNumberOfNodes() view returns(uint256) func (_NodeManager *NodeManagerSession) GetNumberOfNodes() (*big.Int, error) { return _NodeManager.Contract.GetNumberOfNodes(&_NodeManager.CallOpts) } // GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. // -// Solidity: function getNumberOfNodes() constant returns(uint256) +// Solidity: function getNumberOfNodes() view returns(uint256) func (_NodeManager *NodeManagerCallerSession) GetNumberOfNodes() (*big.Int, error) { return _NodeManager.Contract.GetNumberOfNodes(&_NodeManager.CallOpts) } diff --git a/permission/v1/bind/org.go b/permission/v1/bind/org.go index 3e84b06990..d6d115866a 100644 --- a/permission/v1/bind/org.go +++ b/permission/v1/bind/org.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindOrgManager(address common.Address, caller bind.ContractCaller, transact // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_OrgManager *OrgManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_OrgManager *OrgManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _OrgManager.Contract.OrgManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_OrgManager *OrgManagerRaw) Transact(opts *bind.TransactOpts, method strin // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_OrgManager *OrgManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_OrgManager *OrgManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _OrgManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,202 +192,225 @@ func (_OrgManager *OrgManagerTransactorRaw) Transact(opts *bind.TransactOpts, me // CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. // -// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +// Solidity: function checkOrgExists(string _orgId) view returns(bool) func (_OrgManager *OrgManagerCaller) CheckOrgExists(opts *bind.CallOpts, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "checkOrgExists", _orgId) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "checkOrgExists", _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. // -// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +// Solidity: function checkOrgExists(string _orgId) view returns(bool) func (_OrgManager *OrgManagerSession) CheckOrgExists(_orgId string) (bool, error) { return _OrgManager.Contract.CheckOrgExists(&_OrgManager.CallOpts, _orgId) } // CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. // -// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +// Solidity: function checkOrgExists(string _orgId) view returns(bool) func (_OrgManager *OrgManagerCallerSession) CheckOrgExists(_orgId string) (bool, error) { return _OrgManager.Contract.CheckOrgExists(&_OrgManager.CallOpts, _orgId) } // CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. // -// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) view returns(bool) func (_OrgManager *OrgManagerCaller) CheckOrgStatus(opts *bind.CallOpts, _orgId string, _orgStatus *big.Int) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "checkOrgStatus", _orgId, _orgStatus) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "checkOrgStatus", _orgId, _orgStatus) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. // -// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) view returns(bool) func (_OrgManager *OrgManagerSession) CheckOrgStatus(_orgId string, _orgStatus *big.Int) (bool, error) { return _OrgManager.Contract.CheckOrgStatus(&_OrgManager.CallOpts, _orgId, _orgStatus) } // CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. // -// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) view returns(bool) func (_OrgManager *OrgManagerCallerSession) CheckOrgStatus(_orgId string, _orgStatus *big.Int) (bool, error) { return _OrgManager.Contract.CheckOrgStatus(&_OrgManager.CallOpts, _orgId, _orgStatus) } // GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. // -// Solidity: function getNumberOfOrgs() constant returns(uint256) +// Solidity: function getNumberOfOrgs() view returns(uint256) func (_OrgManager *OrgManagerCaller) GetNumberOfOrgs(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "getNumberOfOrgs") - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getNumberOfOrgs") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. // -// Solidity: function getNumberOfOrgs() constant returns(uint256) +// Solidity: function getNumberOfOrgs() view returns(uint256) func (_OrgManager *OrgManagerSession) GetNumberOfOrgs() (*big.Int, error) { return _OrgManager.Contract.GetNumberOfOrgs(&_OrgManager.CallOpts) } // GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. // -// Solidity: function getNumberOfOrgs() constant returns(uint256) +// Solidity: function getNumberOfOrgs() view returns(uint256) func (_OrgManager *OrgManagerCallerSession) GetNumberOfOrgs() (*big.Int, error) { return _OrgManager.Contract.GetNumberOfOrgs(&_OrgManager.CallOpts) } // GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. // -// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgDetails(string _orgId) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerCaller) GetOrgDetails(opts *bind.CallOpts, _orgId string) (string, string, string, *big.Int, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(string) - ret3 = new(*big.Int) - ret4 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - ret4, + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getOrgDetails", _orgId) + + if err != nil { + return *new(string), *new(string), *new(string), *new(*big.Int), *new(*big.Int), err } - err := _OrgManager.contract.Call(opts, out, "getOrgDetails", _orgId) - return *ret0, *ret1, *ret2, *ret3, *ret4, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, out4, err + } // GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. // -// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgDetails(string _orgId) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerSession) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { return _OrgManager.Contract.GetOrgDetails(&_OrgManager.CallOpts, _orgId) } // GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. // -// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgDetails(string _orgId) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerCallerSession) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { return _OrgManager.Contract.GetOrgDetails(&_OrgManager.CallOpts, _orgId) } // GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. // -// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgInfo(uint256 _orgIndex) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerCaller) GetOrgInfo(opts *bind.CallOpts, _orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(string) - ret3 = new(*big.Int) - ret4 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - ret4, + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getOrgInfo", _orgIndex) + + if err != nil { + return *new(string), *new(string), *new(string), *new(*big.Int), *new(*big.Int), err } - err := _OrgManager.contract.Call(opts, out, "getOrgInfo", _orgIndex) - return *ret0, *ret1, *ret2, *ret3, *ret4, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, out4, err + } // GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. // -// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgInfo(uint256 _orgIndex) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerSession) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { return _OrgManager.Contract.GetOrgInfo(&_OrgManager.CallOpts, _orgIndex) } // GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. // -// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgInfo(uint256 _orgIndex) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerCallerSession) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { return _OrgManager.Contract.GetOrgInfo(&_OrgManager.CallOpts, _orgIndex) } // GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. // -// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +// Solidity: function getSubOrgIndexes(string _orgId) view returns(uint256[]) func (_OrgManager *OrgManagerCaller) GetSubOrgIndexes(opts *bind.CallOpts, _orgId string) ([]*big.Int, error) { - var ( - ret0 = new([]*big.Int) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "getSubOrgIndexes", _orgId) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getSubOrgIndexes", _orgId) + + if err != nil { + return *new([]*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new([]*big.Int)).(*[]*big.Int) + + return out0, err + } // GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. // -// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +// Solidity: function getSubOrgIndexes(string _orgId) view returns(uint256[]) func (_OrgManager *OrgManagerSession) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { return _OrgManager.Contract.GetSubOrgIndexes(&_OrgManager.CallOpts, _orgId) } // GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. // -// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +// Solidity: function getSubOrgIndexes(string _orgId) view returns(uint256[]) func (_OrgManager *OrgManagerCallerSession) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { return _OrgManager.Contract.GetSubOrgIndexes(&_OrgManager.CallOpts, _orgId) } // GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. // -// Solidity: function getUltimateParent(string _orgId) constant returns(string) +// Solidity: function getUltimateParent(string _orgId) view returns(string) func (_OrgManager *OrgManagerCaller) GetUltimateParent(opts *bind.CallOpts, _orgId string) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "getUltimateParent", _orgId) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getUltimateParent", _orgId) + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. // -// Solidity: function getUltimateParent(string _orgId) constant returns(string) +// Solidity: function getUltimateParent(string _orgId) view returns(string) func (_OrgManager *OrgManagerSession) GetUltimateParent(_orgId string) (string, error) { return _OrgManager.Contract.GetUltimateParent(&_OrgManager.CallOpts, _orgId) } // GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. // -// Solidity: function getUltimateParent(string _orgId) constant returns(string) +// Solidity: function getUltimateParent(string _orgId) view returns(string) func (_OrgManager *OrgManagerCallerSession) GetUltimateParent(_orgId string) (string, error) { return _OrgManager.Contract.GetUltimateParent(&_OrgManager.CallOpts, _orgId) } diff --git a/permission/v1/bind/permission_impl.go b/permission/v1/bind/permission_impl.go index 2f488be8f8..53df6517cf 100644 --- a/permission/v1/bind/permission_impl.go +++ b/permission/v1/bind/permission_impl.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindPermImpl(address common.Address, caller bind.ContractCaller, transactor // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermImpl *PermImplRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermImpl *PermImplRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermImpl.Contract.PermImplCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_PermImpl *PermImplRaw) Transact(opts *bind.TransactOpts, method string, p // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermImpl *PermImplCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermImpl *PermImplCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermImpl.Contract.contract.Call(opts, result, method, params...) } @@ -194,172 +192,192 @@ func (_PermImpl *PermImplTransactorRaw) Transact(opts *bind.TransactOpts, method // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermImpl *PermImplCaller) GetNetworkBootStatus(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "getNetworkBootStatus") - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "getNetworkBootStatus") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermImpl *PermImplSession) GetNetworkBootStatus() (bool, error) { return _PermImpl.Contract.GetNetworkBootStatus(&_PermImpl.CallOpts) } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermImpl *PermImplCallerSession) GetNetworkBootStatus() (bool, error) { return _PermImpl.Contract.GetNetworkBootStatus(&_PermImpl.CallOpts) } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermImpl *PermImplCaller) GetPendingOp(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(common.Address) - ret3 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "getPendingOp", _orgId) + + if err != nil { + return *new(string), *new(string), *new(common.Address), *new(*big.Int), err } - err := _PermImpl.contract.Call(opts, out, "getPendingOp", _orgId) - return *ret0, *ret1, *ret2, *ret3, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(common.Address)).(*common.Address) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, err + } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermImpl *PermImplSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { return _PermImpl.Contract.GetPendingOp(&_PermImpl.CallOpts, _orgId) } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermImpl *PermImplCallerSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { return _PermImpl.Contract.GetPendingOp(&_PermImpl.CallOpts, _orgId) } // GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. // -// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +// Solidity: function getPolicyDetails() view returns(string, string, string, bool) func (_PermImpl *PermImplCaller) GetPolicyDetails(opts *bind.CallOpts) (string, string, string, bool, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(string) - ret3 = new(bool) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "getPolicyDetails") + + if err != nil { + return *new(string), *new(string), *new(string), *new(bool), err } - err := _PermImpl.contract.Call(opts, out, "getPolicyDetails") - return *ret0, *ret1, *ret2, *ret3, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(bool)).(*bool) + + return out0, out1, out2, out3, err + } // GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. // -// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +// Solidity: function getPolicyDetails() view returns(string, string, string, bool) func (_PermImpl *PermImplSession) GetPolicyDetails() (string, string, string, bool, error) { return _PermImpl.Contract.GetPolicyDetails(&_PermImpl.CallOpts) } // GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. // -// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +// Solidity: function getPolicyDetails() view returns(string, string, string, bool) func (_PermImpl *PermImplCallerSession) GetPolicyDetails() (string, string, string, bool, error) { return _PermImpl.Contract.GetPolicyDetails(&_PermImpl.CallOpts) } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermImpl *PermImplCaller) IsNetworkAdmin(opts *bind.CallOpts, _account common.Address) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "isNetworkAdmin", _account) - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "isNetworkAdmin", _account) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermImpl *PermImplSession) IsNetworkAdmin(_account common.Address) (bool, error) { return _PermImpl.Contract.IsNetworkAdmin(&_PermImpl.CallOpts, _account) } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermImpl *PermImplCallerSession) IsNetworkAdmin(_account common.Address) (bool, error) { return _PermImpl.Contract.IsNetworkAdmin(&_PermImpl.CallOpts, _account) } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplCaller) IsOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "isOrgAdmin", _account, _orgId) - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "isOrgAdmin", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { return _PermImpl.Contract.IsOrgAdmin(&_PermImpl.CallOpts, _account, _orgId) } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplCallerSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { return _PermImpl.Contract.IsOrgAdmin(&_PermImpl.CallOpts, _account, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "validateAccount", _account, _orgId) - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "validateAccount", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _PermImpl.Contract.ValidateAccount(&_PermImpl.CallOpts, _account, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _PermImpl.Contract.ValidateAccount(&_PermImpl.CallOpts, _account, _orgId) } diff --git a/permission/v1/bind/permission_interface.go b/permission/v1/bind/permission_interface.go index 39468af8ef..a8da143baa 100644 --- a/permission/v1/bind/permission_interface.go +++ b/permission/v1/bind/permission_interface.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindPermInterface(address common.Address, caller bind.ContractCaller, trans // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermInterface *PermInterfaceRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermInterface *PermInterfaceRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermInterface.Contract.PermInterfaceCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_PermInterface *PermInterfaceRaw) Transact(opts *bind.TransactOpts, method // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermInterface *PermInterfaceCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermInterface *PermInterfaceCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermInterface.Contract.contract.Call(opts, result, method, params...) } @@ -194,164 +192,189 @@ func (_PermInterface *PermInterfaceTransactorRaw) Transact(opts *bind.TransactOp // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermInterface *PermInterfaceCaller) GetNetworkBootStatus(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "getNetworkBootStatus") - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "getNetworkBootStatus") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermInterface *PermInterfaceSession) GetNetworkBootStatus() (bool, error) { return _PermInterface.Contract.GetNetworkBootStatus(&_PermInterface.CallOpts) } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermInterface *PermInterfaceCallerSession) GetNetworkBootStatus() (bool, error) { return _PermInterface.Contract.GetNetworkBootStatus(&_PermInterface.CallOpts) } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermInterface *PermInterfaceCaller) GetPendingOp(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(common.Address) - ret3 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "getPendingOp", _orgId) + + if err != nil { + return *new(string), *new(string), *new(common.Address), *new(*big.Int), err } - err := _PermInterface.contract.Call(opts, out, "getPendingOp", _orgId) - return *ret0, *ret1, *ret2, *ret3, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(common.Address)).(*common.Address) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, err + } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermInterface *PermInterfaceSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { return _PermInterface.Contract.GetPendingOp(&_PermInterface.CallOpts, _orgId) } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermInterface *PermInterfaceCallerSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { return _PermInterface.Contract.GetPendingOp(&_PermInterface.CallOpts, _orgId) } // GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. // -// Solidity: function getPermissionsImpl() constant returns(address) +// Solidity: function getPermissionsImpl() view returns(address) func (_PermInterface *PermInterfaceCaller) GetPermissionsImpl(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "getPermissionsImpl") - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "getPermissionsImpl") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. // -// Solidity: function getPermissionsImpl() constant returns(address) +// Solidity: function getPermissionsImpl() view returns(address) func (_PermInterface *PermInterfaceSession) GetPermissionsImpl() (common.Address, error) { return _PermInterface.Contract.GetPermissionsImpl(&_PermInterface.CallOpts) } // GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. // -// Solidity: function getPermissionsImpl() constant returns(address) +// Solidity: function getPermissionsImpl() view returns(address) func (_PermInterface *PermInterfaceCallerSession) GetPermissionsImpl() (common.Address, error) { return _PermInterface.Contract.GetPermissionsImpl(&_PermInterface.CallOpts) } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermInterface *PermInterfaceCaller) IsNetworkAdmin(opts *bind.CallOpts, _account common.Address) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "isNetworkAdmin", _account) - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "isNetworkAdmin", _account) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermInterface *PermInterfaceSession) IsNetworkAdmin(_account common.Address) (bool, error) { return _PermInterface.Contract.IsNetworkAdmin(&_PermInterface.CallOpts, _account) } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermInterface *PermInterfaceCallerSession) IsNetworkAdmin(_account common.Address) (bool, error) { return _PermInterface.Contract.IsNetworkAdmin(&_PermInterface.CallOpts, _account) } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceCaller) IsOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "isOrgAdmin", _account, _orgId) - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "isOrgAdmin", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { return _PermInterface.Contract.IsOrgAdmin(&_PermInterface.CallOpts, _account, _orgId) } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceCallerSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { return _PermInterface.Contract.IsOrgAdmin(&_PermInterface.CallOpts, _account, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "validateAccount", _account, _orgId) - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "validateAccount", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _PermInterface.Contract.ValidateAccount(&_PermInterface.CallOpts, _account, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _PermInterface.Contract.ValidateAccount(&_PermInterface.CallOpts, _account, _orgId) } diff --git a/permission/v1/bind/permission_upgr.go b/permission/v1/bind/permission_upgr.go index 6f8de04912..f03d2298e5 100644 --- a/permission/v1/bind/permission_upgr.go +++ b/permission/v1/bind/permission_upgr.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -34,7 +32,7 @@ const PermUpgrABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"getPermImpl\", var PermUpgrParsedABI, _ = abi.JSON(strings.NewReader(PermUpgrABI)) // PermUpgrBin is the compiled bytecode used for deploying new contracts. -var PermUpgrBin = "0x608060405234801561001057600080fd5b506040516020806106e78339810180604052602081101561003057600080fd5b5051600080546001600160a01b039092166001600160a01b031990921691909117905560028054600160a01b60ff0219169055610675806100726000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630e32cf901461005c57806322bcb39a14610080578063a75b87d2146100a8578063e572515c146100b0578063f09a4016146100b8575b600080fd5b6100646100e6565b604080516001600160a01b039092168252519081900360200190f35b6100a66004803603602081101561009657600080fd5b50356001600160a01b03166100f5565b005b61006461030b565b61006461031a565b6100a6600480360360408110156100ce57600080fd5b506001600160a01b0381358116916020013516610329565b6001546001600160a01b031690565b6000546001600160a01b0316331461014b5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b60608060606000600160009054906101000a90046001600160a01b03166001600160a01b031663cc9ba6fa6040518163ffffffff1660e01b815260040160006040518083038186803b1580156101a057600080fd5b505afa1580156101b4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405260808110156101dd57600080fd5b8101908080516401000000008111156101f557600080fd5b8201602081018481111561020857600080fd5b815164010000000081118282018710171561022257600080fd5b5050929190602001805164010000000081111561023e57600080fd5b8201602081018481111561025157600080fd5b815164010000000081118282018710171561026b57600080fd5b5050929190602001805164010000000081111561028757600080fd5b8201602081018481111561029a57600080fd5b81516401000000008111828201871017156102b457600080fd5b50506020909101519498509296509194509192506102d9915086905085858585610443565b600180546001600160a01b0319166001600160a01b03878116919091179182905561030491166105e4565b5050505050565b6000546001600160a01b031690565b6002546001600160a01b031690565b6000546001600160a01b0316331461037f5760408051600160e51b62461bcd02815260206004820152600e6024820152600160911b6d34b73b30b634b21031b0b63632b902604482015290519081900360640190fd5b600254600160a01b900460ff16156103e15760408051600160e51b62461bcd02815260206004820152601960248201527f63616e206265206578656375746564206f6e6c79206f6e636500000000000000604482015290519081900360640190fd5b600180546001600160a01b038084166001600160a01b031992831617928390556002805486831693169290921790915561041b91166105e4565b50506002805474ff00000000000000000000000000000000000000001916600160a01b179055565b846001600160a01b031663f5ad584a858585856040518563ffffffff1660e01b81526004018080602001806020018060200185151515158152602001848103845288818151815260200191508051906020019080838360005b838110156104b457818101518382015260200161049c565b50505050905090810190601f1680156104e15780820380516001836020036101000a031916815260200191505b50848103835287518152875160209182019189019080838360005b838110156105145781810151838201526020016104fc565b50505050905090810190601f1680156105415780820380516001836020036101000a031916815260200191505b50848103825286518152865160209182019188019080838360005b8381101561057457818101518382015260200161055c565b50505050905090810190601f1680156105a15780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b1580156105c557600080fd5b505af11580156105d9573d6000803e3d6000fd5b505050505050505050565b60025460408051600160e01b63511bbd9f0281526001600160a01b0384811660048301529151919092169163511bbd9f91602480830192600092919082900301818387803b15801561063557600080fd5b505af1158015610304573d6000803e3d6000fdfea165627a7a72305820baed98682426c4ca5713954d4aec5ce8f78637bbf627bd8f53ff37aac2394a950029" +var PermUpgrBin = "0x608060405234801561001057600080fd5b50604051610bfa380380610bfa8339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260146101000a81548160ff02191690831515021790555050610b4b806100af6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630e32cf901461005c57806322bcb39a146100a6578063a75b87d2146100ea578063e572515c14610134578063f09a40161461017e575b600080fd5b6100646101e2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100e8600480360360208110156100bc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061020c565b005b6100f2610639565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61013c610662565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101e06004803603604081101561019457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061068c565b005b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146102ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f696e76616c69642063616c6c657200000000000000000000000000000000000081525060200191505060405180910390fd5b60608060606000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663cc9ba6fa6040518163ffffffff1660e01b815260040160006040518083038186803b15801561033d57600080fd5b505afa158015610351573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f82011682018060405250608081101561037b57600080fd5b810190808051604051939291908464010000000082111561039b57600080fd5b838201915060208201858111156103b157600080fd5b82518660018202830111640100000000821117156103ce57600080fd5b8083526020830192505050908051906020019080838360005b838110156104025780820151818401526020810190506103e7565b50505050905090810190601f16801561042f5780820380516001836020036101000a031916815260200191505b506040526020018051604051939291908464010000000082111561045257600080fd5b8382019150602082018581111561046857600080fd5b825186600182028301116401000000008211171561048557600080fd5b8083526020830192505050908051906020019080838360005b838110156104b957808201518184015260208101905061049e565b50505050905090810190601f1680156104e65780820380516001836020036101000a031916815260200191505b506040526020018051604051939291908464010000000082111561050957600080fd5b8382019150602082018581111561051f57600080fd5b825186600182028301116401000000008211171561053c57600080fd5b8083526020830192505050908051906020019080838360005b83811015610570578082015181840152602081019050610555565b50505050905090810190601f16801561059d5780820380516001836020036101000a031916815260200191505b506040526020018051906020019092919050505093509350935093506105c6858585858561089d565b84600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610632600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610a5a565b5050505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461074e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f696e76616c69642063616c6c657200000000000000000000000000000000000081525060200191505060405180910390fd5b600260149054906101000a900460ff16156107d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f63616e206265206578656375746564206f6e6c79206f6e63650000000000000081525060200191505060405180910390fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555081600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061087e600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610a5a565b6001600260146101000a81548160ff0219169083151502179055505050565b8473ffffffffffffffffffffffffffffffffffffffff1663f5ad584a858585856040518563ffffffff1660e01b81526004018080602001806020018060200185151515158152602001848103845288818151815260200191508051906020019080838360005b8381101561091e578082015181840152602081019050610903565b50505050905090810190601f16801561094b5780820380516001836020036101000a031916815260200191505b50848103835287818151815260200191508051906020019080838360005b83811015610984578082015181840152602081019050610969565b50505050905090810190601f1680156109b15780820380516001836020036101000a031916815260200191505b50848103825286818151815260200191508051906020019080838360005b838110156109ea5780820151818401526020810190506109cf565b50505050905090810190601f168015610a175780820380516001836020036101000a031916815260200191505b50975050505050505050600060405180830381600087803b158015610a3b57600080fd5b505af1158015610a4f573d6000803e3d6000fd5b505050505050505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663511bbd9f826040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b158015610afb57600080fd5b505af1158015610b0f573d6000803e3d6000fd5b505050505056fea265627a7a72315820316b667b04f5e53cd6b4085849e4dd3299ffca85b1af531372c992cf4c25dde464736f6c63430005110032" // DeployPermUpgr deploys a new Ethereum contract, binding an instance of PermUpgr to it. func DeployPermUpgr(auth *bind.TransactOpts, backend bind.ContractBackend, _guardian common.Address) (common.Address, *types.Transaction, *PermUpgr, error) { @@ -158,7 +156,7 @@ func bindPermUpgr(address common.Address, caller bind.ContractCaller, transactor // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermUpgr *PermUpgrRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermUpgr *PermUpgrRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermUpgr.Contract.PermUpgrCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_PermUpgr *PermUpgrRaw) Transact(opts *bind.TransactOpts, method string, p // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermUpgr *PermUpgrCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermUpgr *PermUpgrCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermUpgr.Contract.contract.Call(opts, result, method, params...) } @@ -194,78 +192,93 @@ func (_PermUpgr *PermUpgrTransactorRaw) Transact(opts *bind.TransactOpts, method // GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. // -// Solidity: function getGuardian() constant returns(address) +// Solidity: function getGuardian() view returns(address) func (_PermUpgr *PermUpgrCaller) GetGuardian(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PermUpgr.contract.Call(opts, out, "getGuardian") - return *ret0, err + var out []interface{} + err := _PermUpgr.contract.Call(opts, &out, "getGuardian") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. // -// Solidity: function getGuardian() constant returns(address) +// Solidity: function getGuardian() view returns(address) func (_PermUpgr *PermUpgrSession) GetGuardian() (common.Address, error) { return _PermUpgr.Contract.GetGuardian(&_PermUpgr.CallOpts) } // GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. // -// Solidity: function getGuardian() constant returns(address) +// Solidity: function getGuardian() view returns(address) func (_PermUpgr *PermUpgrCallerSession) GetGuardian() (common.Address, error) { return _PermUpgr.Contract.GetGuardian(&_PermUpgr.CallOpts) } // GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. // -// Solidity: function getPermImpl() constant returns(address) +// Solidity: function getPermImpl() view returns(address) func (_PermUpgr *PermUpgrCaller) GetPermImpl(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PermUpgr.contract.Call(opts, out, "getPermImpl") - return *ret0, err + var out []interface{} + err := _PermUpgr.contract.Call(opts, &out, "getPermImpl") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. // -// Solidity: function getPermImpl() constant returns(address) +// Solidity: function getPermImpl() view returns(address) func (_PermUpgr *PermUpgrSession) GetPermImpl() (common.Address, error) { return _PermUpgr.Contract.GetPermImpl(&_PermUpgr.CallOpts) } // GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. // -// Solidity: function getPermImpl() constant returns(address) +// Solidity: function getPermImpl() view returns(address) func (_PermUpgr *PermUpgrCallerSession) GetPermImpl() (common.Address, error) { return _PermUpgr.Contract.GetPermImpl(&_PermUpgr.CallOpts) } // GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. // -// Solidity: function getPermInterface() constant returns(address) +// Solidity: function getPermInterface() view returns(address) func (_PermUpgr *PermUpgrCaller) GetPermInterface(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PermUpgr.contract.Call(opts, out, "getPermInterface") - return *ret0, err + var out []interface{} + err := _PermUpgr.contract.Call(opts, &out, "getPermInterface") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. // -// Solidity: function getPermInterface() constant returns(address) +// Solidity: function getPermInterface() view returns(address) func (_PermUpgr *PermUpgrSession) GetPermInterface() (common.Address, error) { return _PermUpgr.Contract.GetPermInterface(&_PermUpgr.CallOpts) } // GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. // -// Solidity: function getPermInterface() constant returns(address) +// Solidity: function getPermInterface() view returns(address) func (_PermUpgr *PermUpgrCallerSession) GetPermInterface() (common.Address, error) { return _PermUpgr.Contract.GetPermInterface(&_PermUpgr.CallOpts) } diff --git a/permission/v1/bind/roles.go b/permission/v1/bind/roles.go index 1e05bbe3e7..5e8fa5b647 100644 --- a/permission/v1/bind/roles.go +++ b/permission/v1/bind/roles.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindRoleManager(address common.Address, caller bind.ContractCaller, transac // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_RoleManager *RoleManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_RoleManager *RoleManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _RoleManager.Contract.RoleManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_RoleManager *RoleManagerRaw) Transact(opts *bind.TransactOpts, method str // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_RoleManager *RoleManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_RoleManager *RoleManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _RoleManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,33 +192,38 @@ func (_RoleManager *RoleManagerTransactorRaw) Transact(opts *bind.TransactOpts, // GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. // -// Solidity: function getNumberOfRoles() constant returns(uint256) +// Solidity: function getNumberOfRoles() view returns(uint256) func (_RoleManager *RoleManagerCaller) GetNumberOfRoles(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "getNumberOfRoles") - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "getNumberOfRoles") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. // -// Solidity: function getNumberOfRoles() constant returns(uint256) +// Solidity: function getNumberOfRoles() view returns(uint256) func (_RoleManager *RoleManagerSession) GetNumberOfRoles() (*big.Int, error) { return _RoleManager.Contract.GetNumberOfRoles(&_RoleManager.CallOpts) } // GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. // -// Solidity: function getNumberOfRoles() constant returns(uint256) +// Solidity: function getNumberOfRoles() view returns(uint256) func (_RoleManager *RoleManagerCallerSession) GetNumberOfRoles() (*big.Int, error) { return _RoleManager.Contract.GetNumberOfRoles(&_RoleManager.CallOpts) } // GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. // -// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetails(string _roleId, string _orgId) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerCaller) GetRoleDetails(opts *bind.CallOpts, _roleId string, _orgId string) (struct { RoleId string OrgId string @@ -229,7 +232,10 @@ func (_RoleManager *RoleManagerCaller) GetRoleDetails(opts *bind.CallOpts, _role Admin bool Active bool }, error) { - ret := new(struct { + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "getRoleDetails", _roleId, _orgId) + + outstruct := new(struct { RoleId string OrgId string AccessType *big.Int @@ -237,14 +243,21 @@ func (_RoleManager *RoleManagerCaller) GetRoleDetails(opts *bind.CallOpts, _role Admin bool Active bool }) - out := ret - err := _RoleManager.contract.Call(opts, out, "getRoleDetails", _roleId, _orgId) - return *ret, err + + outstruct.RoleId = out[0].(string) + outstruct.OrgId = out[1].(string) + outstruct.AccessType = out[2].(*big.Int) + outstruct.Voter = out[3].(bool) + outstruct.Admin = out[4].(bool) + outstruct.Active = out[5].(bool) + + return *outstruct, err + } // GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. // -// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetails(string _roleId, string _orgId) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerSession) GetRoleDetails(_roleId string, _orgId string) (struct { RoleId string OrgId string @@ -258,7 +271,7 @@ func (_RoleManager *RoleManagerSession) GetRoleDetails(_roleId string, _orgId st // GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. // -// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetails(string _roleId, string _orgId) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerCallerSession) GetRoleDetails(_roleId string, _orgId string) (struct { RoleId string OrgId string @@ -272,7 +285,7 @@ func (_RoleManager *RoleManagerCallerSession) GetRoleDetails(_roleId string, _or // GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. // -// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerCaller) GetRoleDetailsFromIndex(opts *bind.CallOpts, _rIndex *big.Int) (struct { RoleId string OrgId string @@ -281,7 +294,10 @@ func (_RoleManager *RoleManagerCaller) GetRoleDetailsFromIndex(opts *bind.CallOp Admin bool Active bool }, error) { - ret := new(struct { + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "getRoleDetailsFromIndex", _rIndex) + + outstruct := new(struct { RoleId string OrgId string AccessType *big.Int @@ -289,14 +305,21 @@ func (_RoleManager *RoleManagerCaller) GetRoleDetailsFromIndex(opts *bind.CallOp Admin bool Active bool }) - out := ret - err := _RoleManager.contract.Call(opts, out, "getRoleDetailsFromIndex", _rIndex) - return *ret, err + + outstruct.RoleId = out[0].(string) + outstruct.OrgId = out[1].(string) + outstruct.AccessType = out[2].(*big.Int) + outstruct.Voter = out[3].(bool) + outstruct.Admin = out[4].(bool) + outstruct.Active = out[5].(bool) + + return *outstruct, err + } // GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. // -// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerSession) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { RoleId string OrgId string @@ -310,7 +333,7 @@ func (_RoleManager *RoleManagerSession) GetRoleDetailsFromIndex(_rIndex *big.Int // GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. // -// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerCallerSession) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { RoleId string OrgId string @@ -324,78 +347,93 @@ func (_RoleManager *RoleManagerCallerSession) GetRoleDetailsFromIndex(_rIndex *b // IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. // -// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCaller) IsAdminRole(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "isAdminRole", _roleId, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "isAdminRole", _roleId, _orgId, _ultParent) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. // -// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerSession) IsAdminRole(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.IsAdminRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. // -// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCallerSession) IsAdminRole(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.IsAdminRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. // -// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCaller) IsVoterRole(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "isVoterRole", _roleId, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "isVoterRole", _roleId, _orgId, _ultParent) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. // -// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerSession) IsVoterRole(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.IsVoterRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. // -// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCallerSession) IsVoterRole(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.IsVoterRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // RoleExists is a free data retrieval call binding the contract method 0xabf5739f. // -// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCaller) RoleExists(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "roleExists", _roleId, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "roleExists", _roleId, _orgId, _ultParent) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // RoleExists is a free data retrieval call binding the contract method 0xabf5739f. // -// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerSession) RoleExists(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.RoleExists(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // RoleExists is a free data retrieval call binding the contract method 0xabf5739f. // -// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCallerSession) RoleExists(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.RoleExists(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } diff --git a/permission/v1/bind/voter.go b/permission/v1/bind/voter.go index 8c5835206d..3c31bfb46b 100644 --- a/permission/v1/bind/voter.go +++ b/permission/v1/bind/voter.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindVoterManager(address common.Address, caller bind.ContractCaller, transa // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VoterManager *VoterManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VoterManager *VoterManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VoterManager.Contract.VoterManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_VoterManager *VoterManagerRaw) Transact(opts *bind.TransactOpts, method s // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VoterManager *VoterManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VoterManager *VoterManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VoterManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,34 +192,34 @@ func (_VoterManager *VoterManagerTransactorRaw) Transact(opts *bind.TransactOpts // GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. // -// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOpDetails(string _orgId) view returns(string, string, address, uint256) func (_VoterManager *VoterManagerCaller) GetPendingOpDetails(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(common.Address) - ret3 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - } - err := _VoterManager.contract.Call(opts, out, "getPendingOpDetails", _orgId) - return *ret0, *ret1, *ret2, *ret3, err + var out []interface{} + err := _VoterManager.contract.Call(opts, &out, "getPendingOpDetails", _orgId) + + if err != nil { + return *new(string), *new(string), *new(common.Address), *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(common.Address)).(*common.Address) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, err + } // GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. // -// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOpDetails(string _orgId) view returns(string, string, address, uint256) func (_VoterManager *VoterManagerSession) GetPendingOpDetails(_orgId string) (string, string, common.Address, *big.Int, error) { return _VoterManager.Contract.GetPendingOpDetails(&_VoterManager.CallOpts, _orgId) } // GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. // -// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOpDetails(string _orgId) view returns(string, string, address, uint256) func (_VoterManager *VoterManagerCallerSession) GetPendingOpDetails(_orgId string) (string, string, common.Address, *big.Int, error) { return _VoterManager.Contract.GetPendingOpDetails(&_VoterManager.CallOpts, _orgId) } diff --git a/permission/v2/bind/accounts.go b/permission/v2/bind/accounts.go index 8af01882c2..4c572d2d18 100644 --- a/permission/v2/bind/accounts.go +++ b/permission/v2/bind/accounts.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindAcctManager(address common.Address, caller bind.ContractCaller, transac // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_AcctManager *AcctManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_AcctManager *AcctManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _AcctManager.Contract.AcctManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_AcctManager *AcctManagerRaw) Transact(opts *bind.TransactOpts, method str // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_AcctManager *AcctManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_AcctManager *AcctManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _AcctManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,258 +192,288 @@ func (_AcctManager *AcctManagerTransactorRaw) Transact(opts *bind.TransactOpts, // CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. // -// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) view returns(bool) func (_AcctManager *AcctManagerCaller) CheckOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string, _ultParent string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "checkOrgAdmin", _account, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "checkOrgAdmin", _account, _orgId, _ultParent) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. // -// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) view returns(bool) func (_AcctManager *AcctManagerSession) CheckOrgAdmin(_account common.Address, _orgId string, _ultParent string) (bool, error) { return _AcctManager.Contract.CheckOrgAdmin(&_AcctManager.CallOpts, _account, _orgId, _ultParent) } // CheckOrgAdmin is a free data retrieval call binding the contract method 0xe8b42bf4. // -// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function checkOrgAdmin(address _account, string _orgId, string _ultParent) view returns(bool) func (_AcctManager *AcctManagerCallerSession) CheckOrgAdmin(_account common.Address, _orgId string, _ultParent string) (bool, error) { return _AcctManager.Contract.CheckOrgAdmin(&_AcctManager.CallOpts, _account, _orgId, _ultParent) } // GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. // -// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetails(address _account) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerCaller) GetAccountDetails(opts *bind.CallOpts, _account common.Address) (common.Address, string, string, *big.Int, bool, error) { - var ( - ret0 = new(common.Address) - ret1 = new(string) - ret2 = new(string) - ret3 = new(*big.Int) - ret4 = new(bool) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - ret4, - } - err := _AcctManager.contract.Call(opts, out, "getAccountDetails", _account) - return *ret0, *ret1, *ret2, *ret3, *ret4, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getAccountDetails", _account) + + if err != nil { + return *new(common.Address), *new(string), *new(string), *new(*big.Int), *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(bool)).(*bool) + + return out0, out1, out2, out3, out4, err + } // GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. // -// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetails(address _account) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerSession) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { return _AcctManager.Contract.GetAccountDetails(&_AcctManager.CallOpts, _account) } // GetAccountDetails is a free data retrieval call binding the contract method 0x2aceb534. // -// Solidity: function getAccountDetails(address _account) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetails(address _account) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerCallerSession) GetAccountDetails(_account common.Address) (common.Address, string, string, *big.Int, bool, error) { return _AcctManager.Contract.GetAccountDetails(&_AcctManager.CallOpts, _account) } // GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. // -// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerCaller) GetAccountDetailsFromIndex(opts *bind.CallOpts, _aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { - var ( - ret0 = new(common.Address) - ret1 = new(string) - ret2 = new(string) - ret3 = new(*big.Int) - ret4 = new(bool) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - ret4, - } - err := _AcctManager.contract.Call(opts, out, "getAccountDetailsFromIndex", _aIndex) - return *ret0, *ret1, *ret2, *ret3, *ret4, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getAccountDetailsFromIndex", _aIndex) + + if err != nil { + return *new(common.Address), *new(string), *new(string), *new(*big.Int), *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(bool)).(*bool) + + return out0, out1, out2, out3, out4, err + } // GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. // -// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerSession) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { return _AcctManager.Contract.GetAccountDetailsFromIndex(&_AcctManager.CallOpts, _aIndex) } // GetAccountDetailsFromIndex is a free data retrieval call binding the contract method 0xb2018568. // -// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) constant returns(address, string, string, uint256, bool) +// Solidity: function getAccountDetailsFromIndex(uint256 _aIndex) view returns(address, string, string, uint256, bool) func (_AcctManager *AcctManagerCallerSession) GetAccountDetailsFromIndex(_aIndex *big.Int) (common.Address, string, string, *big.Int, bool, error) { return _AcctManager.Contract.GetAccountDetailsFromIndex(&_AcctManager.CallOpts, _aIndex) } // GetAccountOrgRole is a free data retrieval call binding the contract method 0x6acee5fd. // -// Solidity: function getAccountOrgRole(address _account) constant returns(string, string) +// Solidity: function getAccountOrgRole(address _account) view returns(string, string) func (_AcctManager *AcctManagerCaller) GetAccountOrgRole(opts *bind.CallOpts, _account common.Address) (string, string, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ) - out := &[]interface{}{ - ret0, - ret1, + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getAccountOrgRole", _account) + + if err != nil { + return *new(string), *new(string), err } - err := _AcctManager.contract.Call(opts, out, "getAccountOrgRole", _account) - return *ret0, *ret1, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + + return out0, out1, err + } // GetAccountOrgRole is a free data retrieval call binding the contract method 0x6acee5fd. // -// Solidity: function getAccountOrgRole(address _account) constant returns(string, string) +// Solidity: function getAccountOrgRole(address _account) view returns(string, string) func (_AcctManager *AcctManagerSession) GetAccountOrgRole(_account common.Address) (string, string, error) { return _AcctManager.Contract.GetAccountOrgRole(&_AcctManager.CallOpts, _account) } // GetAccountOrgRole is a free data retrieval call binding the contract method 0x6acee5fd. // -// Solidity: function getAccountOrgRole(address _account) constant returns(string, string) +// Solidity: function getAccountOrgRole(address _account) view returns(string, string) func (_AcctManager *AcctManagerCallerSession) GetAccountOrgRole(_account common.Address) (string, string, error) { return _AcctManager.Contract.GetAccountOrgRole(&_AcctManager.CallOpts, _account) } // GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. // -// Solidity: function getAccountRole(address _account) constant returns(string) +// Solidity: function getAccountRole(address _account) view returns(string) func (_AcctManager *AcctManagerCaller) GetAccountRole(opts *bind.CallOpts, _account common.Address) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "getAccountRole", _account) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getAccountRole", _account) + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. // -// Solidity: function getAccountRole(address _account) constant returns(string) +// Solidity: function getAccountRole(address _account) view returns(string) func (_AcctManager *AcctManagerSession) GetAccountRole(_account common.Address) (string, error) { return _AcctManager.Contract.GetAccountRole(&_AcctManager.CallOpts, _account) } // GetAccountRole is a free data retrieval call binding the contract method 0x81d66b23. // -// Solidity: function getAccountRole(address _account) constant returns(string) +// Solidity: function getAccountRole(address _account) view returns(string) func (_AcctManager *AcctManagerCallerSession) GetAccountRole(_account common.Address) (string, error) { return _AcctManager.Contract.GetAccountRole(&_AcctManager.CallOpts, _account) } // GetAccountStatus is a free data retrieval call binding the contract method 0xfd4fa05a. // -// Solidity: function getAccountStatus(address _account) constant returns(uint256) +// Solidity: function getAccountStatus(address _account) view returns(uint256) func (_AcctManager *AcctManagerCaller) GetAccountStatus(opts *bind.CallOpts, _account common.Address) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "getAccountStatus", _account) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getAccountStatus", _account) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetAccountStatus is a free data retrieval call binding the contract method 0xfd4fa05a. // -// Solidity: function getAccountStatus(address _account) constant returns(uint256) +// Solidity: function getAccountStatus(address _account) view returns(uint256) func (_AcctManager *AcctManagerSession) GetAccountStatus(_account common.Address) (*big.Int, error) { return _AcctManager.Contract.GetAccountStatus(&_AcctManager.CallOpts, _account) } // GetAccountStatus is a free data retrieval call binding the contract method 0xfd4fa05a. // -// Solidity: function getAccountStatus(address _account) constant returns(uint256) +// Solidity: function getAccountStatus(address _account) view returns(uint256) func (_AcctManager *AcctManagerCallerSession) GetAccountStatus(_account common.Address) (*big.Int, error) { return _AcctManager.Contract.GetAccountStatus(&_AcctManager.CallOpts, _account) } // GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. // -// Solidity: function getNumberOfAccounts() constant returns(uint256) +// Solidity: function getNumberOfAccounts() view returns(uint256) func (_AcctManager *AcctManagerCaller) GetNumberOfAccounts(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "getNumberOfAccounts") - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "getNumberOfAccounts") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. // -// Solidity: function getNumberOfAccounts() constant returns(uint256) +// Solidity: function getNumberOfAccounts() view returns(uint256) func (_AcctManager *AcctManagerSession) GetNumberOfAccounts() (*big.Int, error) { return _AcctManager.Contract.GetNumberOfAccounts(&_AcctManager.CallOpts) } // GetNumberOfAccounts is a free data retrieval call binding the contract method 0x309e36ef. // -// Solidity: function getNumberOfAccounts() constant returns(uint256) +// Solidity: function getNumberOfAccounts() view returns(uint256) func (_AcctManager *AcctManagerCallerSession) GetNumberOfAccounts() (*big.Int, error) { return _AcctManager.Contract.GetNumberOfAccounts(&_AcctManager.CallOpts) } // OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. // -// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +// Solidity: function orgAdminExists(string _orgId) view returns(bool) func (_AcctManager *AcctManagerCaller) OrgAdminExists(opts *bind.CallOpts, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "orgAdminExists", _orgId) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "orgAdminExists", _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. // -// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +// Solidity: function orgAdminExists(string _orgId) view returns(bool) func (_AcctManager *AcctManagerSession) OrgAdminExists(_orgId string) (bool, error) { return _AcctManager.Contract.OrgAdminExists(&_AcctManager.CallOpts, _orgId) } // OrgAdminExists is a free data retrieval call binding the contract method 0x950145cf. // -// Solidity: function orgAdminExists(string _orgId) constant returns(bool) +// Solidity: function orgAdminExists(string _orgId) view returns(bool) func (_AcctManager *AcctManagerCallerSession) OrgAdminExists(_orgId string) (bool, error) { return _AcctManager.Contract.OrgAdminExists(&_AcctManager.CallOpts, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_AcctManager *AcctManagerCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _AcctManager.contract.Call(opts, out, "validateAccount", _account, _orgId) - return *ret0, err + var out []interface{} + err := _AcctManager.contract.Call(opts, &out, "validateAccount", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_AcctManager *AcctManagerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _AcctManager.Contract.ValidateAccount(&_AcctManager.CallOpts, _account, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_AcctManager *AcctManagerCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _AcctManager.Contract.ValidateAccount(&_AcctManager.CallOpts, _account, _orgId) } diff --git a/permission/v2/bind/nodes.go b/permission/v2/bind/nodes.go index c9184b295b..d48e39f515 100644 --- a/permission/v2/bind/nodes.go +++ b/permission/v2/bind/nodes.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindNodeManager(address common.Address, caller bind.ContractCaller, transac // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_NodeManager *NodeManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_NodeManager *NodeManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _NodeManager.Contract.NodeManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_NodeManager *NodeManagerRaw) Transact(opts *bind.TransactOpts, method str // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_NodeManager *NodeManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_NodeManager *NodeManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _NodeManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,33 +192,38 @@ func (_NodeManager *NodeManagerTransactorRaw) Transact(opts *bind.TransactOpts, // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_NodeManager *NodeManagerCaller) ConnectionAllowed(opts *bind.CallOpts, _enodeId string, _ip string, _port uint16) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _NodeManager.contract.Call(opts, out, "connectionAllowed", _enodeId, _ip, _port) - return *ret0, err + var out []interface{} + err := _NodeManager.contract.Call(opts, &out, "connectionAllowed", _enodeId, _ip, _port) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_NodeManager *NodeManagerSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { return _NodeManager.Contract.ConnectionAllowed(&_NodeManager.CallOpts, _enodeId, _ip, _port) } // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_NodeManager *NodeManagerCallerSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { return _NodeManager.Contract.ConnectionAllowed(&_NodeManager.CallOpts, _enodeId, _ip, _port) } // GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. // -// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +// Solidity: function getNodeDetails(string enodeId) view returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) func (_NodeManager *NodeManagerCaller) GetNodeDetails(opts *bind.CallOpts, enodeId string) (struct { OrgId string EnodeId string @@ -229,7 +232,10 @@ func (_NodeManager *NodeManagerCaller) GetNodeDetails(opts *bind.CallOpts, enode Raftport uint16 NodeStatus *big.Int }, error) { - ret := new(struct { + var out []interface{} + err := _NodeManager.contract.Call(opts, &out, "getNodeDetails", enodeId) + + outstruct := new(struct { OrgId string EnodeId string Ip string @@ -237,14 +243,21 @@ func (_NodeManager *NodeManagerCaller) GetNodeDetails(opts *bind.CallOpts, enode Raftport uint16 NodeStatus *big.Int }) - out := ret - err := _NodeManager.contract.Call(opts, out, "getNodeDetails", enodeId) - return *ret, err + + outstruct.OrgId = out[0].(string) + outstruct.EnodeId = out[1].(string) + outstruct.Ip = out[2].(string) + outstruct.Port = out[3].(uint16) + outstruct.Raftport = out[4].(uint16) + outstruct.NodeStatus = out[5].(*big.Int) + + return *outstruct, err + } // GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. // -// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +// Solidity: function getNodeDetails(string enodeId) view returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) func (_NodeManager *NodeManagerSession) GetNodeDetails(enodeId string) (struct { OrgId string EnodeId string @@ -258,7 +271,7 @@ func (_NodeManager *NodeManagerSession) GetNodeDetails(enodeId string) (struct { // GetNodeDetails is a free data retrieval call binding the contract method 0x3f0e0e47. // -// Solidity: function getNodeDetails(string enodeId) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +// Solidity: function getNodeDetails(string enodeId) view returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) func (_NodeManager *NodeManagerCallerSession) GetNodeDetails(enodeId string) (struct { OrgId string EnodeId string @@ -272,7 +285,7 @@ func (_NodeManager *NodeManagerCallerSession) GetNodeDetails(enodeId string) (st // GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. // -// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) view returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) func (_NodeManager *NodeManagerCaller) GetNodeDetailsFromIndex(opts *bind.CallOpts, _nodeIndex *big.Int) (struct { OrgId string EnodeId string @@ -281,7 +294,10 @@ func (_NodeManager *NodeManagerCaller) GetNodeDetailsFromIndex(opts *bind.CallOp Raftport uint16 NodeStatus *big.Int }, error) { - ret := new(struct { + var out []interface{} + err := _NodeManager.contract.Call(opts, &out, "getNodeDetailsFromIndex", _nodeIndex) + + outstruct := new(struct { OrgId string EnodeId string Ip string @@ -289,14 +305,21 @@ func (_NodeManager *NodeManagerCaller) GetNodeDetailsFromIndex(opts *bind.CallOp Raftport uint16 NodeStatus *big.Int }) - out := ret - err := _NodeManager.contract.Call(opts, out, "getNodeDetailsFromIndex", _nodeIndex) - return *ret, err + + outstruct.OrgId = out[0].(string) + outstruct.EnodeId = out[1].(string) + outstruct.Ip = out[2].(string) + outstruct.Port = out[3].(uint16) + outstruct.Raftport = out[4].(uint16) + outstruct.NodeStatus = out[5].(*big.Int) + + return *outstruct, err + } // GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. // -// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) view returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) func (_NodeManager *NodeManagerSession) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (struct { OrgId string EnodeId string @@ -310,7 +333,7 @@ func (_NodeManager *NodeManagerSession) GetNodeDetailsFromIndex(_nodeIndex *big. // GetNodeDetailsFromIndex is a free data retrieval call binding the contract method 0x97c07a9b. // -// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) constant returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) +// Solidity: function getNodeDetailsFromIndex(uint256 _nodeIndex) view returns(string _orgId, string _enodeId, string _ip, uint16 _port, uint16 _raftport, uint256 _nodeStatus) func (_NodeManager *NodeManagerCallerSession) GetNodeDetailsFromIndex(_nodeIndex *big.Int) (struct { OrgId string EnodeId string @@ -324,26 +347,31 @@ func (_NodeManager *NodeManagerCallerSession) GetNodeDetailsFromIndex(_nodeIndex // GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. // -// Solidity: function getNumberOfNodes() constant returns(uint256) +// Solidity: function getNumberOfNodes() view returns(uint256) func (_NodeManager *NodeManagerCaller) GetNumberOfNodes(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _NodeManager.contract.Call(opts, out, "getNumberOfNodes") - return *ret0, err + var out []interface{} + err := _NodeManager.contract.Call(opts, &out, "getNumberOfNodes") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. // -// Solidity: function getNumberOfNodes() constant returns(uint256) +// Solidity: function getNumberOfNodes() view returns(uint256) func (_NodeManager *NodeManagerSession) GetNumberOfNodes() (*big.Int, error) { return _NodeManager.Contract.GetNumberOfNodes(&_NodeManager.CallOpts) } // GetNumberOfNodes is a free data retrieval call binding the contract method 0xb81c806a. // -// Solidity: function getNumberOfNodes() constant returns(uint256) +// Solidity: function getNumberOfNodes() view returns(uint256) func (_NodeManager *NodeManagerCallerSession) GetNumberOfNodes() (*big.Int, error) { return _NodeManager.Contract.GetNumberOfNodes(&_NodeManager.CallOpts) } diff --git a/permission/v2/bind/org.go b/permission/v2/bind/org.go index 7c245ec6a1..d219e8e7b7 100644 --- a/permission/v2/bind/org.go +++ b/permission/v2/bind/org.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindOrgManager(address common.Address, caller bind.ContractCaller, transact // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_OrgManager *OrgManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_OrgManager *OrgManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _OrgManager.Contract.OrgManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_OrgManager *OrgManagerRaw) Transact(opts *bind.TransactOpts, method strin // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_OrgManager *OrgManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_OrgManager *OrgManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _OrgManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,228 +192,256 @@ func (_OrgManager *OrgManagerTransactorRaw) Transact(opts *bind.TransactOpts, me // CheckOrgActive is a free data retrieval call binding the contract method 0x3fd62ae7. // -// Solidity: function checkOrgActive(string _orgId) constant returns(bool) +// Solidity: function checkOrgActive(string _orgId) view returns(bool) func (_OrgManager *OrgManagerCaller) CheckOrgActive(opts *bind.CallOpts, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "checkOrgActive", _orgId) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "checkOrgActive", _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckOrgActive is a free data retrieval call binding the contract method 0x3fd62ae7. // -// Solidity: function checkOrgActive(string _orgId) constant returns(bool) +// Solidity: function checkOrgActive(string _orgId) view returns(bool) func (_OrgManager *OrgManagerSession) CheckOrgActive(_orgId string) (bool, error) { return _OrgManager.Contract.CheckOrgActive(&_OrgManager.CallOpts, _orgId) } // CheckOrgActive is a free data retrieval call binding the contract method 0x3fd62ae7. // -// Solidity: function checkOrgActive(string _orgId) constant returns(bool) +// Solidity: function checkOrgActive(string _orgId) view returns(bool) func (_OrgManager *OrgManagerCallerSession) CheckOrgActive(_orgId string) (bool, error) { return _OrgManager.Contract.CheckOrgActive(&_OrgManager.CallOpts, _orgId) } // CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. // -// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +// Solidity: function checkOrgExists(string _orgId) view returns(bool) func (_OrgManager *OrgManagerCaller) CheckOrgExists(opts *bind.CallOpts, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "checkOrgExists", _orgId) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "checkOrgExists", _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. // -// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +// Solidity: function checkOrgExists(string _orgId) view returns(bool) func (_OrgManager *OrgManagerSession) CheckOrgExists(_orgId string) (bool, error) { return _OrgManager.Contract.CheckOrgExists(&_OrgManager.CallOpts, _orgId) } // CheckOrgExists is a free data retrieval call binding the contract method 0xffe40d1d. // -// Solidity: function checkOrgExists(string _orgId) constant returns(bool) +// Solidity: function checkOrgExists(string _orgId) view returns(bool) func (_OrgManager *OrgManagerCallerSession) CheckOrgExists(_orgId string) (bool, error) { return _OrgManager.Contract.CheckOrgExists(&_OrgManager.CallOpts, _orgId) } // CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. // -// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) view returns(bool) func (_OrgManager *OrgManagerCaller) CheckOrgStatus(opts *bind.CallOpts, _orgId string, _orgStatus *big.Int) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "checkOrgStatus", _orgId, _orgStatus) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "checkOrgStatus", _orgId, _orgStatus) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. // -// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) view returns(bool) func (_OrgManager *OrgManagerSession) CheckOrgStatus(_orgId string, _orgStatus *big.Int) (bool, error) { return _OrgManager.Contract.CheckOrgStatus(&_OrgManager.CallOpts, _orgId, _orgStatus) } // CheckOrgStatus is a free data retrieval call binding the contract method 0x8c8642df. // -// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) constant returns(bool) +// Solidity: function checkOrgStatus(string _orgId, uint256 _orgStatus) view returns(bool) func (_OrgManager *OrgManagerCallerSession) CheckOrgStatus(_orgId string, _orgStatus *big.Int) (bool, error) { return _OrgManager.Contract.CheckOrgStatus(&_OrgManager.CallOpts, _orgId, _orgStatus) } // GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. // -// Solidity: function getNumberOfOrgs() constant returns(uint256) +// Solidity: function getNumberOfOrgs() view returns(uint256) func (_OrgManager *OrgManagerCaller) GetNumberOfOrgs(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "getNumberOfOrgs") - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getNumberOfOrgs") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. // -// Solidity: function getNumberOfOrgs() constant returns(uint256) +// Solidity: function getNumberOfOrgs() view returns(uint256) func (_OrgManager *OrgManagerSession) GetNumberOfOrgs() (*big.Int, error) { return _OrgManager.Contract.GetNumberOfOrgs(&_OrgManager.CallOpts) } // GetNumberOfOrgs is a free data retrieval call binding the contract method 0x7755ebdd. // -// Solidity: function getNumberOfOrgs() constant returns(uint256) +// Solidity: function getNumberOfOrgs() view returns(uint256) func (_OrgManager *OrgManagerCallerSession) GetNumberOfOrgs() (*big.Int, error) { return _OrgManager.Contract.GetNumberOfOrgs(&_OrgManager.CallOpts) } // GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. // -// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgDetails(string _orgId) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerCaller) GetOrgDetails(opts *bind.CallOpts, _orgId string) (string, string, string, *big.Int, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(string) - ret3 = new(*big.Int) - ret4 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - ret4, + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getOrgDetails", _orgId) + + if err != nil { + return *new(string), *new(string), *new(string), *new(*big.Int), *new(*big.Int), err } - err := _OrgManager.contract.Call(opts, out, "getOrgDetails", _orgId) - return *ret0, *ret1, *ret2, *ret3, *ret4, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, out4, err + } // GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. // -// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgDetails(string _orgId) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerSession) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { return _OrgManager.Contract.GetOrgDetails(&_OrgManager.CallOpts, _orgId) } // GetOrgDetails is a free data retrieval call binding the contract method 0xf4d6d9f5. // -// Solidity: function getOrgDetails(string _orgId) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgDetails(string _orgId) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerCallerSession) GetOrgDetails(_orgId string) (string, string, string, *big.Int, *big.Int, error) { return _OrgManager.Contract.GetOrgDetails(&_OrgManager.CallOpts, _orgId) } // GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. // -// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgInfo(uint256 _orgIndex) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerCaller) GetOrgInfo(opts *bind.CallOpts, _orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(string) - ret3 = new(*big.Int) - ret4 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - ret4, + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getOrgInfo", _orgIndex) + + if err != nil { + return *new(string), *new(string), *new(string), *new(*big.Int), *new(*big.Int), err } - err := _OrgManager.contract.Call(opts, out, "getOrgInfo", _orgIndex) - return *ret0, *ret1, *ret2, *ret3, *ret4, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + out4 := *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, out4, err + } // GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. // -// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgInfo(uint256 _orgIndex) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerSession) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { return _OrgManager.Contract.GetOrgInfo(&_OrgManager.CallOpts, _orgIndex) } // GetOrgInfo is a free data retrieval call binding the contract method 0x5c4f32ee. // -// Solidity: function getOrgInfo(uint256 _orgIndex) constant returns(string, string, string, uint256, uint256) +// Solidity: function getOrgInfo(uint256 _orgIndex) view returns(string, string, string, uint256, uint256) func (_OrgManager *OrgManagerCallerSession) GetOrgInfo(_orgIndex *big.Int) (string, string, string, *big.Int, *big.Int, error) { return _OrgManager.Contract.GetOrgInfo(&_OrgManager.CallOpts, _orgIndex) } // GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. // -// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +// Solidity: function getSubOrgIndexes(string _orgId) view returns(uint256[]) func (_OrgManager *OrgManagerCaller) GetSubOrgIndexes(opts *bind.CallOpts, _orgId string) ([]*big.Int, error) { - var ( - ret0 = new([]*big.Int) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "getSubOrgIndexes", _orgId) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getSubOrgIndexes", _orgId) + + if err != nil { + return *new([]*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new([]*big.Int)).(*[]*big.Int) + + return out0, err + } // GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. // -// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +// Solidity: function getSubOrgIndexes(string _orgId) view returns(uint256[]) func (_OrgManager *OrgManagerSession) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { return _OrgManager.Contract.GetSubOrgIndexes(&_OrgManager.CallOpts, _orgId) } // GetSubOrgIndexes is a free data retrieval call binding the contract method 0x5e99f6e5. // -// Solidity: function getSubOrgIndexes(string _orgId) constant returns(uint256[]) +// Solidity: function getSubOrgIndexes(string _orgId) view returns(uint256[]) func (_OrgManager *OrgManagerCallerSession) GetSubOrgIndexes(_orgId string) ([]*big.Int, error) { return _OrgManager.Contract.GetSubOrgIndexes(&_OrgManager.CallOpts, _orgId) } // GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. // -// Solidity: function getUltimateParent(string _orgId) constant returns(string) +// Solidity: function getUltimateParent(string _orgId) view returns(string) func (_OrgManager *OrgManagerCaller) GetUltimateParent(opts *bind.CallOpts, _orgId string) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _OrgManager.contract.Call(opts, out, "getUltimateParent", _orgId) - return *ret0, err + var out []interface{} + err := _OrgManager.contract.Call(opts, &out, "getUltimateParent", _orgId) + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. // -// Solidity: function getUltimateParent(string _orgId) constant returns(string) +// Solidity: function getUltimateParent(string _orgId) view returns(string) func (_OrgManager *OrgManagerSession) GetUltimateParent(_orgId string) (string, error) { return _OrgManager.Contract.GetUltimateParent(&_OrgManager.CallOpts, _orgId) } // GetUltimateParent is a free data retrieval call binding the contract method 0x177c8d8a. // -// Solidity: function getUltimateParent(string _orgId) constant returns(string) +// Solidity: function getUltimateParent(string _orgId) view returns(string) func (_OrgManager *OrgManagerCallerSession) GetUltimateParent(_orgId string) (string, error) { return _OrgManager.Contract.GetUltimateParent(&_OrgManager.CallOpts, _orgId) } diff --git a/permission/v2/bind/permission_impl.go b/permission/v2/bind/permission_impl.go index 663308606a..6bbf8ba2f7 100644 --- a/permission/v2/bind/permission_impl.go +++ b/permission/v2/bind/permission_impl.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindPermImpl(address common.Address, caller bind.ContractCaller, transactor // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermImpl *PermImplRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermImpl *PermImplRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermImpl.Contract.PermImplCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_PermImpl *PermImplRaw) Transact(opts *bind.TransactOpts, method string, p // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermImpl *PermImplCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermImpl *PermImplCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermImpl.Contract.contract.Call(opts, result, method, params...) } @@ -194,224 +192,254 @@ func (_PermImpl *PermImplTransactorRaw) Transact(opts *bind.TransactOpts, method // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_PermImpl *PermImplCaller) ConnectionAllowed(opts *bind.CallOpts, _enodeId string, _ip string, _port uint16) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "connectionAllowed", _enodeId, _ip, _port) - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "connectionAllowed", _enodeId, _ip, _port) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_PermImpl *PermImplSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { return _PermImpl.Contract.ConnectionAllowed(&_PermImpl.CallOpts, _enodeId, _ip, _port) } // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_PermImpl *PermImplCallerSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { return _PermImpl.Contract.ConnectionAllowed(&_PermImpl.CallOpts, _enodeId, _ip, _port) } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermImpl *PermImplCaller) GetNetworkBootStatus(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "getNetworkBootStatus") - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "getNetworkBootStatus") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermImpl *PermImplSession) GetNetworkBootStatus() (bool, error) { return _PermImpl.Contract.GetNetworkBootStatus(&_PermImpl.CallOpts) } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermImpl *PermImplCallerSession) GetNetworkBootStatus() (bool, error) { return _PermImpl.Contract.GetNetworkBootStatus(&_PermImpl.CallOpts) } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermImpl *PermImplCaller) GetPendingOp(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(common.Address) - ret3 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "getPendingOp", _orgId) + + if err != nil { + return *new(string), *new(string), *new(common.Address), *new(*big.Int), err } - err := _PermImpl.contract.Call(opts, out, "getPendingOp", _orgId) - return *ret0, *ret1, *ret2, *ret3, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(common.Address)).(*common.Address) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, err + } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermImpl *PermImplSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { return _PermImpl.Contract.GetPendingOp(&_PermImpl.CallOpts, _orgId) } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermImpl *PermImplCallerSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { return _PermImpl.Contract.GetPendingOp(&_PermImpl.CallOpts, _orgId) } // GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. // -// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +// Solidity: function getPolicyDetails() view returns(string, string, string, bool) func (_PermImpl *PermImplCaller) GetPolicyDetails(opts *bind.CallOpts) (string, string, string, bool, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(string) - ret3 = new(bool) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "getPolicyDetails") + + if err != nil { + return *new(string), *new(string), *new(string), *new(bool), err } - err := _PermImpl.contract.Call(opts, out, "getPolicyDetails") - return *ret0, *ret1, *ret2, *ret3, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(string)).(*string) + out3 := *abi.ConvertType(out[3], new(bool)).(*bool) + + return out0, out1, out2, out3, err + } // GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. // -// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +// Solidity: function getPolicyDetails() view returns(string, string, string, bool) func (_PermImpl *PermImplSession) GetPolicyDetails() (string, string, string, bool, error) { return _PermImpl.Contract.GetPolicyDetails(&_PermImpl.CallOpts) } // GetPolicyDetails is a free data retrieval call binding the contract method 0xcc9ba6fa. // -// Solidity: function getPolicyDetails() constant returns(string, string, string, bool) +// Solidity: function getPolicyDetails() view returns(string, string, string, bool) func (_PermImpl *PermImplCallerSession) GetPolicyDetails() (string, string, string, bool, error) { return _PermImpl.Contract.GetPolicyDetails(&_PermImpl.CallOpts) } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermImpl *PermImplCaller) IsNetworkAdmin(opts *bind.CallOpts, _account common.Address) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "isNetworkAdmin", _account) - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "isNetworkAdmin", _account) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermImpl *PermImplSession) IsNetworkAdmin(_account common.Address) (bool, error) { return _PermImpl.Contract.IsNetworkAdmin(&_PermImpl.CallOpts, _account) } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermImpl *PermImplCallerSession) IsNetworkAdmin(_account common.Address) (bool, error) { return _PermImpl.Contract.IsNetworkAdmin(&_PermImpl.CallOpts, _account) } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplCaller) IsOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "isOrgAdmin", _account, _orgId) - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "isOrgAdmin", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { return _PermImpl.Contract.IsOrgAdmin(&_PermImpl.CallOpts, _account, _orgId) } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplCallerSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { return _PermImpl.Contract.IsOrgAdmin(&_PermImpl.CallOpts, _account, _orgId) } // TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. // -// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) view returns(bool) func (_PermImpl *PermImplCaller) TransactionAllowed(opts *bind.CallOpts, _sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "transactionAllowed", _sender, _target, _value, _gasPrice, _gasLimit, _payload) - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "transactionAllowed", _sender, _target, _value, _gasPrice, _gasLimit, _payload) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. // -// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) view returns(bool) func (_PermImpl *PermImplSession) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { return _PermImpl.Contract.TransactionAllowed(&_PermImpl.CallOpts, _sender, _target, _value, _gasPrice, _gasLimit, _payload) } // TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. // -// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) view returns(bool) func (_PermImpl *PermImplCallerSession) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { return _PermImpl.Contract.TransactionAllowed(&_PermImpl.CallOpts, _sender, _target, _value, _gasPrice, _gasLimit, _payload) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermImpl.contract.Call(opts, out, "validateAccount", _account, _orgId) - return *ret0, err + var out []interface{} + err := _PermImpl.contract.Call(opts, &out, "validateAccount", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _PermImpl.Contract.ValidateAccount(&_PermImpl.CallOpts, _account, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermImpl *PermImplCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _PermImpl.Contract.ValidateAccount(&_PermImpl.CallOpts, _account, _orgId) } diff --git a/permission/v2/bind/permission_interface.go b/permission/v2/bind/permission_interface.go index 3f0e032bcf..34a2fc1e8e 100644 --- a/permission/v2/bind/permission_interface.go +++ b/permission/v2/bind/permission_interface.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindPermInterface(address common.Address, caller bind.ContractCaller, trans // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermInterface *PermInterfaceRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermInterface *PermInterfaceRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermInterface.Contract.PermInterfaceCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_PermInterface *PermInterfaceRaw) Transact(opts *bind.TransactOpts, method // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermInterface *PermInterfaceCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermInterface *PermInterfaceCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermInterface.Contract.contract.Call(opts, result, method, params...) } @@ -194,216 +192,251 @@ func (_PermInterface *PermInterfaceTransactorRaw) Transact(opts *bind.TransactOp // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_PermInterface *PermInterfaceCaller) ConnectionAllowed(opts *bind.CallOpts, _enodeId string, _ip string, _port uint16) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "connectionAllowed", _enodeId, _ip, _port) - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "connectionAllowed", _enodeId, _ip, _port) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_PermInterface *PermInterfaceSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { return _PermInterface.Contract.ConnectionAllowed(&_PermInterface.CallOpts, _enodeId, _ip, _port) } // ConnectionAllowed is a free data retrieval call binding the contract method 0x45a59e5b. // -// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) constant returns(bool) +// Solidity: function connectionAllowed(string _enodeId, string _ip, uint16 _port) view returns(bool) func (_PermInterface *PermInterfaceCallerSession) ConnectionAllowed(_enodeId string, _ip string, _port uint16) (bool, error) { return _PermInterface.Contract.ConnectionAllowed(&_PermInterface.CallOpts, _enodeId, _ip, _port) } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermInterface *PermInterfaceCaller) GetNetworkBootStatus(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "getNetworkBootStatus") - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "getNetworkBootStatus") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermInterface *PermInterfaceSession) GetNetworkBootStatus() (bool, error) { return _PermInterface.Contract.GetNetworkBootStatus(&_PermInterface.CallOpts) } // GetNetworkBootStatus is a free data retrieval call binding the contract method 0x4cbfa82e. // -// Solidity: function getNetworkBootStatus() constant returns(bool) +// Solidity: function getNetworkBootStatus() view returns(bool) func (_PermInterface *PermInterfaceCallerSession) GetNetworkBootStatus() (bool, error) { return _PermInterface.Contract.GetNetworkBootStatus(&_PermInterface.CallOpts) } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermInterface *PermInterfaceCaller) GetPendingOp(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(common.Address) - ret3 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "getPendingOp", _orgId) + + if err != nil { + return *new(string), *new(string), *new(common.Address), *new(*big.Int), err } - err := _PermInterface.contract.Call(opts, out, "getPendingOp", _orgId) - return *ret0, *ret1, *ret2, *ret3, err + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(common.Address)).(*common.Address) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, err + } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermInterface *PermInterfaceSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { return _PermInterface.Contract.GetPendingOp(&_PermInterface.CallOpts, _orgId) } // GetPendingOp is a free data retrieval call binding the contract method 0xf346a3a7. // -// Solidity: function getPendingOp(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOp(string _orgId) view returns(string, string, address, uint256) func (_PermInterface *PermInterfaceCallerSession) GetPendingOp(_orgId string) (string, string, common.Address, *big.Int, error) { return _PermInterface.Contract.GetPendingOp(&_PermInterface.CallOpts, _orgId) } // GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. // -// Solidity: function getPermissionsImpl() constant returns(address) +// Solidity: function getPermissionsImpl() view returns(address) func (_PermInterface *PermInterfaceCaller) GetPermissionsImpl(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "getPermissionsImpl") - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "getPermissionsImpl") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. // -// Solidity: function getPermissionsImpl() constant returns(address) +// Solidity: function getPermissionsImpl() view returns(address) func (_PermInterface *PermInterfaceSession) GetPermissionsImpl() (common.Address, error) { return _PermInterface.Contract.GetPermissionsImpl(&_PermInterface.CallOpts) } // GetPermissionsImpl is a free data retrieval call binding the contract method 0x03ed6933. // -// Solidity: function getPermissionsImpl() constant returns(address) +// Solidity: function getPermissionsImpl() view returns(address) func (_PermInterface *PermInterfaceCallerSession) GetPermissionsImpl() (common.Address, error) { return _PermInterface.Contract.GetPermissionsImpl(&_PermInterface.CallOpts) } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermInterface *PermInterfaceCaller) IsNetworkAdmin(opts *bind.CallOpts, _account common.Address) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "isNetworkAdmin", _account) - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "isNetworkAdmin", _account) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermInterface *PermInterfaceSession) IsNetworkAdmin(_account common.Address) (bool, error) { return _PermInterface.Contract.IsNetworkAdmin(&_PermInterface.CallOpts, _account) } // IsNetworkAdmin is a free data retrieval call binding the contract method 0xd1aa0c20. // -// Solidity: function isNetworkAdmin(address _account) constant returns(bool) +// Solidity: function isNetworkAdmin(address _account) view returns(bool) func (_PermInterface *PermInterfaceCallerSession) IsNetworkAdmin(_account common.Address) (bool, error) { return _PermInterface.Contract.IsNetworkAdmin(&_PermInterface.CallOpts, _account) } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceCaller) IsOrgAdmin(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "isOrgAdmin", _account, _orgId) - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "isOrgAdmin", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { return _PermInterface.Contract.IsOrgAdmin(&_PermInterface.CallOpts, _account, _orgId) } // IsOrgAdmin is a free data retrieval call binding the contract method 0x9bd38101. // -// Solidity: function isOrgAdmin(address _account, string _orgId) constant returns(bool) +// Solidity: function isOrgAdmin(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceCallerSession) IsOrgAdmin(_account common.Address, _orgId string) (bool, error) { return _PermInterface.Contract.IsOrgAdmin(&_PermInterface.CallOpts, _account, _orgId) } // TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. // -// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) view returns(bool) func (_PermInterface *PermInterfaceCaller) TransactionAllowed(opts *bind.CallOpts, _sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "transactionAllowed", _sender, _target, _value, _gasPrice, _gasLimit, _payload) - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "transactionAllowed", _sender, _target, _value, _gasPrice, _gasLimit, _payload) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. // -// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) view returns(bool) func (_PermInterface *PermInterfaceSession) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { return _PermInterface.Contract.TransactionAllowed(&_PermInterface.CallOpts, _sender, _target, _value, _gasPrice, _gasLimit, _payload) } // TransactionAllowed is a free data retrieval call binding the contract method 0x936421d5. // -// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) constant returns(bool) +// Solidity: function transactionAllowed(address _sender, address _target, uint256 _value, uint256 _gasPrice, uint256 _gasLimit, bytes _payload) view returns(bool) func (_PermInterface *PermInterfaceCallerSession) TransactionAllowed(_sender common.Address, _target common.Address, _value *big.Int, _gasPrice *big.Int, _gasLimit *big.Int, _payload []byte) (bool, error) { return _PermInterface.Contract.TransactionAllowed(&_PermInterface.CallOpts, _sender, _target, _value, _gasPrice, _gasLimit, _payload) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceCaller) ValidateAccount(opts *bind.CallOpts, _account common.Address, _orgId string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PermInterface.contract.Call(opts, out, "validateAccount", _account, _orgId) - return *ret0, err + var out []interface{} + err := _PermInterface.contract.Call(opts, &out, "validateAccount", _account, _orgId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _PermInterface.Contract.ValidateAccount(&_PermInterface.CallOpts, _account, _orgId) } // ValidateAccount is a free data retrieval call binding the contract method 0x6b568d76. // -// Solidity: function validateAccount(address _account, string _orgId) constant returns(bool) +// Solidity: function validateAccount(address _account, string _orgId) view returns(bool) func (_PermInterface *PermInterfaceCallerSession) ValidateAccount(_account common.Address, _orgId string) (bool, error) { return _PermInterface.Contract.ValidateAccount(&_PermInterface.CallOpts, _account, _orgId) } diff --git a/permission/v2/bind/permission_upgr.go b/permission/v2/bind/permission_upgr.go index 133e54ed7e..d3cfe8edc4 100644 --- a/permission/v2/bind/permission_upgr.go +++ b/permission/v2/bind/permission_upgr.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindPermUpgr(address common.Address, caller bind.ContractCaller, transactor // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermUpgr *PermUpgrRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermUpgr *PermUpgrRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermUpgr.Contract.PermUpgrCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_PermUpgr *PermUpgrRaw) Transact(opts *bind.TransactOpts, method string, p // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_PermUpgr *PermUpgrCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_PermUpgr *PermUpgrCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _PermUpgr.Contract.contract.Call(opts, result, method, params...) } @@ -194,78 +192,93 @@ func (_PermUpgr *PermUpgrTransactorRaw) Transact(opts *bind.TransactOpts, method // GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. // -// Solidity: function getGuardian() constant returns(address) +// Solidity: function getGuardian() view returns(address) func (_PermUpgr *PermUpgrCaller) GetGuardian(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PermUpgr.contract.Call(opts, out, "getGuardian") - return *ret0, err + var out []interface{} + err := _PermUpgr.contract.Call(opts, &out, "getGuardian") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. // -// Solidity: function getGuardian() constant returns(address) +// Solidity: function getGuardian() view returns(address) func (_PermUpgr *PermUpgrSession) GetGuardian() (common.Address, error) { return _PermUpgr.Contract.GetGuardian(&_PermUpgr.CallOpts) } // GetGuardian is a free data retrieval call binding the contract method 0xa75b87d2. // -// Solidity: function getGuardian() constant returns(address) +// Solidity: function getGuardian() view returns(address) func (_PermUpgr *PermUpgrCallerSession) GetGuardian() (common.Address, error) { return _PermUpgr.Contract.GetGuardian(&_PermUpgr.CallOpts) } // GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. // -// Solidity: function getPermImpl() constant returns(address) +// Solidity: function getPermImpl() view returns(address) func (_PermUpgr *PermUpgrCaller) GetPermImpl(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PermUpgr.contract.Call(opts, out, "getPermImpl") - return *ret0, err + var out []interface{} + err := _PermUpgr.contract.Call(opts, &out, "getPermImpl") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. // -// Solidity: function getPermImpl() constant returns(address) +// Solidity: function getPermImpl() view returns(address) func (_PermUpgr *PermUpgrSession) GetPermImpl() (common.Address, error) { return _PermUpgr.Contract.GetPermImpl(&_PermUpgr.CallOpts) } // GetPermImpl is a free data retrieval call binding the contract method 0x0e32cf90. // -// Solidity: function getPermImpl() constant returns(address) +// Solidity: function getPermImpl() view returns(address) func (_PermUpgr *PermUpgrCallerSession) GetPermImpl() (common.Address, error) { return _PermUpgr.Contract.GetPermImpl(&_PermUpgr.CallOpts) } // GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. // -// Solidity: function getPermInterface() constant returns(address) +// Solidity: function getPermInterface() view returns(address) func (_PermUpgr *PermUpgrCaller) GetPermInterface(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PermUpgr.contract.Call(opts, out, "getPermInterface") - return *ret0, err + var out []interface{} + err := _PermUpgr.contract.Call(opts, &out, "getPermInterface") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. // -// Solidity: function getPermInterface() constant returns(address) +// Solidity: function getPermInterface() view returns(address) func (_PermUpgr *PermUpgrSession) GetPermInterface() (common.Address, error) { return _PermUpgr.Contract.GetPermInterface(&_PermUpgr.CallOpts) } // GetPermInterface is a free data retrieval call binding the contract method 0xe572515c. // -// Solidity: function getPermInterface() constant returns(address) +// Solidity: function getPermInterface() view returns(address) func (_PermUpgr *PermUpgrCallerSession) GetPermInterface() (common.Address, error) { return _PermUpgr.Contract.GetPermInterface(&_PermUpgr.CallOpts) } diff --git a/permission/v2/bind/roles.go b/permission/v2/bind/roles.go index f0d6a37451..a2ec6237e6 100644 --- a/permission/v2/bind/roles.go +++ b/permission/v2/bind/roles.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindRoleManager(address common.Address, caller bind.ContractCaller, transac // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_RoleManager *RoleManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_RoleManager *RoleManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _RoleManager.Contract.RoleManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_RoleManager *RoleManagerRaw) Transact(opts *bind.TransactOpts, method str // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_RoleManager *RoleManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_RoleManager *RoleManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _RoleManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,33 +192,38 @@ func (_RoleManager *RoleManagerTransactorRaw) Transact(opts *bind.TransactOpts, // GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. // -// Solidity: function getNumberOfRoles() constant returns(uint256) +// Solidity: function getNumberOfRoles() view returns(uint256) func (_RoleManager *RoleManagerCaller) GetNumberOfRoles(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "getNumberOfRoles") - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "getNumberOfRoles") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. // -// Solidity: function getNumberOfRoles() constant returns(uint256) +// Solidity: function getNumberOfRoles() view returns(uint256) func (_RoleManager *RoleManagerSession) GetNumberOfRoles() (*big.Int, error) { return _RoleManager.Contract.GetNumberOfRoles(&_RoleManager.CallOpts) } // GetNumberOfRoles is a free data retrieval call binding the contract method 0x87f55d31. // -// Solidity: function getNumberOfRoles() constant returns(uint256) +// Solidity: function getNumberOfRoles() view returns(uint256) func (_RoleManager *RoleManagerCallerSession) GetNumberOfRoles() (*big.Int, error) { return _RoleManager.Contract.GetNumberOfRoles(&_RoleManager.CallOpts) } // GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. // -// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetails(string _roleId, string _orgId) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerCaller) GetRoleDetails(opts *bind.CallOpts, _roleId string, _orgId string) (struct { RoleId string OrgId string @@ -229,7 +232,10 @@ func (_RoleManager *RoleManagerCaller) GetRoleDetails(opts *bind.CallOpts, _role Admin bool Active bool }, error) { - ret := new(struct { + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "getRoleDetails", _roleId, _orgId) + + outstruct := new(struct { RoleId string OrgId string AccessType *big.Int @@ -237,14 +243,21 @@ func (_RoleManager *RoleManagerCaller) GetRoleDetails(opts *bind.CallOpts, _role Admin bool Active bool }) - out := ret - err := _RoleManager.contract.Call(opts, out, "getRoleDetails", _roleId, _orgId) - return *ret, err + + outstruct.RoleId = out[0].(string) + outstruct.OrgId = out[1].(string) + outstruct.AccessType = out[2].(*big.Int) + outstruct.Voter = out[3].(bool) + outstruct.Admin = out[4].(bool) + outstruct.Active = out[5].(bool) + + return *outstruct, err + } // GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. // -// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetails(string _roleId, string _orgId) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerSession) GetRoleDetails(_roleId string, _orgId string) (struct { RoleId string OrgId string @@ -258,7 +271,7 @@ func (_RoleManager *RoleManagerSession) GetRoleDetails(_roleId string, _orgId st // GetRoleDetails is a free data retrieval call binding the contract method 0x1870aba3. // -// Solidity: function getRoleDetails(string _roleId, string _orgId) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetails(string _roleId, string _orgId) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerCallerSession) GetRoleDetails(_roleId string, _orgId string) (struct { RoleId string OrgId string @@ -272,7 +285,7 @@ func (_RoleManager *RoleManagerCallerSession) GetRoleDetails(_roleId string, _or // GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. // -// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerCaller) GetRoleDetailsFromIndex(opts *bind.CallOpts, _rIndex *big.Int) (struct { RoleId string OrgId string @@ -281,7 +294,10 @@ func (_RoleManager *RoleManagerCaller) GetRoleDetailsFromIndex(opts *bind.CallOp Admin bool Active bool }, error) { - ret := new(struct { + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "getRoleDetailsFromIndex", _rIndex) + + outstruct := new(struct { RoleId string OrgId string AccessType *big.Int @@ -289,14 +305,21 @@ func (_RoleManager *RoleManagerCaller) GetRoleDetailsFromIndex(opts *bind.CallOp Admin bool Active bool }) - out := ret - err := _RoleManager.contract.Call(opts, out, "getRoleDetailsFromIndex", _rIndex) - return *ret, err + + outstruct.RoleId = out[0].(string) + outstruct.OrgId = out[1].(string) + outstruct.AccessType = out[2].(*big.Int) + outstruct.Voter = out[3].(bool) + outstruct.Admin = out[4].(bool) + outstruct.Active = out[5].(bool) + + return *outstruct, err + } // GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. // -// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerSession) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { RoleId string OrgId string @@ -310,7 +333,7 @@ func (_RoleManager *RoleManagerSession) GetRoleDetailsFromIndex(_rIndex *big.Int // GetRoleDetailsFromIndex is a free data retrieval call binding the contract method 0xa451d4a8. // -// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) constant returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) +// Solidity: function getRoleDetailsFromIndex(uint256 _rIndex) view returns(string roleId, string orgId, uint256 accessType, bool voter, bool admin, bool active) func (_RoleManager *RoleManagerCallerSession) GetRoleDetailsFromIndex(_rIndex *big.Int) (struct { RoleId string OrgId string @@ -324,130 +347,155 @@ func (_RoleManager *RoleManagerCallerSession) GetRoleDetailsFromIndex(_rIndex *b // IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. // -// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCaller) IsAdminRole(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "isAdminRole", _roleId, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "isAdminRole", _roleId, _orgId, _ultParent) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. // -// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerSession) IsAdminRole(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.IsAdminRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // IsAdminRole is a free data retrieval call binding the contract method 0xbe322e54. // -// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isAdminRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCallerSession) IsAdminRole(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.IsAdminRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. // -// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCaller) IsVoterRole(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "isVoterRole", _roleId, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "isVoterRole", _roleId, _orgId, _ultParent) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. // -// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerSession) IsVoterRole(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.IsVoterRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // IsVoterRole is a free data retrieval call binding the contract method 0xdeb16ba7. // -// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function isVoterRole(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCallerSession) IsVoterRole(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.IsVoterRole(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // RoleAccess is a free data retrieval call binding the contract method 0xcfc83dfa. // -// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) constant returns(uint256) +// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) view returns(uint256) func (_RoleManager *RoleManagerCaller) RoleAccess(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "roleAccess", _roleId, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "roleAccess", _roleId, _orgId, _ultParent) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // RoleAccess is a free data retrieval call binding the contract method 0xcfc83dfa. // -// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) constant returns(uint256) +// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) view returns(uint256) func (_RoleManager *RoleManagerSession) RoleAccess(_roleId string, _orgId string, _ultParent string) (*big.Int, error) { return _RoleManager.Contract.RoleAccess(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // RoleAccess is a free data retrieval call binding the contract method 0xcfc83dfa. // -// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) constant returns(uint256) +// Solidity: function roleAccess(string _roleId, string _orgId, string _ultParent) view returns(uint256) func (_RoleManager *RoleManagerCallerSession) RoleAccess(_roleId string, _orgId string, _ultParent string) (*big.Int, error) { return _RoleManager.Contract.RoleAccess(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // RoleExists is a free data retrieval call binding the contract method 0xabf5739f. // -// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCaller) RoleExists(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "roleExists", _roleId, _orgId, _ultParent) - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "roleExists", _roleId, _orgId, _ultParent) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // RoleExists is a free data retrieval call binding the contract method 0xabf5739f. // -// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerSession) RoleExists(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.RoleExists(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // RoleExists is a free data retrieval call binding the contract method 0xabf5739f. // -// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) constant returns(bool) +// Solidity: function roleExists(string _roleId, string _orgId, string _ultParent) view returns(bool) func (_RoleManager *RoleManagerCallerSession) RoleExists(_roleId string, _orgId string, _ultParent string) (bool, error) { return _RoleManager.Contract.RoleExists(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent) } // TransactionAllowed is a free data retrieval call binding the contract method 0xd1f77866. // -// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) constant returns(bool) +// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) view returns(bool) func (_RoleManager *RoleManagerCaller) TransactionAllowed(opts *bind.CallOpts, _roleId string, _orgId string, _ultParent string, _typeOfTxn *big.Int) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _RoleManager.contract.Call(opts, out, "transactionAllowed", _roleId, _orgId, _ultParent, _typeOfTxn) - return *ret0, err + var out []interface{} + err := _RoleManager.contract.Call(opts, &out, "transactionAllowed", _roleId, _orgId, _ultParent, _typeOfTxn) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // TransactionAllowed is a free data retrieval call binding the contract method 0xd1f77866. // -// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) constant returns(bool) +// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) view returns(bool) func (_RoleManager *RoleManagerSession) TransactionAllowed(_roleId string, _orgId string, _ultParent string, _typeOfTxn *big.Int) (bool, error) { return _RoleManager.Contract.TransactionAllowed(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent, _typeOfTxn) } // TransactionAllowed is a free data retrieval call binding the contract method 0xd1f77866. // -// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) constant returns(bool) +// Solidity: function transactionAllowed(string _roleId, string _orgId, string _ultParent, uint256 _typeOfTxn) view returns(bool) func (_RoleManager *RoleManagerCallerSession) TransactionAllowed(_roleId string, _orgId string, _ultParent string, _typeOfTxn *big.Int) (bool, error) { return _RoleManager.Contract.TransactionAllowed(&_RoleManager.CallOpts, _roleId, _orgId, _ultParent, _typeOfTxn) } diff --git a/permission/v2/bind/voter.go b/permission/v2/bind/voter.go index af2ba5230e..8c8ee0d18f 100644 --- a/permission/v2/bind/voter.go +++ b/permission/v2/bind/voter.go @@ -1,10 +1,9 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package permission +package bind import ( - "github.com/ethereum/go-ethereum/common/math" "math/big" "strings" @@ -21,7 +20,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = math.U256Bytes _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -158,7 +156,7 @@ func bindVoterManager(address common.Address, caller bind.ContractCaller, transa // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VoterManager *VoterManagerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VoterManager *VoterManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VoterManager.Contract.VoterManagerCaller.contract.Call(opts, result, method, params...) } @@ -177,7 +175,7 @@ func (_VoterManager *VoterManagerRaw) Transact(opts *bind.TransactOpts, method s // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VoterManager *VoterManagerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VoterManager *VoterManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VoterManager.Contract.contract.Call(opts, result, method, params...) } @@ -194,34 +192,34 @@ func (_VoterManager *VoterManagerTransactorRaw) Transact(opts *bind.TransactOpts // GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. // -// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOpDetails(string _orgId) view returns(string, string, address, uint256) func (_VoterManager *VoterManagerCaller) GetPendingOpDetails(opts *bind.CallOpts, _orgId string) (string, string, common.Address, *big.Int, error) { - var ( - ret0 = new(string) - ret1 = new(string) - ret2 = new(common.Address) - ret3 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, - ret3, - } - err := _VoterManager.contract.Call(opts, out, "getPendingOpDetails", _orgId) - return *ret0, *ret1, *ret2, *ret3, err + var out []interface{} + err := _VoterManager.contract.Call(opts, &out, "getPendingOpDetails", _orgId) + + if err != nil { + return *new(string), *new(string), *new(common.Address), *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + out1 := *abi.ConvertType(out[1], new(string)).(*string) + out2 := *abi.ConvertType(out[2], new(common.Address)).(*common.Address) + out3 := *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + + return out0, out1, out2, out3, err + } // GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. // -// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOpDetails(string _orgId) view returns(string, string, address, uint256) func (_VoterManager *VoterManagerSession) GetPendingOpDetails(_orgId string) (string, string, common.Address, *big.Int, error) { return _VoterManager.Contract.GetPendingOpDetails(&_VoterManager.CallOpts, _orgId) } // GetPendingOpDetails is a free data retrieval call binding the contract method 0x014e6acc. // -// Solidity: function getPendingOpDetails(string _orgId) constant returns(string, string, address, uint256) +// Solidity: function getPendingOpDetails(string _orgId) view returns(string, string, address, uint256) func (_VoterManager *VoterManagerCallerSession) GetPendingOpDetails(_orgId string) (string, string, common.Address, *big.Int, error) { return _VoterManager.Contract.GetPendingOpDetails(&_VoterManager.CallOpts, _orgId) } diff --git a/signer/core/api.go b/signer/core/api.go index 4957946e05..1857d19a27 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -43,7 +43,7 @@ const ( // numberOfAccountsToDerive For hardware wallets, the number of accounts to derive numberOfAccountsToDerive = 10 // ExternalAPIVersion -- see extapi_changelog.md - ExternalAPIVersion = "6.0.0" + ExternalAPIVersion = "6.1.0" // InternalAPIVersion -- see intapi_changelog.md InternalAPIVersion = "7.0.1" ) @@ -64,6 +64,8 @@ type ExternalAPI interface { EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Version info about the APIs Version(ctx context.Context) (string, error) + // SignGnosisSafeTransaction signs/confirms a gnosis-safe multisig transaction + SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) } // UIClientAPI specifies what method a UI needs to implement to be able to be used as a @@ -244,6 +246,7 @@ type ( Address common.MixedcaseAddress `json:"address"` Rawdata []byte `json:"raw_data"` Messages []*NameValueType `json:"messages"` + Callinfo []ValidationInfo `json:"call_info"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` } @@ -390,7 +393,9 @@ func (api *SignerAPI) startUSBListener() { // List returns the set of wallet this signer manages. Each wallet can contain // multiple accounts. func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { - var accs []accounts.Account + var accs = make([]accounts.Account, 0) + // accs is initialized as empty list, not nil. We use 'nil' to signal + // rejection, as opposed to an empty list. for _, wallet := range api.am.Wallets() { accs = append(accs, wallet.Accounts()...) } @@ -400,13 +405,11 @@ func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { } if result.Accounts == nil { return nil, ErrRequestDenied - } addresses := make([]common.Address, 0) for _, acc := range result.Accounts { addresses = append(addresses, acc.Address) } - return addresses, nil } @@ -597,6 +600,33 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } +func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) { + // Do the usual validations, but on the last-stage transaction + args := gnosisTx.ArgsForValidation() + msgs, err := api.validator.ValidateTransaction(methodSelector, args) + if err != nil { + return nil, err + } + // If we are in 'rejectMode', then reject rather than show the user warnings + if api.rejectMode { + if err := msgs.getWarnings(); err != nil { + return nil, err + } + } + typedData := gnosisTx.ToTypedData() + signature, preimage, err := api.signTypedData(ctx, signerAddress, typedData, msgs) + if err != nil { + return nil, err + } + checkSummedSender, _ := common.NewMixedcaseAddressFromString(signerAddress.Address().Hex()) + + gnosisTx.Signature = signature + gnosisTx.SafeTxHash = common.BytesToHash(preimage) + gnosisTx.Sender = *checkSummedSender // Must be checksumed to be accepted by relay + + return &gnosisTx, nil +} + // Returns the external api version. This method does not require user acceptance. Available methods are // available via enumeration anyway, and this info does not contain user-specific data func (api *SignerAPI) Version(ctx context.Context) (string, error) { diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 1092e7a923..bda88a8b2e 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -18,6 +18,7 @@ package core import ( "context" + "encoding/json" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -61,13 +62,32 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth } func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { + marshalledData, _ := json.Marshal(data) // can ignore error, marshalling what we just unmarshalled l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", addr.String(), "data", data, "content-type", contentType) + "addr", addr.String(), "data", marshalledData, "content-type", contentType) b, e := l.api.SignData(ctx, contentType, addr, data) l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } +func (l *AuditLogger) SignGnosisSafeTx(ctx context.Context, addr common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) { + sel := "" + if methodSelector != nil { + sel = *methodSelector + } + data, _ := json.Marshal(gnosisTx) // can ignore error, marshalling what we just unmarshalled + l.log.Info("SignGnosisSafeTx", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.String(), "data", string(data), "selector", sel) + res, e := l.api.SignGnosisSafeTx(ctx, addr, gnosisTx, methodSelector) + if res != nil { + data, _ := json.Marshal(res) // can ignore error, marshalling what we just unmarshalled + l.log.Info("SignGnosisSafeTx", "type", "response", "data", string(data), "error", e) + } else { + l.log.Info("SignGnosisSafeTx", "type", "response", "data", res, "error", e) + } + return res, e +} + func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { l.log.Info("SignTypedData", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", addr.String(), "data", data) diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 27a2f71aaa..cbfb56c9df 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -148,6 +148,13 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %s\n", request.Address.String()) + if len(request.Callinfo) != 0 { + fmt.Printf("\nValidation messages:\n") + for _, m := range request.Callinfo { + fmt.Printf(" * %s : %s\n", m.Typ, m.Message) + } + fmt.Println() + } fmt.Printf("messages:\n") for _, nvt := range request.Messages { fmt.Printf("\u00a0\u00a0%v\n", strings.TrimSpace(nvt.Pprint(1))) diff --git a/signer/core/gnosis_safe.go b/signer/core/gnosis_safe.go new file mode 100644 index 0000000000..e4385f9dc3 --- /dev/null +++ b/signer/core/gnosis_safe.go @@ -0,0 +1,91 @@ +package core + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" +) + +// GnosisSafeTx is a type to parse the safe-tx returned by the relayer, +// it also conforms to the API required by the Gnosis Safe tx relay service. +// See 'SafeMultisigTransaction' on https://safe-transaction.mainnet.gnosis.io/ +type GnosisSafeTx struct { + // These fields are only used on output + Signature hexutil.Bytes `json:"signature"` + SafeTxHash common.Hash `json:"contractTransactionHash"` + Sender common.MixedcaseAddress `json:"sender"` + // These fields are used both on input and output + Safe common.MixedcaseAddress `json:"safe"` + To common.MixedcaseAddress `json:"to"` + Value math.Decimal256 `json:"value"` + GasPrice math.Decimal256 `json:"gasPrice"` + Data *hexutil.Bytes `json:"data"` + Operation uint8 `json:"operation"` + GasToken common.Address `json:"gasToken"` + RefundReceiver common.Address `json:"refundReceiver"` + BaseGas big.Int `json:"baseGas"` + SafeTxGas big.Int `json:"safeTxGas"` + Nonce big.Int `json:"nonce"` + InputExpHash common.Hash `json:"safeTxHash"` +} + +// ToTypedData converts the tx to a EIP-712 Typed Data structure for signing +func (tx *GnosisSafeTx) ToTypedData() TypedData { + var data hexutil.Bytes + if tx.Data != nil { + data = *tx.Data + } + gnosisTypedData := TypedData{ + Types: Types{ + "EIP712Domain": []Type{{Name: "verifyingContract", Type: "address"}}, + "SafeTx": []Type{ + {Name: "to", Type: "address"}, + {Name: "value", Type: "uint256"}, + {Name: "data", Type: "bytes"}, + {Name: "operation", Type: "uint8"}, + {Name: "safeTxGas", Type: "uint256"}, + {Name: "baseGas", Type: "uint256"}, + {Name: "gasPrice", Type: "uint256"}, + {Name: "gasToken", Type: "address"}, + {Name: "refundReceiver", Type: "address"}, + {Name: "nonce", Type: "uint256"}, + }, + }, + Domain: TypedDataDomain{ + VerifyingContract: tx.Safe.Address().Hex(), + }, + PrimaryType: "SafeTx", + Message: TypedDataMessage{ + "to": tx.To.Address().Hex(), + "value": tx.Value.String(), + "data": data, + "operation": fmt.Sprintf("%d", tx.Operation), + "safeTxGas": fmt.Sprintf("%#d", &tx.SafeTxGas), + "baseGas": fmt.Sprintf("%#d", &tx.BaseGas), + "gasPrice": tx.GasPrice.String(), + "gasToken": tx.GasToken.Hex(), + "refundReceiver": tx.RefundReceiver.Hex(), + "nonce": fmt.Sprintf("%d", tx.Nonce.Uint64()), + }, + } + return gnosisTypedData +} + +// ArgsForValidation returns a SendTxArgs struct, which can be used for the +// common validations, e.g. look up 4byte destinations +func (tx *GnosisSafeTx) ArgsForValidation() *SendTxArgs { + args := &SendTxArgs{ + From: tx.Safe, + To: &tx.To, + Gas: hexutil.Uint64(tx.SafeTxGas.Uint64()), + GasPrice: hexutil.Big(tx.GasPrice), + Value: hexutil.Big(tx.Value), + Nonce: hexutil.Uint64(tx.Nonce.Uint64()), + Data: tx.Data, + Input: nil, + } + return args +} diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 7fc66b4b74..19377a521b 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -125,7 +125,7 @@ var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) // // Note, the produced signature conforms to the secp256k1 curve R, S and V values, // where the V value will be 27 or 28 for legacy reasons, if legacyV==true. -func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) { +func (api *SignerAPI) sign(req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) { // We make the request prior to looking up if we actually have the account, to prevent // account-enumeration via the API res, err := api.UI.ApproveSignData(req) @@ -136,7 +136,7 @@ func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, l return nil, ErrRequestDenied } // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr.Address()} + account := accounts.Account{Address: req.Address.Address()} wallet, err := api.am.Find(account) if err != nil { return nil, err @@ -167,7 +167,7 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com if err != nil { return nil, err } - signature, err := api.sign(addr, req, transformV) + signature, err := api.sign(req, transformV) if err != nil { api.UI.ShowError(err.Error()) return nil, err @@ -312,28 +312,47 @@ func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error) // SignTypedData signs EIP-712 conformant typed data // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") +// It returns +// - the signature, +// - and/or any error func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) { + signature, _, err := api.signTypedData(ctx, addr, typedData, nil) + return signature, err +} + +// signTypedData is identical to the capitalized version, except that it also returns the hash (preimage) +// - the signature preimage (hash) +func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress, + typedData TypedData, validationMessages *ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) { domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) if err != nil { - return nil, err + return nil, nil, err } typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { - return nil, err + return nil, nil, err } rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) sighash := crypto.Keccak256(rawData) messages, err := typedData.Format() if err != nil { - return nil, err - } - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Messages: messages, Hash: sighash} - signature, err := api.sign(addr, req, true) + return nil, nil, err + } + req := &SignDataRequest{ + ContentType: DataTyped.Mime, + Rawdata: rawData, + Messages: messages, + Hash: sighash, + Address: addr} + if validationMessages != nil { + req.Callinfo = validationMessages.Messages + } + signature, err := api.sign(req, true) if err != nil { api.UI.ShowError(err.Error()) - return nil, err + return nil, nil, err } - return signature, nil + return signature, sighash, nil } // HashStruct generates a keccak256 hash of the encoding of the provided data @@ -420,8 +439,8 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter buffer := bytes.Buffer{} // Verify extra data - if len(typedData.Types[primaryType]) < len(data) { - return nil, errors.New("there is extra data provided in the message") + if exp, got := len(typedData.Types[primaryType]), len(data); exp < got { + return nil, fmt.Errorf("there is extra data provided in the message (%d < %d)", exp, got) } // Add typehash @@ -834,7 +853,11 @@ func (nvt *NameValueType) Pprint(depth int) string { output.WriteString(sublevel) } } else { - output.WriteString(fmt.Sprintf("%q\n", nvt.Value)) + if nvt.Value != nil { + output.WriteString(fmt.Sprintf("%q\n", nvt.Value)) + } else { + output.WriteString("\n") + } } return output.String() } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index ab5f2cc962..23b7b9897b 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -17,6 +17,7 @@ package core_test import ( + "bytes" "context" "encoding/json" "fmt" @@ -414,3 +415,119 @@ func TestFuzzerFiles(t *testing.T) { typedData.Format() } } + +var gnosisTypedData = ` +{ + "types": { + "EIP712Domain": [ + { "type": "address", "name": "verifyingContract" } + ], + "SafeTx": [ + { "type": "address", "name": "to" }, + { "type": "uint256", "name": "value" }, + { "type": "bytes", "name": "data" }, + { "type": "uint8", "name": "operation" }, + { "type": "uint256", "name": "safeTxGas" }, + { "type": "uint256", "name": "baseGas" }, + { "type": "uint256", "name": "gasPrice" }, + { "type": "address", "name": "gasToken" }, + { "type": "address", "name": "refundReceiver" }, + { "type": "uint256", "name": "nonce" } + ] + }, + "domain": { + "verifyingContract": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3" + }, + "primaryType": "SafeTx", + "message": { + "to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D", + "value": "20000000000000000", + "data": "0x", + "operation": 0, + "safeTxGas": 27845, + "baseGas": 0, + "gasPrice": "0", + "gasToken": "0x0000000000000000000000000000000000000000", + "refundReceiver": "0x0000000000000000000000000000000000000000", + "nonce": 3 + } +}` + +var gnosisTx = ` +{ + "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3", + "to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D", + "value": "20000000000000000", + "data": null, + "operation": 0, + "gasToken": "0x0000000000000000000000000000000000000000", + "safeTxGas": 27845, + "baseGas": 0, + "gasPrice": "0", + "refundReceiver": "0x0000000000000000000000000000000000000000", + "nonce": 3, + "executionDate": null, + "submissionDate": "2020-09-15T21:59:23.815748Z", + "modified": "2020-09-15T21:59:23.815748Z", + "blockNumber": null, + "transactionHash": null, + "safeTxHash": "0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f", + "executor": null, + "isExecuted": false, + "isSuccessful": null, + "ethGasPrice": null, + "gasUsed": null, + "fee": null, + "origin": null, + "dataDecoded": null, + "confirmationsRequired": null, + "confirmations": [ + { + "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34", + "submissionDate": "2020-09-15T21:59:28.281243Z", + "transactionHash": null, + "confirmationType": "CONFIRMATION", + "signature": "0x5e562065a0cb15d766dac0cd49eb6d196a41183af302c4ecad45f1a81958d7797753f04424a9b0aa1cb0448e4ec8e189540fbcdda7530ef9b9d95dfc2d36cb521b", + "signatureType": "EOA" + } + ], + "signatures": null + } +` + +// TestGnosisTypedData tests the scenario where a user submits a full EIP-712 +// struct without using the gnosis-specific endpoint +func TestGnosisTypedData(t *testing.T) { + var td core.TypedData + err := json.Unmarshal([]byte(gnosisTypedData), &td) + if err != nil { + t.Fatalf("unmarshalling failed '%v'", err) + } + _, sighash, err := sign(td) + if err != nil { + t.Fatal(err) + } + expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f") + if !bytes.Equal(expSigHash, sighash) { + t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) + } +} + +// TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe +// specific data, and we fill the TypedData struct on our side +func TestGnosisCustomData(t *testing.T) { + var tx core.GnosisSafeTx + err := json.Unmarshal([]byte(gnosisTx), &tx) + if err != nil { + t.Fatal(err) + } + var td = tx.ToTypedData() + _, sighash, err := sign(td) + if err != nil { + t.Fatal(err) + } + expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f") + if !bytes.Equal(expSigHash, sighash) { + t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) + } +} diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go index ed5c7c0586..76d3c800f7 100644 --- a/tests/fuzzers/abi/abifuzzer.go +++ b/tests/fuzzers/abi/abifuzzer.go @@ -30,7 +30,7 @@ import ( func unpackPack(abi abi.ABI, method string, inputType []interface{}, input []byte) bool { outptr := reflect.New(reflect.TypeOf(inputType)) - if err := abi.Unpack(outptr.Interface(), method, input); err == nil { + if err := abi.UnpackIntoInterface(outptr.Interface(), method, input); err == nil { output, err := abi.Pack(method, input) if err != nil { // We have some false positives as we can unpack these type successfully, but not pack them @@ -51,7 +51,7 @@ func unpackPack(abi abi.ABI, method string, inputType []interface{}, input []byt func packUnpack(abi abi.ABI, method string, input []interface{}) bool { if packed, err := abi.Pack(method, input); err == nil { outptr := reflect.New(reflect.TypeOf(input)) - err := abi.Unpack(outptr.Interface(), method, packed) + err := abi.UnpackIntoInterface(outptr.Interface(), method, packed) if err != nil { panic(err) } diff --git a/trie/committer.go b/trie/committer.go index fc8b7ceda5..20c95bed08 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" ) @@ -33,10 +32,9 @@ const leafChanSize = 200 // leaf represents a trie leaf value type leaf struct { - size int // size of the rlp data (estimate) - hash common.Hash // hash of rlp data - node node // the node to commit - vnodes bool // set to true if the node (possibly) contains a valueNode + size int // size of the rlp data (estimate) + hash common.Hash // hash of rlp data + node node // the node to commit } // committer is a type used for the trie Commit operation. A committer has some @@ -74,18 +72,12 @@ func returnCommitterToPool(h *committer) { committerPool.Put(h) } -// commitNeeded returns 'false' if the given node is already in sync with db -func (c *committer) commitNeeded(n node) bool { - hash, dirty := n.cache() - return hash == nil || dirty -} - // commit collapses a node down into a hash node and inserts it into the database func (c *committer) Commit(n node, db *Database) (hashNode, error) { if db == nil { return nil, errors.New("no db provided") } - h, err := c.commit(n, db, true) + h, err := c.commit(n, db) if err != nil { return nil, err } @@ -93,7 +85,7 @@ func (c *committer) Commit(n node, db *Database) (hashNode, error) { } // commit collapses a node down into a hash node and inserts it into the database -func (c *committer) commit(n node, db *Database, force bool) (node, error) { +func (c *committer) commit(n node, db *Database) (node, error) { // if this path is clean, use available cached data hash, dirty := n.cache() if hash != nil && !dirty { @@ -104,8 +96,11 @@ func (c *committer) commit(n node, db *Database, force bool) (node, error) { case *shortNode: // Commit child collapsed := cn.copy() - if _, ok := cn.Val.(valueNode); !ok { - childV, err := c.commit(cn.Val, db, false) + + // If the child is fullnode, recursively commit. + // Otherwise it can only be hashNode or valueNode. + if _, ok := cn.Val.(*fullNode); ok { + childV, err := c.commit(cn.Val, db) if err != nil { return nil, err } @@ -113,78 +108,78 @@ func (c *committer) commit(n node, db *Database, force bool) (node, error) { } // The key needs to be copied, since we're delivering it to database collapsed.Key = hexToCompact(cn.Key) - hashedNode := c.store(collapsed, db, force, true) + hashedNode := c.store(collapsed, db) if hn, ok := hashedNode.(hashNode); ok { return hn, nil } return collapsed, nil case *fullNode: - hashedKids, hasVnodes, err := c.commitChildren(cn, db, force) + hashedKids, err := c.commitChildren(cn, db) if err != nil { return nil, err } collapsed := cn.copy() collapsed.Children = hashedKids - hashedNode := c.store(collapsed, db, force, hasVnodes) + hashedNode := c.store(collapsed, db) if hn, ok := hashedNode.(hashNode); ok { return hn, nil } return collapsed, nil - case valueNode: - return c.store(cn, db, force, false), nil - // hashnodes aren't stored case hashNode: return cn, nil + default: + // nil, valuenode shouldn't be committed + panic(fmt.Sprintf("%T: invalid node: %v", n, n)) } - return hash, nil } // commitChildren commits the children of the given fullnode -func (c *committer) commitChildren(n *fullNode, db *Database, force bool) ([17]node, bool, error) { +func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, error) { var children [17]node - var hasValueNodeChildren = false - for i, child := range n.Children { + for i := 0; i < 16; i++ { + child := n.Children[i] if child == nil { continue } - hnode, err := c.commit(child, db, false) - if err != nil { - return children, false, err + // If it's the hashed child, save the hash value directly. + // Note: it's impossible that the child in range [0, 15] + // is a valuenode. + if hn, ok := child.(hashNode); ok { + children[i] = hn + continue } - children[i] = hnode - if _, ok := hnode.(valueNode); ok { - hasValueNodeChildren = true + // Commit the child recursively and store the "hashed" value. + // Note the returned node can be some embedded nodes, so it's + // possible the type is not hashnode. + hashed, err := c.commit(child, db) + if err != nil { + return children, err } + children[i] = hashed } - return children, hasValueNodeChildren, nil + // For the 17th child, it's possible the type is valuenode. + if n.Children[16] != nil { + children[16] = n.Children[16] + } + return children, nil } // store hashes the node n and if we have a storage layer specified, it writes // the key/value pair to it and tracks any node->child references as well as any // node->external trie references. -func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren bool) node { +func (c *committer) store(n node, db *Database) node { // Larger nodes are replaced by their hash and stored in the database. var ( hash, _ = n.cache() size int ) if hash == nil { - if vn, ok := n.(valueNode); ok { - c.tmp.Reset() - if err := rlp.Encode(&c.tmp, vn); err != nil { - panic("encode error: " + err.Error()) - } - size = len(c.tmp) - if size < 32 && !force { - return n // Nodes smaller than 32 bytes are stored inside their parent - } - hash = c.makeHashNode(c.tmp) - } else { - // This was not generated - must be a small node stored in the parent - // No need to do anything here - return n - } + // This was not generated - must be a small node stored in the parent. + // In theory we should apply the leafCall here if it's not nil(embedded + // node usually contains value). But small value(less than 32bytes) is + // not our target. + return n } else { // We have the hash already, estimate the RLP encoding-size of the node. // The size is used for mem tracking, does not need to be exact @@ -194,10 +189,9 @@ func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren boo // The leaf channel will be active only when there an active leaf-callback if c.leafCh != nil { c.leafCh <- &leaf{ - size: size, - hash: common.BytesToHash(hash), - node: n, - vnodes: hasVnodeChildren, + size: size, + hash: common.BytesToHash(hash), + node: n, } } else if db != nil { // No leaf-callback used, but there's still a database. Do serial @@ -209,30 +203,30 @@ func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren boo return hash } -// commitLoop does the actual insert + leaf callback for nodes +// commitLoop does the actual insert + leaf callback for nodes. func (c *committer) commitLoop(db *Database) { for item := range c.leafCh { var ( - hash = item.hash - size = item.size - n = item.node - hasVnodes = item.vnodes + hash = item.hash + size = item.size + n = item.node ) // We are pooling the trie nodes into an intermediate memory cache db.lock.Lock() db.insert(hash, size, n) db.lock.Unlock() - if c.onleaf != nil && hasVnodes { + + if c.onleaf != nil { switch n := n.(type) { case *shortNode: if child, ok := n.Val.(valueNode); ok { c.onleaf(nil, child, hash) } case *fullNode: - for i := 0; i < 16; i++ { - if child, ok := n.Children[i].(valueNode); ok { - c.onleaf(nil, child, hash) - } + // For children in range [0, 15], it's impossible + // to contain valuenode. Only check the 17th child. + if n.Children[16] != nil { + c.onleaf(nil, n.Children[16].(valueNode), hash) } } } diff --git a/trie/database.go b/trie/database.go index 87ce25d19e..d2d652b6ba 100644 --- a/trie/database.go +++ b/trie/database.go @@ -99,6 +99,11 @@ type rawNode []byte func (n rawNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") } func (n rawNode) fstring(ind string) string { panic("this should never end up in a live trie") } +func (n rawNode) EncodeRLP(w io.Writer) error { + _, err := w.Write([]byte(n)) + return err +} + // rawFullNode represents only the useful data content of a full node, with the // caches and flags stripped out to minimize its data storage. This type honors // the same RLP encoding as the original parent. @@ -202,7 +207,7 @@ func forGatherChildren(n node, onChild func(hash common.Hash)) { } case hashNode: onChild(common.BytesToHash(n)) - case valueNode, nil: + case valueNode, nil, rawNode: default: panic(fmt.Sprintf("unknown node type: %T", n)) } diff --git a/trie/encoding.go b/trie/encoding.go index 1955a3e664..8ee0022ef3 100644 --- a/trie/encoding.go +++ b/trie/encoding.go @@ -51,6 +51,35 @@ func hexToCompact(hex []byte) []byte { return buf } +// hexToCompactInPlace places the compact key in input buffer, returning the length +// needed for the representation +func hexToCompactInPlace(hex []byte) int { + var ( + hexLen = len(hex) // length of the hex input + firstByte = byte(0) + ) + // Check if we have a terminator there + if hexLen > 0 && hex[hexLen-1] == 16 { + firstByte = 1 << 5 + hexLen-- // last part was the terminator, ignore that + } + var ( + binLen = hexLen/2 + 1 + ni = 0 // index in hex + bi = 1 // index in bin (compact) + ) + if hexLen&1 == 1 { + firstByte |= 1 << 4 // odd flag + firstByte |= hex[0] // first nibble is contained in the first byte + ni++ + } + for ; ni < hexLen; bi, ni = bi+1, ni+2 { + hex[bi] = hex[ni]<<4 | hex[ni+1] + } + hex[0] = firstByte + return binLen +} + func compactToHex(compact []byte) []byte { if len(compact) == 0 { return compact diff --git a/trie/encoding_test.go b/trie/encoding_test.go index 97d8da1361..16393313f7 100644 --- a/trie/encoding_test.go +++ b/trie/encoding_test.go @@ -18,6 +18,8 @@ package trie import ( "bytes" + "encoding/hex" + "math/rand" "testing" ) @@ -75,6 +77,40 @@ func TestHexKeybytes(t *testing.T) { } } +func TestHexToCompactInPlace(t *testing.T) { + for i, keyS := range []string{ + "00", + "060a040c0f000a090b040803010801010900080d090a0a0d0903000b10", + "10", + } { + hexBytes, _ := hex.DecodeString(keyS) + exp := hexToCompact(hexBytes) + sz := hexToCompactInPlace(hexBytes) + got := hexBytes[:sz] + if !bytes.Equal(exp, got) { + t.Fatalf("test %d: encoding err\ninp %v\ngot %x\nexp %x\n", i, keyS, got, exp) + } + } +} + +func TestHexToCompactInPlaceRandom(t *testing.T) { + for i := 0; i < 10000; i++ { + l := rand.Intn(128) + key := make([]byte, l) + rand.Read(key) + hexBytes := keybytesToHex(key) + hexOrig := []byte(string(hexBytes)) + exp := hexToCompact(hexBytes) + sz := hexToCompactInPlace(hexBytes) + got := hexBytes[:sz] + + if !bytes.Equal(exp, got) { + t.Fatalf("encoding err \ncpt %x\nhex %x\ngot %x\nexp %x\n", + key, hexOrig, got, exp) + } + } +} + func BenchmarkHexToCompact(b *testing.B) { testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/} for i := 0; i < b.N; i++ { diff --git a/trie/hasher.go b/trie/hasher.go index 57cd3e1f36..3a62a2f119 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -66,11 +66,11 @@ func returnHasherToPool(h *hasher) { // hash collapses a node down into a hash node, also returning a copy of the // original node initialized with the computed hash to replace the original one. func (h *hasher) hash(n node, force bool) (hashed node, cached node) { - // We're not storing the node, just hashing, use available cached data + // Return the cached hash if it's available if hash, _ := n.cache(); hash != nil { return hash, n } - // Trie not processed yet or needs storage, walk the children + // Trie not processed yet, walk the children switch n := n.(type) { case *shortNode: collapsed, cached := h.hashShortNodeChildren(n) diff --git a/trie/stacktrie.go b/trie/stacktrie.go new file mode 100644 index 0000000000..ffccbbf4ac --- /dev/null +++ b/trie/stacktrie.go @@ -0,0 +1,411 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +var ErrCommitDisabled = errors.New("no database for committing") + +var stPool = sync.Pool{ + New: func() interface{} { + return NewStackTrie(nil) + }, +} + +func stackTrieFromPool(db ethdb.KeyValueStore) *StackTrie { + st := stPool.Get().(*StackTrie) + st.db = db + return st +} + +func returnToPool(st *StackTrie) { + st.Reset() + stPool.Put(st) +} + +// StackTrie is a trie implementation that expects keys to be inserted +// in order. Once it determines that a subtree will no longer be inserted +// into, it will hash it and free up the memory it uses. +type StackTrie struct { + nodeType uint8 // node type (as in branch, ext, leaf) + val []byte // value contained by this node if it's a leaf + key []byte // key chunk covered by this (full|ext) node + keyOffset int // offset of the key chunk inside a full key + children [16]*StackTrie // list of children (for fullnodes and exts) + + db ethdb.KeyValueStore // Pointer to the commit db, can be nil +} + +// NewStackTrie allocates and initializes an empty trie. +func NewStackTrie(db ethdb.KeyValueStore) *StackTrie { + return &StackTrie{ + nodeType: emptyNode, + db: db, + } +} + +func newLeaf(ko int, key, val []byte, db ethdb.KeyValueStore) *StackTrie { + st := stackTrieFromPool(db) + st.nodeType = leafNode + st.keyOffset = ko + st.key = append(st.key, key[ko:]...) + st.val = val + return st +} + +func newExt(ko int, key []byte, child *StackTrie, db ethdb.KeyValueStore) *StackTrie { + st := stackTrieFromPool(db) + st.nodeType = extNode + st.keyOffset = ko + st.key = append(st.key, key[ko:]...) + st.children[0] = child + return st +} + +// List all values that StackTrie#nodeType can hold +const ( + emptyNode = iota + branchNode + extNode + leafNode + hashedNode +) + +// TryUpdate inserts a (key, value) pair into the stack trie +func (st *StackTrie) TryUpdate(key, value []byte) error { + k := keybytesToHex(key) + if len(value) == 0 { + panic("deletion not supported") + } + st.insert(k[:len(k)-1], value) + return nil +} + +func (st *StackTrie) Update(key, value []byte) { + if err := st.TryUpdate(key, value); err != nil { + log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) + } +} + +func (st *StackTrie) Reset() { + st.db = nil + st.key = st.key[:0] + st.val = nil + for i := range st.children { + st.children[i] = nil + } + st.nodeType = emptyNode + st.keyOffset = 0 +} + +// Helper function that, given a full key, determines the index +// at which the chunk pointed by st.keyOffset is different from +// the same chunk in the full key. +func (st *StackTrie) getDiffIndex(key []byte) int { + diffindex := 0 + for ; diffindex < len(st.key) && st.key[diffindex] == key[st.keyOffset+diffindex]; diffindex++ { + } + return diffindex +} + +// Helper function to that inserts a (key, value) pair into +// the trie. +func (st *StackTrie) insert(key, value []byte) { + switch st.nodeType { + case branchNode: /* Branch */ + idx := int(key[st.keyOffset]) + // Unresolve elder siblings + for i := idx - 1; i >= 0; i-- { + if st.children[i] != nil { + if st.children[i].nodeType != hashedNode { + st.children[i].hash() + } + break + } + } + // Add new child + if st.children[idx] == nil { + st.children[idx] = stackTrieFromPool(st.db) + st.children[idx].keyOffset = st.keyOffset + 1 + } + st.children[idx].insert(key, value) + case extNode: /* Ext */ + // Compare both key chunks and see where they differ + diffidx := st.getDiffIndex(key) + + // Check if chunks are identical. If so, recurse into + // the child node. Otherwise, the key has to be split + // into 1) an optional common prefix, 2) the fullnode + // representing the two differing path, and 3) a leaf + // for each of the differentiated subtrees. + if diffidx == len(st.key) { + // Ext key and key segment are identical, recurse into + // the child node. + st.children[0].insert(key, value) + return + } + // Save the original part. Depending if the break is + // at the extension's last byte or not, create an + // intermediate extension or use the extension's child + // node directly. + var n *StackTrie + if diffidx < len(st.key)-1 { + n = newExt(diffidx+1, st.key, st.children[0], st.db) + } else { + // Break on the last byte, no need to insert + // an extension node: reuse the current node + n = st.children[0] + } + // Convert to hash + n.hash() + var p *StackTrie + if diffidx == 0 { + // the break is on the first byte, so + // the current node is converted into + // a branch node. + st.children[0] = nil + p = st + st.nodeType = branchNode + } else { + // the common prefix is at least one byte + // long, insert a new intermediate branch + // node. + st.children[0] = stackTrieFromPool(st.db) + st.children[0].nodeType = branchNode + st.children[0].keyOffset = st.keyOffset + diffidx + p = st.children[0] + } + // Create a leaf for the inserted part + o := newLeaf(st.keyOffset+diffidx+1, key, value, st.db) + + // Insert both child leaves where they belong: + origIdx := st.key[diffidx] + newIdx := key[diffidx+st.keyOffset] + p.children[origIdx] = n + p.children[newIdx] = o + st.key = st.key[:diffidx] + + case leafNode: /* Leaf */ + // Compare both key chunks and see where they differ + diffidx := st.getDiffIndex(key) + + // Overwriting a key isn't supported, which means that + // the current leaf is expected to be split into 1) an + // optional extension for the common prefix of these 2 + // keys, 2) a fullnode selecting the path on which the + // keys differ, and 3) one leaf for the differentiated + // component of each key. + if diffidx >= len(st.key) { + panic("Trying to insert into existing key") + } + + // Check if the split occurs at the first nibble of the + // chunk. In that case, no prefix extnode is necessary. + // Otherwise, create that + var p *StackTrie + if diffidx == 0 { + // Convert current leaf into a branch + st.nodeType = branchNode + p = st + st.children[0] = nil + } else { + // Convert current node into an ext, + // and insert a child branch node. + st.nodeType = extNode + st.children[0] = NewStackTrie(st.db) + st.children[0].nodeType = branchNode + st.children[0].keyOffset = st.keyOffset + diffidx + p = st.children[0] + } + + // Create the two child leaves: the one containing the + // original value and the one containing the new value + // The child leave will be hashed directly in order to + // free up some memory. + origIdx := st.key[diffidx] + p.children[origIdx] = newLeaf(diffidx+1, st.key, st.val, st.db) + p.children[origIdx].hash() + + newIdx := key[diffidx+st.keyOffset] + p.children[newIdx] = newLeaf(p.keyOffset+1, key, value, st.db) + + // Finally, cut off the key part that has been passed + // over to the children. + st.key = st.key[:diffidx] + st.val = nil + case emptyNode: /* Empty */ + st.nodeType = leafNode + st.key = key[st.keyOffset:] + st.val = value + case hashedNode: + panic("trying to insert into hash") + default: + panic("invalid type") + } +} + +// hash() hashes the node 'st' and converts it into 'hashedNode', if possible. +// Possible outcomes: +// 1. The rlp-encoded value was >= 32 bytes: +// - Then the 32-byte `hash` will be accessible in `st.val`. +// - And the 'st.type' will be 'hashedNode' +// 2. The rlp-encoded value was < 32 bytes +// - Then the <32 byte rlp-encoded value will be accessible in 'st.val'. +// - And the 'st.type' will be 'hashedNode' AGAIN +// +// This method will also: +// set 'st.type' to hashedNode +// clear 'st.key' +func (st *StackTrie) hash() { + /* Shortcut if node is already hashed */ + if st.nodeType == hashedNode { + return + } + // The 'hasher' is taken from a pool, but we don't actually + // claim an instance until all children are done with their hashing, + // and we actually need one + var h *hasher + + switch st.nodeType { + case branchNode: + var nodes [17]node + for i, child := range st.children { + if child == nil { + nodes[i] = nilValueNode + continue + } + child.hash() + if len(child.val) < 32 { + nodes[i] = rawNode(child.val) + } else { + nodes[i] = hashNode(child.val) + } + st.children[i] = nil // Reclaim mem from subtree + returnToPool(child) + } + nodes[16] = nilValueNode + h = newHasher(false) + defer returnHasherToPool(h) + h.tmp.Reset() + if err := rlp.Encode(&h.tmp, nodes); err != nil { + panic(err) + } + case extNode: + h = newHasher(false) + defer returnHasherToPool(h) + h.tmp.Reset() + st.children[0].hash() + // This is also possible: + //sz := hexToCompactInPlace(st.key) + //n := [][]byte{ + // st.key[:sz], + // st.children[0].val, + //} + n := [][]byte{ + hexToCompact(st.key), + st.children[0].val, + } + if err := rlp.Encode(&h.tmp, n); err != nil { + panic(err) + } + returnToPool(st.children[0]) + st.children[0] = nil // Reclaim mem from subtree + case leafNode: + h = newHasher(false) + defer returnHasherToPool(h) + h.tmp.Reset() + st.key = append(st.key, byte(16)) + sz := hexToCompactInPlace(st.key) + n := [][]byte{st.key[:sz], st.val} + if err := rlp.Encode(&h.tmp, n); err != nil { + panic(err) + } + case emptyNode: + st.val = st.val[:0] + st.val = append(st.val, emptyRoot[:]...) + st.key = st.key[:0] + st.nodeType = hashedNode + return + default: + panic("Invalid node type") + } + st.key = st.key[:0] + st.nodeType = hashedNode + if len(h.tmp) < 32 { + st.val = st.val[:0] + st.val = append(st.val, h.tmp...) + return + } + // Going to write the hash to the 'val'. Need to ensure it's properly sized first + // Typically, 'branchNode's will have no 'val', and require this allocation + if required := 32 - len(st.val); required > 0 { + buf := make([]byte, required) + st.val = append(st.val, buf...) + } + st.val = st.val[:32] + h.sha.Reset() + h.sha.Write(h.tmp) + h.sha.Read(st.val) + if st.db != nil { + // TODO! Is it safe to Put the slice here? + // Do all db implementations copy the value provided? + st.db.Put(st.val, h.tmp) + } +} + +// Hash returns the hash of the current node +func (st *StackTrie) Hash() (h common.Hash) { + st.hash() + if len(st.val) != 32 { + // If the node's RLP isn't 32 bytes long, the node will not + // be hashed, and instead contain the rlp-encoding of the + // node. For the top level node, we need to force the hashing. + ret := make([]byte, 32) + h := newHasher(false) + defer returnHasherToPool(h) + h.sha.Reset() + h.sha.Write(st.val) + h.sha.Read(ret) + return common.BytesToHash(ret) + } + return common.BytesToHash(st.val) +} + +// Commit will firstly hash the entrie trie if it's still not hashed +// and then commit all nodes to the associated database. Actually most +// of the trie nodes MAY have been committed already. The main purpose +// here is to commit the root node. +// +// The associated database is expected, otherwise the whole commit +// functionality should be disabled. +func (st *StackTrie) Commit() (common.Hash, error) { + if st.db == nil { + return common.Hash{}, ErrCommitDisabled + } + st.hash() + h := common.BytesToHash(st.val) + return h, nil +} diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go new file mode 100644 index 0000000000..26e3bade27 --- /dev/null +++ b/trie/stacktrie_test.go @@ -0,0 +1,242 @@ +package trie + +import ( + "bytes" + "fmt" + "math/big" + mrand "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +func TestSizeBug(t *testing.T) { + st := NewStackTrie(nil) + nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + + leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + + nt.TryUpdate(leaf, value) + st.TryUpdate(leaf, value) + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +func TestEmptyBug(t *testing.T) { + st := NewStackTrie(nil) + nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + + //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + kvs := []struct { + K string + V string + }{ + {K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "9496f4ec2bf9dab484cac6be589e8417d84781be08"}, + {K: "40edb63a35fcf86c08022722aa3287cdd36440d671b4918131b2514795fefa9c", V: "01"}, + {K: "b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", V: "947a30f7736e48d6599356464ba4c150d8da0302ff"}, + {K: "c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b", V: "02"}, + } + + for _, kv := range kvs { + nt.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + st.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + } + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +func TestValLength56(t *testing.T) { + st := NewStackTrie(nil) + nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + + //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + kvs := []struct { + K string + V string + }{ + {K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"}, + } + + for _, kv := range kvs { + nt.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + st.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + } + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +func genTxs(num uint64) (types.Transactions, error) { + key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + if err != nil { + return nil, err + } + var addr = crypto.PubkeyToAddress(key.PublicKey) + newTx := func(i uint64) (*types.Transaction, error) { + signer := types.NewEIP155Signer(big.NewInt(18)) + tx, err := types.SignTx(types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil), signer, key) + return tx, err + } + var txs types.Transactions + for i := uint64(0); i < num; i++ { + tx, err := newTx(i) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + return txs, nil +} + +func TestDeriveSha(t *testing.T) { + txs, err := genTxs(0) + if err != nil { + t.Fatal(err) + } + for len(txs) < 1000 { + exp := types.DeriveSha(txs, newEmpty()) + got := types.DeriveSha(txs, NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) + } + newTxs, err := genTxs(uint64(len(txs) + 1)) + if err != nil { + t.Fatal(err) + } + txs = append(txs, newTxs...) + } +} + +func BenchmarkDeriveSha200(b *testing.B) { + txs, err := genTxs(200) + if err != nil { + b.Fatal(err) + } + var exp common.Hash + var got common.Hash + b.Run("std_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + exp = types.DeriveSha(txs, newEmpty()) + } + }) + + b.Run("stack_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + got = types.DeriveSha(txs, NewStackTrie(nil)) + } + }) + if got != exp { + b.Errorf("got %x exp %x", got, exp) + } +} + +type dummyDerivableList struct { + len int + seed int +} + +func newDummy(seed int) *dummyDerivableList { + d := &dummyDerivableList{} + src := mrand.NewSource(int64(seed)) + // don't use lists longer than 4K items + d.len = int(src.Int63() & 0x0FFF) + d.seed = seed + return d +} + +func (d *dummyDerivableList) Len() int { + return d.len +} + +func (d *dummyDerivableList) GetRlp(i int) []byte { + src := mrand.NewSource(int64(d.seed + i)) + // max item size 256, at least 1 byte per item + size := 1 + src.Int63()&0x00FF + data := make([]byte, size) + _, err := mrand.New(src).Read(data) + if err != nil { + panic(err) + } + return data +} + +func printList(l types.DerivableList) { + fmt.Printf("list length: %d\n", l.Len()) + fmt.Printf("{\n") + for i := 0; i < l.Len(); i++ { + v := l.GetRlp(i) + fmt.Printf("\"0x%x\",\n", v) + } + fmt.Printf("},\n") +} + +func TestFuzzDeriveSha(t *testing.T) { + // increase this for longer runs -- it's set to quite low for travis + rndSeed := mrand.Int() + for i := 0; i < 10; i++ { + seed := rndSeed + i + exp := types.DeriveSha(newDummy(i), newEmpty()) + got := types.DeriveSha(newDummy(i), NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + printList(newDummy(seed)) + t.Fatalf("seed %d: got %x exp %x", seed, got, exp) + } + } +} + +type flatList struct { + rlpvals []string +} + +func newFlatList(rlpvals []string) *flatList { + return &flatList{rlpvals} +} +func (f *flatList) Len() int { + return len(f.rlpvals) +} +func (f *flatList) GetRlp(i int) []byte { + return hexutil.MustDecode(f.rlpvals[i]) +} + +// TestDerivableList contains testcases found via fuzzing +func TestDerivableList(t *testing.T) { + type tcase []string + tcs := []tcase{ + { + "0xc041", + }, + { + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + }, + { + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + "0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a", + "0x14abd5c47c0be87b0454596baad2", + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + }, + } + for i, tc := range tcs[1:] { + exp := types.DeriveSha(newFlatList(tc), newEmpty()) + got := types.DeriveSha(newFlatList(tc), NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + t.Fatalf("case %d: got %x exp %x", i, got, exp) + } + } +} diff --git a/trie/trie.go b/trie/trie.go index 1e1749a4ff..6ddbbd78d3 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -505,13 +505,16 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { if t.root == nil { return emptyRoot, nil } + // Derive the hash for all dirty nodes first. We hold the assumption + // in the following procedure that all nodes are hashed. rootHash := t.Hash() h := newCommitter() defer returnCommitterToPool(h) + // Do a quick check if we really need to commit, before we spin // up goroutines. This can happen e.g. if we load a trie for reading storage // values, but don't write to it. - if !h.commitNeeded(t.root) { + if _, dirty := t.root.cache(); !dirty { return rootHash, nil } var wg sync.WaitGroup diff --git a/trie/trie_test.go b/trie/trie_test.go index 2356b7a746..539451fbf4 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -19,7 +19,9 @@ package trie import ( "bytes" "encoding/binary" + "errors" "fmt" + "hash" "io/ioutil" "math/big" "math/rand" @@ -31,9 +33,11 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/leveldb" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" ) func init() { @@ -659,6 +663,196 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) { return addresses, accounts } +// spongeDb is a dummy db backend which accumulates writes in a sponge +type spongeDb struct { + sponge hash.Hash + id string + journal []string +} + +func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") } +func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") } +func (s *spongeDb) Delete(key []byte) error { panic("implement me") } +func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} } +func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") } +func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } +func (s *spongeDb) Close() error { return nil } +func (s *spongeDb) Put(key []byte, value []byte) error { + valbrief := value + if len(valbrief) > 8 { + valbrief = valbrief[:8] + } + s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, key[:8], len(value), valbrief)) + s.sponge.Write(key) + s.sponge.Write(value) + return nil +} +func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") } + +// spongeBatch is a dummy batch which immediately writes to the underlying spongedb +type spongeBatch struct { + db *spongeDb +} + +func (b *spongeBatch) Put(key, value []byte) error { + b.db.Put(key, value) + return nil +} +func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } +func (b *spongeBatch) ValueSize() int { return 100 } +func (b *spongeBatch) Write() error { return nil } +func (b *spongeBatch) Reset() {} +func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil } + +// TestCommitSequence tests that the trie.Commit operation writes the elements of the trie +// in the expected order, and calls the callbacks in the expected order. +// The test data was based on the 'master' code, and is basically random. It can be used +// to check whether changes to the trie modifies the write order or data in any way. +func TestCommitSequence(t *testing.T) { + for i, tc := range []struct { + count int + expWriteSeqHash []byte + expCallbackSeqHash []byte + }{ + {20, common.FromHex("68c495e45209e243eb7e4f4e8ca8f9f7be71003bd9cafb8061b4534373740193"), + common.FromHex("01783213033d6b7781a641ab499e680d959336d025ac16f44d02f4f0c021bbf5")}, + {200, common.FromHex("3b20d16c13c4bc3eb3b8d0ad7a169fef3b1600e056c0665895d03d3d2b2ff236"), + common.FromHex("fb8db0ec82e8f02729f11228940885b181c3047ab0d654ed0110291ca57111a8")}, + {2000, common.FromHex("34eff3d1048bebdf77e9ae8bd939f2e7c742edc3dcd1173cff1aad9dbd20451a"), + common.FromHex("1c981604b1a9f8ffa40e0ae66b14830a87f5a4ed8345146a3912e6b2dcb05e63")}, + } { + addresses, accounts := makeAccounts(tc.count) + // This spongeDb is used to check the sequence of disk-db-writes + s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} + db := NewDatabase(s) + trie, _ := New(common.Hash{}, db) + // Another sponge is used to check the callback-sequence + callbackSponge := sha3.NewLegacyKeccak256() + // Fill the trie with elements + for i := 0; i < tc.count; i++ { + trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + // Flush trie -> database + root, _ := trie.Commit(nil) + // Flush memdb -> disk (sponge) + db.Commit(root, false, func(c common.Hash) { + // And spongify the callback-order + callbackSponge.Write(c[:]) + }) + if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) + } + if got, exp := callbackSponge.Sum(nil), tc.expCallbackSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp) + } + } +} + +// TestCommitSequenceRandomBlobs is identical to TestCommitSequence +// but uses random blobs instead of 'accounts' +func TestCommitSequenceRandomBlobs(t *testing.T) { + for i, tc := range []struct { + count int + expWriteSeqHash []byte + expCallbackSeqHash []byte + }{ + {20, common.FromHex("8e4a01548551d139fa9e833ebc4e66fc1ba40a4b9b7259d80db32cff7b64ebbc"), + common.FromHex("450238d73bc36dc6cc6f926987e5428535e64be403877c4560e238a52749ba24")}, + {200, common.FromHex("6869b4e7b95f3097a19ddb30ff735f922b915314047e041614df06958fc50554"), + common.FromHex("0ace0b03d6cb8c0b82f6289ef5b1a1838306b455a62dafc63cada8e2924f2550")}, + {2000, common.FromHex("444200e6f4e2df49f77752f629a96ccf7445d4698c164f962bbd85a0526ef424"), + common.FromHex("117d30dafaa62a1eed498c3dfd70982b377ba2b46dd3e725ed6120c80829e518")}, + } { + prng := rand.New(rand.NewSource(int64(i))) + // This spongeDb is used to check the sequence of disk-db-writes + s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} + db := NewDatabase(s) + trie, _ := New(common.Hash{}, db) + // Another sponge is used to check the callback-sequence + callbackSponge := sha3.NewLegacyKeccak256() + // Fill the trie with elements + for i := 0; i < tc.count; i++ { + key := make([]byte, 32) + var val []byte + // 50% short elements, 50% large elements + if prng.Intn(2) == 0 { + val = make([]byte, 1+prng.Intn(32)) + } else { + val = make([]byte, 1+prng.Intn(4096)) + } + prng.Read(key) + prng.Read(val) + trie.Update(key, val) + } + // Flush trie -> database + root, _ := trie.Commit(nil) + // Flush memdb -> disk (sponge) + db.Commit(root, false, func(c common.Hash) { + // And spongify the callback-order + callbackSponge.Write(c[:]) + }) + if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) + } + if got, exp := callbackSponge.Sum(nil), tc.expCallbackSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp) + } + } +} + +func TestCommitSequenceStackTrie(t *testing.T) { + for count := 1; count < 200; count++ { + prng := rand.New(rand.NewSource(int64(count))) + // This spongeDb is used to check the sequence of disk-db-writes + s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} + db := NewDatabase(s) + trie, _ := New(common.Hash{}, db) + // Another sponge is used for the stacktrie commits + stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} + stTrie := NewStackTrie(stackTrieSponge) + // Fill the trie with elements + for i := 1; i < count; i++ { + // For the stack trie, we need to do inserts in proper order + key := make([]byte, 32) + binary.BigEndian.PutUint64(key, uint64(i)) + var val []byte + // 50% short elements, 50% large elements + if prng.Intn(2) == 0 { + val = make([]byte, 1+prng.Intn(32)) + } else { + val = make([]byte, 1+prng.Intn(1024)) + } + prng.Read(val) + trie.TryUpdate(key, common.CopyBytes(val)) + stTrie.TryUpdate(key, common.CopyBytes(val)) + } + // Flush trie -> database + root, _ := trie.Commit(nil) + // Flush memdb -> disk (sponge) + db.Commit(root, false, nil) + // And flush stacktrie -> disk + stRoot, err := stTrie.Commit() + if err != nil { + t.Fatalf("Failed to commit stack trie %v", err) + } + if stRoot != root { + t.Fatalf("root wrong, got %x exp %x", stRoot, root) + } + if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) { + // Show the journal + t.Logf("Expected:") + for i, v := range s.journal { + t.Logf("op %d: %v", i, v) + } + t.Logf("Stacktrie:") + for i, v := range stackTrieSponge.journal { + t.Logf("op %d: %v", i, v) + } + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", count, got, exp) + } + } +} + // BenchmarkCommitAfterHashFixedSize benchmarks the Commit (after Hash) of a fixed number of updates to a trie. // This benchmark is meant to capture the difference on efficiency of small versus large changes. Typically, // storage tries are small (a couple of entries), whereas the full post-block account trie update is large (a couple