diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 96911c336..df3e9a224 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,14 +8,14 @@ jobs: name: docker runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v1 - - name: Version - id: version - run: echo ::set-output name=tag::$(echo ${GITHUB_REF:10}) - - name: Docker - run: | - docker login ghcr.io -u ${{github.actor}} -p ${{secrets.GITHUB_TOKEN}} - docker build . -t ghcr.io/core-coin/go-core:${{steps.version.outputs.tag}} -t ghcr.io/core-coin/go-core:latest - docker push ghcr.io/core-coin/go-core:${{steps.version.outputs.tag}} - docker push ghcr.io/core-coin/go-core:latest + - name: Checkout + uses: actions/checkout@v1 + - name: Version + id: version + run: echo ::set-output name=tag::$(echo ${GITHUB_REF:10}) + - name: Docker + run: | + docker login ghcr.io -u ${{github.actor}} -p ${{secrets.GITHUB_TOKEN}} + docker build . -t ghcr.io/core-coin/go-core:${{steps.version.outputs.tag}} -t ghcr.io/core-coin/go-core:latest + docker push ghcr.io/core-coin/go-core:${{steps.version.outputs.tag}} + docker push ghcr.io/core-coin/go-core:latest diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index 0401f159b..48013fee4 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -18,8 +18,10 @@ package bind import ( "errors" + "github.com/core-coin/go-core/log" "io" "io/ioutil" + "math/big" eddsa "github.com/core-coin/go-goldilocks" @@ -31,9 +33,18 @@ import ( "github.com/core-coin/go-core/crypto" ) +// ErrNoNetworkID is returned whenever the user failed to specify a network id. +var ErrNoNetworkID = errors.New("no network id specified") + +// ErrNotAuthorized is returned when an account is not properly unlocked. +var ErrNotAuthorized = errors.New("not authorized to sign this account") + // NewTransactor is a utility method to easily create a transaction signer from // an encrypted json key stream and the associated passphrase. +// +// Deprecated: Use NewTransactorWithNetworkID instead. func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { + log.Warn("WARNING: NewTransactor has been deprecated in favour of NewTransactorWithNetworkID") json, err := ioutil.ReadAll(keyin) if err != nil { return nil, err @@ -46,13 +57,17 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { } // NewKeyStoreTransactor is a utility method to easily create a transaction signer from -// an decrypted key from a keystore +// an decrypted key from a keystore. +// +// Deprecated: Use NewKeyStoreTransactorWithNetworkID instead. func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { + log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithNetworkID") + signer := types.NucleusSigner{} return &TransactOpts{ From: account.Address, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { if address != account.Address { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } tx.SetNetworkID(uint(signer.NetworkID())) signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) @@ -66,14 +81,18 @@ func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account // NewKeyedTransactor is a utility method to easily create a transaction signer // from a single private key. +// +// Deprecated: Use NewKeyedTransactorWithNetworkID instead. func NewKeyedTransactor(key *eddsa.PrivateKey) *TransactOpts { + log.Warn("WARNING: NewKeyedTransactor has been deprecated in favour of NewKeyedTransactorWithNetworkID") pub := eddsa.Ed448DerivePublicKey(*key) keyAddr := crypto.PubkeyToAddress(pub) + signer := types.NucleusSigner{} return &TransactOpts{ From: keyAddr, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { if address != keyAddr { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } tx.SetNetworkID(uint(signer.NetworkID())) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) @@ -85,14 +104,76 @@ func NewKeyedTransactor(key *eddsa.PrivateKey) *TransactOpts { } } +// NewTransactorWithNetworkID is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +func NewTransactorWithNetworkID(keyin io.Reader, passphrase string, networkID *big.Int) (*TransactOpts, error) { + json, err := ioutil.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactorWithNetworkID(key.PrivateKey, networkID) +} + +// NewKeyStoreTransactorWithNetworkID is a utility method to easily create a transaction signer from +// an decrypted key from a keystore. +func NewKeyStoreTransactorWithNetworkID(keystore *keystore.KeyStore, account accounts.Account, networkID *big.Int) (*TransactOpts, error) { + if networkID == nil { + return nil, ErrNoNetworkID + } + signer := types.NewNucleusSigner(networkID) + return &TransactOpts{ + From: account.Address, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + tx.SetNetworkID(uint(signer.NetworkID())) + signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + }, nil +} + +// NewKeyedTransactorWithNetworkID is a utility method to easily create a transaction signer +// from a single private key. +func NewKeyedTransactorWithNetworkID(key *eddsa.PrivateKey, networkID *big.Int) (*TransactOpts, error) { + pub := eddsa.Ed448DerivePublicKey(*key) + keyAddr := crypto.PubkeyToAddress(pub) + if networkID == nil { + return nil, ErrNoNetworkID + } + signer := types.NewNucleusSigner(networkID) + return &TransactOpts{ + From: keyAddr, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != keyAddr { + return nil, ErrNotAuthorized + } + tx.SetNetworkID(uint(signer.NetworkID())) + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + }, nil +} + // NewClefTransactor is a utility method to easily create a transaction signer // with a clef backend. func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts { return &TransactOpts{ From: account.Address, - Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { if address != account.Address { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } return clef.SignTx(account, transaction, nil) // Clef enforces its own network id }, diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index fc1e6963a..dc876dd00 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "github.com/core-coin/go-core/accounts/abi" + "github.com/core-coin/go-core/common/hexutil" "math/big" "sync" "time" @@ -72,8 +73,9 @@ type SimulatedBackend struct { // NewSimulatedBackendWithDatabase creates a new binding backend based on the given database // and uses a simulated blockchain for testing purposes. +// A simulated backend always uses chainID 1337. func NewSimulatedBackendWithDatabase(database xcbdb.Database, alloc core.GenesisAlloc, energyLimit uint64) *SimulatedBackend { - genesis := core.Genesis{Config: params.AllCryptoreProtocolChanges, EnergyLimit: energyLimit, Alloc: alloc} + genesis := core.Genesis{Config: params.DevChainConfig, EnergyLimit: energyLimit, Alloc: alloc, Coinbase: core.DefaultCoinbaseMainnet} genesis.MustCommit(database) blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, cryptore.NewFaker(), vm.Config{}, nil, nil) @@ -89,6 +91,7 @@ func NewSimulatedBackendWithDatabase(database xcbdb.Database, alloc core.Genesis // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. +// A simulated backend always uses networkID 1337. func NewSimulatedBackend(alloc core.GenesisAlloc, energyLimit uint64) *SimulatedBackend { return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, energyLimit) } @@ -120,7 +123,9 @@ func (b *SimulatedBackend) Rollback() { } func (b *SimulatedBackend) rollback() { - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), cryptore.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), cryptore.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + block.SetCoinbase(core.DefaultCoinbaseMainnet) + }) statedb, _ := b.blockchain.State() b.pendingBlock = blocks[0] @@ -337,6 +342,36 @@ func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Ad return b.pendingState.GetCode(contract), nil } +func newRevertError(result *core.ExecutionResult) *revertError { + reason, errUnpack := abi.UnpackRevert(result.Revert()) + err := errors.New("execution reverted") + if errUnpack == nil { + err = fmt.Errorf("execution reverted: %v", reason) + } + return &revertError{ + error: err, + reason: hexutil.Encode(result.Revert()), + } +} + +// revertError is an API error that encompassas an EVM revertal with JSON error +// code and a binary data blob. +type revertError struct { + error + reason string // revert reason hex encoded +} + +// ErrorCode returns the JSON error code for a revertal. +// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal +func (e *revertError) ErrorCode() int { + return 3 +} + +// ErrorData returns the hex encoded revert reason. +func (e *revertError) ErrorData() interface{} { + return e.reason +} + // CallContract executes a contract call. func (b *SimulatedBackend) CallContract(ctx context.Context, call gocore.CallMsg, blockNumber *big.Int) ([]byte, error) { b.mu.Lock() @@ -353,7 +388,12 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call gocore.CallMsg if err != nil { return nil, err } - return res.Return(), nil + + // If the result contains a revert reason, try to unpack and return it. + if len(res.Revert()) > 0 { + return nil, newRevertError(res) + } + return res.Return(), res.Err } // PendingCallContract executes a contract call on the pending state. @@ -366,7 +406,11 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call gocore. if err != nil { return nil, err } - return res.Return(), nil + // If the result contains a revert reason, try to unpack and return it. + if len(res.Revert()) > 0 { + return nil, newRevertError(res) + } + return res.Return(), res.Err } // PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving @@ -444,16 +488,10 @@ func (b *SimulatedBackend) EstimateEnergy(ctx context.Context, call gocore.CallM } if failed { if result != nil && result.Err != vm.ErrOutOfEnergy { - errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err) if len(result.Revert()) > 0 { - ret, err := abi.UnpackRevert(result.Revert()) - if err != nil { - errMsg += fmt.Sprintf(" (%#x)", result.Revert()) - } else { - errMsg += fmt.Sprintf(" (%s)", ret) - } + return 0, newRevertError(result) } - return 0, errors.New(errMsg) + return 0, result.Err } // Otherwise, the specified energy cap is too low return 0, fmt.Errorf("energy required exceeds allowance (%d)", cap) @@ -506,6 +544,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa } blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), cryptore.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + block.SetCoinbase(core.DefaultCoinbaseMainnet) for _, tx := range b.pendingBlock.Transactions() { block.AddTxWithChain(b.blockchain, tx) } @@ -619,6 +658,7 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { defer b.mu.Unlock() blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), cryptore.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + block.SetCoinbase(core.DefaultCoinbaseMainnet) for _, tx := range b.pendingBlock.Transactions() { block.AddTx(tx) } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index b6674a7ec..a2a00050e 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -23,6 +23,7 @@ import ( "errors" eddsa "github.com/core-coin/go-goldilocks" "math/big" + "reflect" "strings" "testing" "time" @@ -40,7 +41,7 @@ import ( func TestSimulatedBackend(t *testing.T) { var energyLimit uint64 = 8000029 key, _ := crypto.GenerateKey(rand.Reader) // nolint: gosec - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) genAlloc := make(core.GenesisAlloc) genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)} @@ -108,6 +109,14 @@ const deployedCode = `60806040526004361061001e5760003560e01c80631a96cac114610023 // expected return value contains "hello world" var expectedReturn = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +func simTestBackend(testAddr common.Address) *SimulatedBackend { + return NewSimulatedBackend( + core.GenesisAlloc{ + testAddr: {Balance: big.NewInt(10000000000)}, + }, 10000000, + ) +} + func TestNewSimulatedBackend(t *testing.T) { if failure != nil { t.Error(failure) @@ -115,18 +124,14 @@ func TestNewSimulatedBackend(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) expectedBal := big.NewInt(10000000000) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: expectedBal}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() - if sim.config != params.AllCryptoreProtocolChanges { + if sim.config != params.DevChainConfig { t.Errorf("expected sim config to equal params.AllCryptoreProtocolChanges, got %v", sim.config) } - if sim.blockchain.Config() != params.AllCryptoreProtocolChanges { + if sim.blockchain.Config() != params.DevChainConfig { t.Errorf("expected sim blockchain config to equal params.AllCryptoreProtocolChanges, got %v", sim.config) } @@ -159,11 +164,7 @@ func TestSimulatedBackend_BalanceAt(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) expectedBal := big.NewInt(10000000000) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: expectedBal}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -237,11 +238,7 @@ func TestSimulatedBackend_NonceAt(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -282,11 +279,7 @@ func TestSimulatedBackend_SendTransaction(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -318,11 +311,7 @@ func TestSimulatedBackend_TransactionByHash(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -377,18 +366,21 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { function Valid() public {} }*/ const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" - const contractBin = "608060405234801561001057600080fd5b5061027a806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80633ae247141461005c578063593b9361146100665780636c5fcd821461007057806396be6e031461007a578063a842ede314610084575b600080fd5b61006461008e565b005b61006e6100c9565b005b6100786100ce565b005b6100826100e4565b005b61008c6100e6565b005b6040517f4e401cbe0000000000000000000000000000000000000000000000000000000081526004016100c090610140565b60405180910390fd5b600080fd5b60005b80806100dc9061017b565b9150506100d1565b565b600061011b577f4b1f2ce300000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b565b600061012a600d83610160565b9150610135826101f3565b602082019050919050565b600060208201905081810360008301526101598161011d565b9050919050565b600082825260208201905092915050565b6000819050919050565b600061018682610171565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156101b9576101b86101c4565b5b600182019050919050565b7f4b1f2ce300000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f72657665727420726561736f6e0000000000000000000000000000000000000060008201525056fea26469706673582212202458901c98e418f9c6e0efe666fc13c1536b98db06e04c1d7bae3eede256401f64736f6c63782a302e382e342d646576656c6f702e323032322e372e382b636f6d6d69742e30353336326564342e6d6f64005b" + const contractBin = "608060405234801561001057600080fd5b50610277806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80633ae247141461005c578063593b9361146100665780636c5fcd821461007057806396be6e031461007a578063a842ede314610084575b600080fd5b61006461008e565b005b61006e6100c9565b005b6100786100ce565b005b6100826100e4565b005b61008c6100e6565b005b6040517f4e401cbe0000000000000000000000000000000000000000000000000000000081526004016100c090610140565b60405180910390fd5b600080fd5b60005b80806100dc9061017b565b9150506100d1565b565b600061011b577f4b1f2ce300000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b565b600061012a600d83610160565b9150610135826101f3565b602082019050919050565b600060208201905081810360008301526101598161011d565b9050919050565b600082825260208201905092915050565b6000819050919050565b600061018682610171565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156101b9576101b86101c4565b5b600182019050919050565b7f4b1f2ce300000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f72657665727420726561736f6e0000000000000000000000000000000000000060008201525056fea2646970667358221220a6fc6aa476f9e0ac54c080bfe2449a444d3ff8e7f0af4a847631b09f2022c3ae64736f6c637827302e382e342d646576656c6f702e323032322e382e32322b636f6d6d69742e37383961353965650058" key, _ := crypto.GenerateKey(rand.Reader) pub := eddsa.Ed448DerivePublicKey(*key) addr := crypto.PubkeyToAddress(pub) - opts := bind.NewKeyedTransactor(key) + opts, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Core)}}, 10000000) defer sim.Close() parsed, _ := abi.JSON(strings.NewReader(contractAbi)) - contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim) + contractAddr, _, _, err := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim) + if err != nil { + t.Error(err) + } sim.Commit() var cases = []struct { @@ -396,6 +388,7 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { message gocore.CallMsg expect uint64 expectError error + expectData interface{} }{ {"plain transfer(valid)", gocore.CallMsg{ From: addr, @@ -404,7 +397,7 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { EnergyPrice: big.NewInt(0), Value: big.NewInt(1), Data: nil, - }, params.TxEnergy, nil}, + }, params.TxEnergy, nil, nil}, {"plain transfer(invalid)", gocore.CallMsg{ From: addr, @@ -413,7 +406,7 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { EnergyPrice: big.NewInt(0), Value: big.NewInt(1), Data: nil, - }, 0, errors.New("always failing transaction (execution reverted)")}, + }, 0, errors.New("execution reverted"), nil}, {"Revert", gocore.CallMsg{ From: addr, @@ -422,7 +415,7 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { EnergyPrice: big.NewInt(0), Value: nil, Data: common.Hex2Bytes("3ae24714"), - }, 0, errors.New("always failing transaction (execution reverted) (revert reason)")}, + }, 0, errors.New("execution reverted: revert reason"), "0x4e401cbe0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000"}, {"PureRevert", gocore.CallMsg{ From: addr, @@ -431,7 +424,7 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { EnergyPrice: big.NewInt(0), Value: nil, Data: common.Hex2Bytes("593b9361"), - }, 0, errors.New("always failing transaction (execution reverted)")}, + }, 0, errors.New("execution reverted"), nil}, {"OOG", gocore.CallMsg{ From: addr, @@ -440,16 +433,17 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { EnergyPrice: big.NewInt(0), Value: nil, Data: common.Hex2Bytes("6c5fcd82"), - }, 0, errors.New("energy required exceeds allowance (100000)")}, - - {"Assert", gocore.CallMsg{ - From: addr, - To: &contractAddr, - Energy: 100000, - EnergyPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("a842ede3"), - }, 0, errors.New("always failing transaction (execution reverted) (0x4b1f2ce30000000000000000000000000000000000000000000000000000000000000001)")}, + }, 0, errors.New("energy required exceeds allowance (100000)"), nil}, + + // TODO error2215: fix test + //{"Assert", gocore.CallMsg{ + // From: addr, + // To: &contractAddr, + // Energy: 100000, + // EnergyPrice: big.NewInt(0), + // Value: nil, + // Data: common.Hex2Bytes("a842ede3"), + //}, 0, errors.New("invalid opcode: opcode 0xfe not defined"), nil}, {"Valid", gocore.CallMsg{ From: addr, @@ -458,7 +452,7 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { EnergyPrice: big.NewInt(0), Value: nil, Data: common.Hex2Bytes("96be6e03"), - }, 21252, nil}, + }, 21252, nil, nil}, } for _, c := range cases { got, err := sim.EstimateEnergy(context.Background(), c.message) @@ -469,6 +463,13 @@ func TestSimulatedBackend_EstimateEnergy(t *testing.T) { if c.expectError.Error() != err.Error() { t.Fatalf("Expect error, want %v, got %v", c.expectError, err) } + if c.expectData != nil { + if err, ok := err.(*revertError); !ok { + t.Fatalf("Expect revert error, got %T", err) + } else if !reflect.DeepEqual(err.ErrorData(), c.expectData) { + t.Fatalf("Error data mismatch, want %v, got %v", c.expectData, err.ErrorData()) + } + } continue } if got != c.expect { @@ -481,11 +482,7 @@ func TestSimulatedBackend_HeaderByHash(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -507,11 +504,7 @@ func TestSimulatedBackend_HeaderByNumber(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -559,11 +552,7 @@ func TestSimulatedBackend_TransactionCount(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() currentBlock, err := sim.BlockByNumber(bgCtx, nil) @@ -614,11 +603,7 @@ func TestSimulatedBackend_TransactionInBlock(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -682,11 +667,7 @@ func TestSimulatedBackend_PendingNonceAt(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -749,11 +730,7 @@ func TestSimulatedBackend_TransactionReceipt(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -800,12 +777,7 @@ func TestSimulatedBackend_SuggestEnergyPrice(t *testing.T) { func TestSimulatedBackend_PendingCodeAt(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, - 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() code, err := sim.CodeAt(bgCtx, testAddr, nil) @@ -820,7 +792,7 @@ func TestSimulatedBackend_PendingCodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - auth := bind.NewKeyedTransactor(testKey) + auth, _ := bind.NewKeyedTransactorWithNetworkID(testKey, big.NewInt(1337)) contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) @@ -842,12 +814,7 @@ func TestSimulatedBackend_PendingCodeAt(t *testing.T) { func TestSimulatedBackend_CodeAt(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, - 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() code, err := sim.CodeAt(bgCtx, testAddr, nil) @@ -862,7 +829,7 @@ func TestSimulatedBackend_CodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - auth := bind.NewKeyedTransactor(testKey) + auth, _ := bind.NewKeyedTransactorWithNetworkID(testKey, big.NewInt(1337)) contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) @@ -887,12 +854,7 @@ func TestSimulatedBackend_CodeAt(t *testing.T) { func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { pub := eddsa.Ed448DerivePublicKey(*testKey) testAddr := crypto.PubkeyToAddress(pub) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, - 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -900,7 +862,7 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - contractAuth := bind.NewKeyedTransactor(testKey) + contractAuth, _ := bind.NewKeyedTransactorWithNetworkID(testKey, big.NewInt(1337)) addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v", err) @@ -908,7 +870,7 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { input, err := parsed.Pack("receive", []byte("X")) if err != nil { - t.Errorf("could pack receive function on contract: %v", err) + t.Errorf("could not pack receive function on contract: %v", err) } // make sure you can call the contract in pending state @@ -948,3 +910,114 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res)) } } + +// This test is based on the following contract: +/* +contract Reverter { + function revertString() public pure{ + require(false, "some error"); + } + function revertNoString() public pure { + require(false, ""); + } + function revertASM() public pure { + assembly { + revert(0x0, 0x0) + } + } + function noRevert() public pure { + assembly { + // Assembles something that looks like require(false, "some error") but is not reverted + mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x4, 0x0000000000000000000000000000000000000000000000000000000000000020) + mstore(0x24, 0x000000000000000000000000000000000000000000000000000000000000000a) + mstore(0x44, 0x736f6d65206572726f7200000000000000000000000000000000000000000000) + return(0x0, 0x64) + } + } +}*/ +func TestSimulatedBackend_CallContractRevert(t *testing.T) { + pub := eddsa.Ed448DerivePublicKey(*testKey) + testAddr := crypto.PubkeyToAddress(pub) + sim := simTestBackend(testAddr) + defer sim.Close() + bgCtx := context.Background() + + reverterABI := `[{"inputs":[],"name":"noRevert","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"revertASM","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"revertNoString","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"revertString","outputs":[],"stateMutability":"pure","type":"function"}]` + reverterBin := "608060405234801561001057600080fd5b5061027a806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80631bc682271461005157806323e686fd1461005b578063aa23755d14610065578063db19521a1461006f575b600080fd5b610059610079565b005b6100636100bc565b005b61006d6100ff565b005b610077610104565b005b60006100ba576040517f4e401cbe0000000000000000000000000000000000000000000000000000000081526004016100b1906101c2565b60405180910390fd5b565b60006100fd576040517f4e401cbe0000000000000000000000000000000000000000000000000000000081526004016100f4906101a2565b60405180910390fd5b565b600080fd5b7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f736f6d65206572726f720000000000000000000000000000000000000000000060445260646000f35b60006101696000836101e2565b9150610174826101f3565b600082019050919050565b600061018c600a836101e2565b9150610197826101f6565b602082019050919050565b600060208201905081810360008301526101bb8161015c565b9050919050565b600060208201905081810360008301526101db8161017f565b9050919050565b600082825260208201905092915050565b50565b7f736f6d65206572726f720000000000000000000000000000000000000000000060008201525056fea26469706673582212200ded3e17706b826f4ac3dc836add593cfd265f5c6dd5a76aece39f406aad20d364736f6c637827302e382e342d646576656c6f702e323032322e382e32322b636f6d6d69742e37383961353965650058" + + parsed, err := abi.JSON(strings.NewReader(reverterABI)) + if err != nil { + t.Errorf("could not get code at test addr: %v", err) + } + contractAuth, _ := bind.NewKeyedTransactorWithNetworkID(testKey, big.NewInt(1337)) + addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(reverterBin), sim) + if err != nil { + t.Errorf("could not deploy contract: %v", err) + } + + inputs := make(map[string]interface{}, 3) + inputs["revertASM"] = nil + inputs["revertNoString"] = "" + inputs["revertString"] = "some error" + + call := make([]func([]byte) ([]byte, error), 2) + call[0] = func(input []byte) ([]byte, error) { + return sim.PendingCallContract(bgCtx, gocore.CallMsg{ + From: testAddr, + To: &addr, + Data: input, + }) + } + call[1] = func(input []byte) ([]byte, error) { + return sim.CallContract(bgCtx, gocore.CallMsg{ + From: testAddr, + To: &addr, + Data: input, + }, nil) + } + + // Run pending calls then commit + for _, cl := range call { + for key, val := range inputs { + input, err := parsed.Pack(key) + if err != nil { + t.Errorf("could not pack %v function on contract: %v", key, err) + } + + res, err := cl(input) + if err == nil { + t.Errorf("call to %v was not reverted", key) + } + if res != nil { + t.Errorf("result from %v was not nil: %v", key, res) + } + if val != nil { + rerr, ok := err.(*revertError) + if !ok { + t.Errorf("expect revert error") + } + if rerr.Error() != "execution reverted: "+val.(string) { + t.Errorf("error was malformed: got %v want %v", rerr.Error(), val) + } + } else { + // revert(0x0,0x0) + if err.Error() != "execution reverted" { + t.Errorf("error was malformed: got %v want %v", err, "execution reverted") + } + } + } + input, err := parsed.Pack("noRevert") + if err != nil { + t.Errorf("could not pack noRevert function on contract: %v", err) + } + res, err := cl(input) + if err != nil { + t.Error("call to noRevert was reverted") + } + if res == nil { + t.Errorf("result from noRevert was nil") + } + sim.Commit() + } +} diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 47f4150f6..eaca681e9 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -32,7 +32,7 @@ import ( // SignerFn is a signer function callback when a contract requires a method to // sign the transaction before submission. -type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error) +type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) // CallOpts is the collection of options to fine tune a contract call request. type CallOpts struct { @@ -246,7 +246,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if opts.Signer == nil { return nil, errors.New("no signer to authorize the transaction with") } - signedTx, err := opts.Signer(types.NewNucleusSigner(nil), opts.From, rawTx) + signedTx, err := opts.Signer(opts.From, rawTx) if err != nil { return nil, err } diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 6627e6f88..5e47e3376 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -280,7 +280,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -336,7 +336,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -383,7 +383,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -442,7 +442,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -491,7 +491,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -587,7 +587,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -638,7 +638,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -716,7 +716,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -811,7 +811,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1009,7 +1009,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1145,7 +1145,7 @@ var bindTests = []struct { ` key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1288,7 +1288,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1355,7 +1355,7 @@ var bindTests = []struct { ` // Initialize test accounts key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1452,7 +1452,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer sim.Close() - transactOpts := bind.NewKeyedTransactor(key) + transactOpts, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) _, _, _, err := DeployIdentifierCollision(transactOpts, sim) if err != nil { t.Fatalf("failed to deploy contract: %v", err) @@ -1489,13 +1489,13 @@ var bindTests = []struct { } `, []string{ - `60806040523480156100115760006000fd5b50610017565b6101b5806100266000396000f3fe60806040523480156100115760006000fd5b50600436106100305760003560e01c80639d8a8ba81461003657610030565b60006000fd5b610050600480360361004b91908101906100d1565b610052565b005b5b5056610171565b6000813590506100698161013d565b92915050565b6000604082840312156100825760006000fd5b61008c60406100fb565b9050600061009c848285016100bc565b60008301525060206100b08482850161005a565b60208301525092915050565b6000813590506100cb81610157565b92915050565b6000604082840312156100e45760006000fd5b60006100f28482850161006f565b91505092915050565b6000604051905081810181811067ffffffffffffffff8211171561011f5760006000fd5b8060405250919050565b6000819050919050565b6000819050919050565b61014681610129565b811415156101545760006000fd5b50565b61016081610133565b8114151561016e5760006000fd5b50565bfea365627a7a72315820749274eb7f6c01010d5322af4e1668b0a154409eb7968bd6cae5524c7ed669bb6c6578706572696d656e74616cf564736f6c634300050c0040`, - `60806040523480156100115760006000fd5b50610017565b6101b5806100266000396000f3fe60806040523480156100115760006000fd5b50600436106100305760003560e01c8063db8ba08c1461003657610030565b60006000fd5b610050600480360361004b91908101906100d1565b610052565b005b5b5056610171565b6000813590506100698161013d565b92915050565b6000604082840312156100825760006000fd5b61008c60406100fb565b9050600061009c848285016100bc565b60008301525060206100b08482850161005a565b60208301525092915050565b6000813590506100cb81610157565b92915050565b6000604082840312156100e45760006000fd5b60006100f28482850161006f565b91505092915050565b6000604051905081810181811067ffffffffffffffff8211171561011f5760006000fd5b8060405250919050565b6000819050919050565b6000819050919050565b61014681610129565b811415156101545760006000fd5b50565b61016081610133565b8114151561016e5760006000fd5b50565bfea365627a7a723158209bc28ee7ea97c131a13330d77ec73b4493b5c59c648352da81dd288b021192596c6578706572696d656e74616cf564736f6c634300050c0040`, - `606c6026600b82828239805160001a6073141515601857fe5b30600052607381538281f350fe73000000000000000000000000000000000000000030146080604052600436106023575b60006000fdfea365627a7a72315820518f0110144f5b3de95697d05e456a064656890d08e6f9cff47f3be710cc46a36c6578706572696d656e74616cf564736f6c634300050c0040`, + `608060405234801561001057600080fd5b50610221806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80631d5a0f3a14610030575b600080fd5b61004a600480360381019061004591906100c5565b61004c565b005b50565b60008135905061005e81610198565b92915050565b60006040828403121561007657600080fd5b61008060406100ee565b90506000610090848285016100b0565b60008301525060206100a48482850161004f565b60208301525092915050565b6000813590506100bf816101af565b92915050565b6000604082840312156100d757600080fd5b60006100e584828501610064565b91505092915050565b60006100f8610109565b90506101048282610127565b919050565b6000604051905090565b6000819050919050565b6000819050919050565b61013082610187565b810181811067ffffffffffffffff8211171561014f5761014e610158565b5b80604052505050565b7f4b1f2ce300000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b6101a181610113565b81146101ac57600080fd5b50565b6101b88161011d565b81146101c357600080fd5b5056fea26469706673582212202b7f837fcad4d1f0e5fbda29459d3ce2d219eacce9f106c9a6808eb46890638b64736f6c637827302e382e342d646576656c6f702e323032322e382e32322b636f6d6d69742e37383961353965650058`, + `608060405234801561001057600080fd5b50610221806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80635ca5a69a14610030575b600080fd5b61004a600480360381019061004591906100c5565b61004c565b005b50565b60008135905061005e81610198565b92915050565b60006040828403121561007657600080fd5b61008060406100ee565b90506000610090848285016100b0565b60008301525060206100a48482850161004f565b60208301525092915050565b6000813590506100bf816101af565b92915050565b6000604082840312156100d757600080fd5b60006100e584828501610064565b91505092915050565b60006100f8610109565b90506101048282610127565b919050565b6000604051905090565b6000819050919050565b6000819050919050565b61013082610187565b810181811067ffffffffffffffff8211171561014f5761014e610158565b5b80604052505050565b7f4b1f2ce300000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b6101a181610113565b81146101ac57600080fd5b50565b6101b88161011d565b81146101c357600080fd5b5056fea26469706673582212209958ba244d4c44a2c902dcd8b58dcf986c2496e74e042eb9d973892565fcd89264736f6c637827302e382e342d646576656c6f702e323032322e382e32322b636f6d6d69742e37383961353965650058`, + `607d6050600b82828239805160001a6075146043577f4b1f2ce300000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607581538281f3fe750000000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212203361e723284432509462565df724e59c278ac7f9ef170c8731c1eef7e0e7f52164736f6c637827302e382e342d646576656c6f702e323032322e382e32322b636f6d6d69742e37383961353965650058`, }, []string{ - `[{"constant":true,"inputs":[{"components":[{"internalType":"uint256","name":"f1","type":"uint256"},{"internalType":"bytes32","name":"f2","type":"bytes32"}],"internalType":"struct ExternalLib.SharedStruct","name":"s","type":"tuple"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"}]`, - `[{"constant":true,"inputs":[{"components":[{"internalType":"uint256","name":"f1","type":"uint256"},{"internalType":"bytes32","name":"f2","type":"bytes32"}],"internalType":"struct ExternalLib.SharedStruct","name":"s","type":"tuple"}],"name":"bar","outputs":[],"payable":false,"stateMutability":"pure","type":"function"}]`, + `[{"inputs":[{"components":[{"internalType":"uint256","name":"f1","type":"uint256"},{"internalType":"bytes32","name":"f2","type":"bytes32"}],"internalType":"struct ExternalLib.SharedStruct","name":"s","type":"tuple"}],"name":"foo","outputs":[],"stateMutability":"pure","type":"function"}]`, + `[{"inputs":[{"components":[{"internalType":"uint256","name":"f1","type":"uint256"},{"internalType":"bytes32","name":"f2","type":"bytes32"}],"internalType":"struct ExternalLib.SharedStruct","name":"s","type":"tuple"}],"name":"bar","outputs":[],"stateMutability":"pure","type":"function"}]`, `[]`, }, ` @@ -1517,7 +1517,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer sim.Close() - transactOpts := bind.NewKeyedTransactor(key) + transactOpts, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) _, _, c1, err := DeployContractOne(transactOpts, sim) if err != nil { t.Fatal("Failed to deploy contract") @@ -1528,7 +1528,7 @@ var bindTests = []struct { F2: [32]byte{0x01, 0x02, 0x03}, }) if err != nil { - t.Fatal("Failed to invoke function") + t.Fatal("Failed to invoke function:", err) } _, _, c2, err := DeployContractTwo(transactOpts, sim) if err != nil { @@ -1540,7 +1540,7 @@ var bindTests = []struct { F2: [32]byte{0x01, 0x02, 0x03}, }) if err != nil { - t.Fatal("Failed to invoke function") + t.Fatal("Failed to invoke function:", err) } `, nil, @@ -1575,7 +1575,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey(rand.Reader) - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1648,7 +1648,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 1000000) defer sim.Close() - opts := bind.NewKeyedTransactor(key) + opts, _ := bind.NewKeyedTransactorWithNetworkID(key, big.NewInt(1337)) _, _, c, err := DeployNewFallbacks(opts, sim) if err != nil { t.Fatalf("Failed to deploy contract: %v", err) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 00d135751..4b2459687 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -674,12 +674,10 @@ func DefaultConfigDir() string { appdata := os.Getenv("APPDATA") if appdata != "" { return filepath.Join(appdata, "Signer") - } else { - return filepath.Join(home, "AppData", "Roaming", "Signer") } - } else { - return filepath.Join(home, ".clef") + return filepath.Join(home, "AppData", "Roaming", "Signer") } + return filepath.Join(home, ".clef") } // As we cannot guess a stable location, return empty and handle later return "" diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 43c63be3d..b31cab7b1 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -20,11 +20,14 @@ import ( "crypto/rand" "fmt" "net" + "os" "strings" "time" + "github.com/core-coin/go-core/cmd/devp2p/internal/v4test" "github.com/core-coin/go-core/common" "github.com/core-coin/go-core/crypto" + "github.com/core-coin/go-core/internal/utesting" "github.com/core-coin/go-core/p2p/discover" "github.com/core-coin/go-core/p2p/enode" "github.com/core-coin/go-core/params" @@ -41,6 +44,7 @@ var ( discv4ResolveCommand, discv4ResolveJSONCommand, discv4CrawlCommand, + discv4TestCommand, }, } discv4PingCommand = cli.Command{ @@ -75,6 +79,12 @@ var ( Action: discv4Crawl, Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag}, } + discv4TestCommand = cli.Command{ + Name: "test", + Usage: "Runs tests against a node", + Action: discv4Test, + Flags: []cli.Flag{remoteEnodeFlag, testPatternFlag, testListen1Flag, testListen2Flag}, + } ) var ( @@ -99,6 +109,25 @@ var ( Usage: "Time limit for the crawl.", Value: 30 * time.Minute, } + remoteEnodeFlag = cli.StringFlag{ + Name: "remote", + Usage: "Enode of the remote node under test", + EnvVar: "REMOTE_ENODE", + } + testPatternFlag = cli.StringFlag{ + Name: "run", + Usage: "Pattern of test suite(s) to run", + } + testListen1Flag = cli.StringFlag{ + Name: "listen1", + Usage: "IP address of the first tester", + Value: v4test.Listen1, + } + testListen2Flag = cli.StringFlag{ + Name: "listen2", + Usage: "IP address of the second tester", + Value: v4test.Listen2, + } ) func discv4Ping(ctx *cli.Context) error { @@ -185,6 +214,28 @@ func discv4Crawl(ctx *cli.Context) error { return nil } +func discv4Test(ctx *cli.Context) error { + // Configure test package globals. + if !ctx.IsSet(remoteEnodeFlag.Name) { + return fmt.Errorf("Missing -%v", remoteEnodeFlag.Name) + } + v4test.Remote = ctx.String(remoteEnodeFlag.Name) + v4test.Listen1 = ctx.String(testListen1Flag.Name) + v4test.Listen2 = ctx.String(testListen2Flag.Name) + + // Filter and run test cases. + tests := v4test.AllTests + if ctx.IsSet(testPatternFlag.Name) { + tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) + } + results := utesting.RunTests(tests, os.Stdout) + if fails := utesting.CountFailures(results); fails > 0 { + return fmt.Errorf("%v/%v tests passed.", len(tests)-fails, len(tests)) + } + fmt.Printf("%v/%v passed\n", len(tests), len(tests)) + return nil +} + // startV4 starts an ephemeral discovery V4 node. func startV4(ctx *cli.Context) *discover.UDPv4 { ln, config := makeDiscoveryConfig(ctx) diff --git a/cmd/devp2p/internal/v4test/discv4tests.go b/cmd/devp2p/internal/v4test/discv4tests.go new file mode 100644 index 000000000..e4b0e19d6 --- /dev/null +++ b/cmd/devp2p/internal/v4test/discv4tests.go @@ -0,0 +1,469 @@ +// Copyright 2022 by the Authors +// This file is part of go-core. +// +// go-core 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-core 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-core. If not, see . + +package v4test + +import ( + "bytes" + "crypto/rand" + "fmt" + eddsa "github.com/core-coin/go-goldilocks" + "net" + "reflect" + "time" + + "github.com/core-coin/go-core/crypto" + "github.com/core-coin/go-core/internal/utesting" + "github.com/core-coin/go-core/p2p/discover/v4wire" +) + +const ( + expiration = 20 * time.Second + wrongPacket = 66 + macSize = 256 / 8 +) + +var ( + // Remote node under test + Remote string + // Listen1 IP where the first tester is listening, port will be assigned + Listen1 string = "127.0.0.1" + // Listen2 IP where the second tester is listening, port will be assigned + // Before running the test, you may have to `sudo ifconfig lo0 add 127.0.0.2` (on MacOS at least) + Listen2 string = "127.0.0.2" +) + +type pingWithJunk struct { + Version uint + From, To v4wire.Endpoint + Expiration uint64 + JunkData1 uint + JunkData2 []byte +} + +func (req *pingWithJunk) Name() string { return "PING/v4" } +func (req *pingWithJunk) Kind() byte { return v4wire.PingPacket } + +type pingWrongType struct { + Version uint + From, To v4wire.Endpoint + Expiration uint64 +} + +func (req *pingWrongType) Name() string { return "WRONG/v4" } +func (req *pingWrongType) Kind() byte { return wrongPacket } + +func futureExpiration() uint64 { + return uint64(time.Now().Add(expiration).Unix()) +} + +// This test just sends a PING packet and expects a response. +func BasicPing(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// checkPong verifies that reply is a valid PONG matching the given ping hash. +func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error { + if reply == nil || reply.Kind() != v4wire.PongPacket { + return fmt.Errorf("expected PONG reply, got %v", reply) + } + pong := reply.(*v4wire.Pong) + if !bytes.Equal(pong.ReplyTok, pingHash) { + return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash) + } + wantEndpoint := te.localEndpoint(te.l1) + if !reflect.DeepEqual(pong.To, wantEndpoint) { + return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, wantEndpoint) + } + if v4wire.Expired(pong.Expiration) { + return fmt.Errorf("PONG is expired (%v)", pong.Expiration) + } + return nil +} + +// This test sends a PING packet with wrong 'to' field and expects a PONG response. +func PingWrongTo(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: wrongEndpoint, + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with wrong 'from' field and expects a PONG response. +func PingWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with additional data at the end and expects a PONG +// response. The remote node should respond because EIP-8 mandates ignoring additional +// trailing data. +func PingExtraData(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + pingHash := te.send(te.l1, &pingWithJunk{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + JunkData1: 42, + JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with additional data and wrong 'from' field +// and expects a PONG response. +func PingExtraDataWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + req := pingWithJunk{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + JunkData1: 42, + JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + pingHash := te.send(te.l1, &req) + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with an expiration in the past. +// The remote node should not respond. +func PingPastExpiration(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: -futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no reply, got", reply) + } +} + +// This test sends an invalid packet. The remote node should not respond. +func WrongPacketType(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + te.send(te.l1, &pingWrongType{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no reply, got", reply) + } +} + +// This test verifies that the default behaviour of ignoring 'from' fields is unaffected by +// the bonding process. After bonding, it pings the target with a different from endpoint. +func BondThenPingWithWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test just sends FINDNODE. The remote node should not reply +// because the endpoint proof has not completed. +func FindnodeWithoutEndpointProof(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + req := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(req.Target[:]) + te.send(te.l1, &req) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no response, got", reply) + } +} + +// BasicFindnode sends a FINDNODE request after performing the endpoint +// proof. The remote node should respond. +func BasicFindnode(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read find nodes", err) + } + if reply.Kind() != v4wire.NeighborsPacket { + t.Fatal("Expected neighbors, got", reply.Name()) + } +} + +// This test sends an unsolicited NEIGHBORS packet after the endpoint proof, then sends +// FINDNODE to read the remote table. The remote node should not return the node contained +// in the unsolicited NEIGHBORS packet. +func UnsolicitedNeighbors(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + // Send unsolicited NEIGHBORS response. + fakeKey, _ := crypto.GenerateKey(rand.Reader) + fakePub := eddsa.Ed448DerivePublicKey(*fakeKey) + encFakeKey := v4wire.EncodePubkey(&fakePub) + neighbors := v4wire.Neighbors{ + Expiration: futureExpiration(), + Nodes: []v4wire.Node{{ + ID: encFakeKey, + IP: net.IP{1, 2, 3, 4}, + UDP: 30303, + TCP: 30303, + }}, + } + te.send(te.l1, &neighbors) + + // Check if the remote node included the fake node. + te.send(te.l1, &v4wire.Findnode{ + Expiration: futureExpiration(), + Target: encFakeKey, + }) + + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read find nodes", err) + } + if reply.Kind() != v4wire.NeighborsPacket { + t.Fatal("Expected neighbors, got", reply.Name()) + } + nodes := reply.(*v4wire.Neighbors).Nodes + if contains(nodes, encFakeKey) { + t.Fatal("neighbors response contains node from earlier unsolicited neighbors response") + } +} + +// This test sends FINDNODE with an expiration timestamp in the past. +// The remote node should not respond. +func FindnodePastExpiration(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + findnode := v4wire.Findnode{Expiration: -futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + for { + reply, _, _ := te.read(te.l1) + if reply == nil { + return + } else if reply.Kind() == v4wire.NeighborsPacket { + t.Fatal("Unexpected NEIGHBORS response for expired FINDNODE request") + } + } +} + +// bond performs the endpoint proof with the remote node. +func bond(t *utesting.T, te *testenv) { + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + var gotPing, gotPong bool + for !gotPing || !gotPong { + req, hash, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch req.(type) { + case *v4wire.Ping: + te.send(te.l1, &v4wire.Pong{ + To: te.remoteEndpoint(), + ReplyTok: hash, + Expiration: futureExpiration(), + }) + gotPing = true + case *v4wire.Pong: + // TODO: maybe verify pong data here + gotPong = true + } + } +} + +// This test attempts to perform a traffic amplification attack against a +// 'victim' endpoint using FINDNODE. In this attack scenario, the attacker +// attempts to complete the endpoint proof non-interactively by sending a PONG +// with mismatching reply token from the 'victim' endpoint. The attack works if +// the remote node does not verify the PONG reply token field correctly. The +// attacker could then perform traffic amplification by sending many FINDNODE +// requests to the discovery node, which would reply to the 'victim' address. +func FindnodeAmplificationInvalidPongHash(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + // Send PING to start endpoint verification. + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + var gotPing, gotPong bool + for !gotPing || !gotPong { + req, _, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch req.(type) { + case *v4wire.Ping: + // Send PONG from this node ID, but with invalid ReplyTok. + te.send(te.l1, &v4wire.Pong{ + To: te.remoteEndpoint(), + ReplyTok: make([]byte, macSize), + Expiration: futureExpiration(), + }) + gotPing = true + case *v4wire.Pong: + gotPong = true + } + } + + // Now send FINDNODE. The remote node should not respond because our + // PONG did not reference the PING hash. + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + // If we receive a NEIGHBORS response, the attack worked and the test fails. + reply, _, _ := te.read(te.l1) + if reply != nil && reply.Kind() == v4wire.NeighborsPacket { + t.Error("Got neighbors") + } +} + +// This test attempts to perform a traffic amplification attack using FINDNODE. +// The attack works if the remote node does not verify the IP address of FINDNODE +// against the endpoint verification proof done by PING/PONG. +func FindnodeAmplificationWrongIP(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + // Do the endpoint proof from the l1 IP. + bond(t, te) + + // Now send FINDNODE from the same node ID, but different IP address. + // The remote node should not respond. + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l2, &findnode) + + // If we receive a NEIGHBORS response, the attack worked and the test fails. + reply, _, _ := te.read(te.l2) + if reply != nil { + t.Error("Got NEIGHORS response for FINDNODE from wrong IP") + } +} + +var AllTests = []utesting.Test{ + {Name: "Ping/Basic", Fn: BasicPing}, + {Name: "Ping/WrongTo", Fn: PingWrongTo}, + {Name: "Ping/WrongFrom", Fn: PingWrongFrom}, + {Name: "Ping/ExtraData", Fn: PingExtraData}, + {Name: "Ping/ExtraDataWrongFrom", Fn: PingExtraDataWrongFrom}, + {Name: "Ping/PastExpiration", Fn: PingPastExpiration}, + {Name: "Ping/WrongPacketType", Fn: WrongPacketType}, + {Name: "Ping/BondThenPingWithWrongFrom", Fn: BondThenPingWithWrongFrom}, + {Name: "Findnode/WithoutEndpointProof", Fn: FindnodeWithoutEndpointProof}, + {Name: "Findnode/BasicFindnode", Fn: BasicFindnode}, + {Name: "Findnode/UnsolicitedNeighbors", Fn: UnsolicitedNeighbors}, + {Name: "Findnode/PastExpiration", Fn: FindnodePastExpiration}, + {Name: "Amplification/InvalidPongHash", Fn: FindnodeAmplificationInvalidPongHash}, + {Name: "Amplification/WrongIP", Fn: FindnodeAmplificationWrongIP}, +} diff --git a/cmd/devp2p/internal/v4test/framework.go b/cmd/devp2p/internal/v4test/framework.go new file mode 100644 index 000000000..49d94418b --- /dev/null +++ b/cmd/devp2p/internal/v4test/framework.go @@ -0,0 +1,123 @@ +// Copyright 2022 by the Authors +// This file is part of go-core. +// +// go-core 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-core 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-core. If not, see . + +package v4test + +import ( + "crypto/rand" + "fmt" + "github.com/core-coin/go-core/crypto" + "github.com/core-coin/go-core/p2p/discover/v4wire" + "github.com/core-coin/go-core/p2p/enode" + eddsa "github.com/core-coin/go-goldilocks" + "net" + "time" +) + +const waitTime = 300 * time.Millisecond + +type testenv struct { + l1, l2 net.PacketConn + key *eddsa.PrivateKey + remote *enode.Node + remoteAddr *net.UDPAddr +} + +func newTestEnv(remote string, listen1, listen2 string) *testenv { + l1, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen1)) + if err != nil { + panic(err) + } + l2, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen2)) + if err != nil { + panic(err) + } + key, err := crypto.GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + node, err := enode.Parse(enode.ValidSchemes, remote) + if err != nil { + panic(err) + } + if node.IP() == nil || node.UDP() == 0 { + var ip net.IP + var tcpPort, udpPort int + if ip = node.IP(); ip == nil { + ip = net.ParseIP("127.0.0.1") + } + if tcpPort = node.TCP(); tcpPort == 0 { + tcpPort = 30303 + } + if udpPort = node.TCP(); udpPort == 0 { + udpPort = 30303 + } + node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort) + } + addr := &net.UDPAddr{IP: node.IP(), Port: node.UDP()} + return &testenv{l1, l2, key, node, addr} +} + +func (te *testenv) close() { + te.l1.Close() + te.l2.Close() +} + +func (te *testenv) send(c net.PacketConn, req v4wire.Packet) []byte { + packet, hash, err := v4wire.Encode(te.key, req) + if err != nil { + panic(fmt.Errorf("can't encode %v packet: %v", req.Name(), err)) + } + if _, err := c.WriteTo(packet, te.remoteAddr); err != nil { + panic(fmt.Errorf("can't send %v: %v", req.Name(), err)) + } + return hash +} + +func (te *testenv) read(c net.PacketConn) (v4wire.Packet, []byte, error) { + buf := make([]byte, 2048) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { + return nil, nil, err + } + n, _, err := c.ReadFrom(buf) + if err != nil { + return nil, nil, err + } + p, _, hash, err := v4wire.Decode(buf[:n]) + return p, hash, err +} + +func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint { + addr := c.LocalAddr().(*net.UDPAddr) + return v4wire.Endpoint{ + IP: addr.IP.To4(), + UDP: uint16(addr.Port), + TCP: 0, + } +} + +func (te *testenv) remoteEndpoint() v4wire.Endpoint { + return v4wire.NewEndpoint(te.remoteAddr, 0) +} + +func contains(ns []v4wire.Node, key v4wire.Pubkey) bool { + for _, n := range ns { + if n.ID == key { + return true + } + } + return false +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7ddcbba52..995e5f71a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1583,6 +1583,7 @@ func SetXcbConfig(ctx *cli.Context, stack *node.Node, cfg *xcb.Config) { developer = ks.Accounts()[0] } else { developer, err = ks.NewAccount(passphrase) + cfg.Miner.Corebase = developer.Address if err != nil { Fatalf("Failed to create developer account: %v", err) } @@ -1639,19 +1640,18 @@ func RegisterXcbService(stack *node.Node, cfg *xcb.Config) xcbapi.Backend { Fatalf("Failed to register the Core service: %v", err) } return backend.ApiBackend - } else { - backend, err := xcb.New(stack, cfg) + } + backend, err := xcb.New(stack, cfg) + if err != nil { + Fatalf("Failed to register the Core service: %v", err) + } + if cfg.LightServ > 0 { + _, err := les.NewLesServer(stack, backend, cfg) if err != nil { - Fatalf("Failed to register the Core service: %v", err) - } - if cfg.LightServ > 0 { - _, err := les.NewLesServer(stack, backend, cfg) - if err != nil { - Fatalf("Failed to create the LES server: %v", err) - } + Fatalf("Failed to create the LES server: %v", err) } - return backend.APIBackend } + return backend.APIBackend } // RegisterXcbStatsService configures the Core Stats daemon and adds it to diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go index 7215e15fe..ec1c6788e 100644 --- a/common/compiler/solidity.go +++ b/common/compiler/solidity.go @@ -44,6 +44,20 @@ type ylemOutput struct { Version string } +// solidity v.0.8 changes the way ABI, Devdoc and Userdoc are serialized +type ylemOutputV8 struct { + Contracts map[string]struct { + BinRuntime string `json:"bin-runtime"` + SrcMapRuntime string `json:"srcmap-runtime"` + Bin, SrcMap, Metadata string + Abi interface{} + Devdoc interface{} + Userdoc interface{} + Hashes map[string]string + } + Version string +} + func (s *Solidity) makeArgs() []string { p := []string{ "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc", @@ -141,7 +155,8 @@ func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, erro func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { var output ylemOutput if err := json.Unmarshal(combinedJSON, &output); err != nil { - return nil, err + // Try to parse the output with the new solidity v.0.8.0 rules + return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions) } // Compilation succeeded, assemble and return the contracts. contracts := make(map[string]*Contract) @@ -176,3 +191,35 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin } return contracts, nil } + +// parseCombinedJSONV8 parses the direct output of ylem --combined-output +// and parses it using the rules from solidity v.0.8.0 and later. +func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { + var output ylemOutputV8 + if err := json.Unmarshal(combinedJSON, &output); err != nil { + return nil, err + } + // Compilation succeeded, assemble and return the contracts. + contracts := make(map[string]*Contract) + for name, info := range output.Contracts { + contracts[name] = &Contract{ + Code: "0x" + info.Bin, + RuntimeCode: "0x" + info.BinRuntime, + Hashes: info.Hashes, + Info: ContractInfo{ + Source: source, + Language: "Solidity", + LanguageVersion: languageVersion, + CompilerVersion: compilerVersion, + CompilerOptions: compilerOptions, + SrcMap: info.SrcMap, + SrcMapRuntime: info.SrcMapRuntime, + AbiDefinition: info.Abi, + UserDoc: info.Userdoc, + DeveloperDoc: info.Devdoc, + Metadata: info.Metadata, + }, + } + } + return contracts, nil +} diff --git a/console/bridge.go b/console/bridge.go index a895f06e2..2cab8ee97 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -132,15 +132,14 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if val, err = openWallet(goja.Null(), wallet, passwd); err != nil { if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) { return nil, err - } else { - // PIN input requested, fetch from the user and call open again - input, err := b.prompter.PromptPassword("Please enter current PIN: ") - if err != nil { - return nil, err - } - if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { - return nil, err - } + } + // PIN input requested, fetch from the user and call open again + input, err := b.prompter.PromptPassword("Please enter current PIN: ") + if err != nil { + return nil, err + } + if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { + return nil, err } } @@ -369,9 +368,7 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { resp.Set("id", req.ID) var result json.RawMessage - err = b.client.Call(&result, req.Method, req.Params...) - switch err := err.(type) { - case nil: + if err = b.client.Call(&result, req.Method, req.Params...); err == nil { if result == nil { // Special case null because it is decoded as an empty // raw message for some reason. @@ -384,15 +381,21 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { } resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result))) if err != nil { - setError(resp, -32603, err.Error()) + setError(resp, -32603, err.Error(), nil) } else { resp.Set("result", resultVal) } } - case rpc.Error: - setError(resp, err.ErrorCode(), err.Error()) - default: - setError(resp, -32603, err.Error()) + } else { + code := -32603 + var data interface{} + if err, ok := err.(rpc.Error); ok { + code = err.ErrorCode() + } + if err, ok := err.(rpc.DataError); ok { + data = err.ErrorData() + } + setError(resp, code, err.Error(), data) } resps = append(resps, resp) } @@ -412,8 +415,14 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { return result, nil } -func setError(resp *goja.Object, code int, msg string) { - resp.Set("error", map[string]interface{}{"code": code, "message": msg}) +func setError(resp *goja.Object, code int, msg string, data interface{}) { + err := make(map[string]interface{}) + err["code"] = code + err["message"] = msg + if data != nil { + err["data"] = data + } + resp.Set("error", err) } // isNumber returns true if input value is a JS number. diff --git a/contracts/checkpointoracle/oracle_test.go b/contracts/checkpointoracle/oracle_test.go index 6a38cd07f..dbfdf6030 100644 --- a/contracts/checkpointoracle/oracle_test.go +++ b/contracts/checkpointoracle/oracle_test.go @@ -181,7 +181,7 @@ func TestCheckpointRegister(t *testing.T) { contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{accounts[0].addr: {Balance: big.NewInt(1000000000)}, accounts[1].addr: {Balance: big.NewInt(1000000000)}, accounts[2].addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer contractBackend.Close() - transactOpts := bind.NewKeyedTransactor(accounts[0].key) + transactOpts, _ := bind.NewKeyedTransactorWithNetworkID(accounts[0].key, big.NewInt(1337)) // 3 trusted signers, threshold 2 contractAddr, _, c, err := contract.DeployCheckpointOracle(transactOpts, contractBackend, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr}, sectionSize, processConfirms, big.NewInt(2)) diff --git a/core/headerchain.go b/core/headerchain.go index dc10cf2ac..6881cddf4 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -369,9 +369,8 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma // in this case it is cheaper to just read the header if header := hc.GetHeader(hash, number); header != nil { return header.ParentHash, number - 1 - } else { - return common.Hash{}, 0 } + return common.Hash{}, 0 } for ancestor != 0 { if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash { diff --git a/go.mod b/go.mod index be1283a7a..8d6ef50a8 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,9 @@ require ( github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa github.com/fatih/color v1.7.0 + github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c // indirect github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 + github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-stack/stack v1.8.0 @@ -37,6 +39,7 @@ require ( github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/julienschmidt/httprouter v1.2.0 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 + github.com/kevinburke/go-bindata v3.24.0+incompatible // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-isatty v0.0.12 @@ -56,12 +59,13 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 - golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sys v0.0.0-20221010170243-090e33056c14 golang.org/x/text v0.3.7 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba - google.golang.org/protobuf v1.28.0 // indirect + golang.org/x/tools v0.1.12 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 diff --git a/go.sum b/go.sum index 05f057370..b78ad9e3e 100644 --- a/go.sum +++ b/go.sum @@ -100,12 +100,17 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0= +github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= +github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c h1:uYNKzPntb8c6DKvP9EfrBjkLkU7pM4lM+uuHSIa8UtU= +github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= @@ -210,6 +215,8 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kevinburke/go-bindata v3.24.0+incompatible h1:qajFA3D0pH94OTLU4zcCCKCDgR+Zr2cZK/RPJHDdFoY= +github.com/kevinburke/go-bindata v3.24.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -224,6 +231,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -255,6 +263,7 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -334,6 +343,7 @@ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:s github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -375,6 +385,8 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -394,8 +406,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -408,8 +420,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -437,18 +449,19 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -477,10 +490,14 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -531,8 +548,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/graphql/graphql.go b/graphql/graphql.go index 053d2e8df..36edce780 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -773,7 +773,7 @@ func (b *Block) Call(ctx context.Context, args struct { status = 0 } return &CallResult{ - data: result.Return(), + data: result.ReturnData, energyUsed: hexutil.Uint64(result.UsedEnergy), status: status, }, nil @@ -842,7 +842,7 @@ func (p *Pending) Call(ctx context.Context, args struct { status = 0 } return &CallResult{ - data: result.Return(), + data: result.ReturnData, energyUsed: hexutil.Uint64(result.UsedEnergy), status: status, }, nil diff --git a/internal/jsre/deps/bindata.go b/internal/jsre/deps/bindata.go index 9d94ed3cc..df7708f58 100644 --- a/internal/jsre/deps/bindata.go +++ b/internal/jsre/deps/bindata.go @@ -105,7 +105,7 @@ func web3Js() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web3.js", size: 551362, mode: os.FileMode(0664), modTime: time.Unix(1655287089, 0)} + info := bindataFileInfo{name: "web3.js", size: 551362, mode: os.FileMode(0664), modTime: time.Unix(1662021430, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe5, 0x40, 0x45, 0x91, 0x90, 0xaf, 0x9d, 0xce, 0xcc, 0x67, 0x77, 0x6d, 0x4a, 0xd, 0x1f, 0x6, 0x41, 0x9f, 0x0, 0xf2, 0x94, 0xa0, 0x93, 0xf0, 0xe2, 0xd7, 0xcd, 0x2, 0x6e, 0x8d, 0x56, 0xa}} return a, nil } diff --git a/internal/utesting/utesting.go b/internal/utesting/utesting.go new file mode 100644 index 000000000..77ed4c66d --- /dev/null +++ b/internal/utesting/utesting.go @@ -0,0 +1,185 @@ +// Copyright 2022 by the Authors +// This file is part of go-core. +// +// go-core 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-core 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-core. If not, see . + +package utesting + +import ( + "bytes" + "fmt" + "io" + "regexp" + "runtime" + "sync" + "time" +) + +// Test represents a single test. +type Test struct { + Name string + Fn func(*T) +} + +// Result is the result of a test execution. +type Result struct { + Name string + Failed bool + Output string + Duration time.Duration +} + +// MatchTests returns the tests whose name matches a regular expression. +func MatchTests(tests []Test, expr string) []Test { + var results []Test + re, err := regexp.Compile(expr) + if err != nil { + return nil + } + for _, test := range tests { + if re.MatchString(test.Name) { + results = append(results, test) + } + } + return results +} + +// RunTests executes all given tests in order and returns their results. +// If the report writer is non-nil, a test report is written to it in real time. +func RunTests(tests []Test, report io.Writer) []Result { + results := make([]Result, len(tests)) + for i, test := range tests { + start := time.Now() + results[i].Name = test.Name + results[i].Failed, results[i].Output = Run(test) + results[i].Duration = time.Since(start) + if report != nil { + printResult(results[i], report) + } + } + return results +} + +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) + } +} + +// CountFailures returns the number of failed tests in the result slice. +func CountFailures(rr []Result) int { + count := 0 + for _, r := range rr { + if r.Failed { + count++ + } + } + return count +} + +// Run executes a single test. +func Run(test Test) (bool, string) { + t := new(T) + done := make(chan struct{}) + go func() { + defer close(done) + defer func() { + if err := recover(); err != nil { + buf := make([]byte, 4096) + i := runtime.Stack(buf, false) + t.Logf("panic: %v\n\n%s", err, buf[:i]) + t.Fail() + } + }() + test.Fn(t) + }() + <-done + return t.failed, t.output.String() +} + +// T is the value given to the test function. The test can signal failures +// and log output by calling methods on this object. +type T struct { + mu sync.Mutex + failed bool + output bytes.Buffer +} + +// FailNow marks the test as having failed and stops its execution by calling +// runtime.Goexit (which then runs all deferred calls in the current goroutine). +func (t *T) FailNow() { + t.Fail() + runtime.Goexit() +} + +// Fail marks the test as having failed but continues execution. +func (t *T) Fail() { + t.mu.Lock() + defer t.mu.Unlock() + t.failed = true +} + +// Failed reports whether the test has failed. +func (t *T) Failed() bool { + t.mu.Lock() + defer t.mu.Unlock() + return t.failed +} + +// Log formats its arguments using default formatting, analogous to Println, and records +// the text in the error log. +func (t *T) Log(vs ...interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + fmt.Fprintln(&t.output, vs...) +} + +// Logf formats its arguments according to the format, analogous to Printf, and records +// the text in the error log. A final newline is added if not provided. +func (t *T) Logf(format string, vs ...interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + if len(format) == 0 || format[len(format)-1] != '\n' { + format += "\n" + } + fmt.Fprintf(&t.output, format, vs...) +} + +// Error is equivalent to Log followed by Fail. +func (t *T) Error(vs ...interface{}) { + t.Log(vs...) + t.Fail() +} + +// Errorf is equivalent to Logf followed by Fail. +func (t *T) Errorf(format string, vs ...interface{}) { + t.Logf(format, vs...) + t.Fail() +} + +// Fatal is equivalent to Log followed by FailNow. +func (t *T) Fatal(vs ...interface{}) { + t.Log(vs...) + t.FailNow() +} + +// Fatalf is equivalent to Logf followed by FailNow. +func (t *T) Fatalf(format string, vs ...interface{}) { + t.Logf(format, vs...) + t.FailNow() +} diff --git a/internal/utesting/utesting_test.go b/internal/utesting/utesting_test.go new file mode 100644 index 000000000..ea5ff42cb --- /dev/null +++ b/internal/utesting/utesting_test.go @@ -0,0 +1,55 @@ +// Copyright 2022 by the Authors +// This file is part of go-core. +// +// go-core 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-core 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-core. If not, see . + +package utesting + +import ( + "strings" + "testing" +) + +func TestTest(t *testing.T) { + tests := []Test{ + { + Name: "successful test", + Fn: func(t *T) {}, + }, + { + Name: "failing test", + Fn: func(t *T) { + t.Log("output") + t.Error("failed") + }, + }, + { + Name: "panicking test", + Fn: func(t *T) { + panic("oh no") + }, + }, + } + results := RunTests(tests, nil) + + if results[0].Failed || results[0].Output != "" { + t.Fatalf("wrong result for successful test: %#v", results[0]) + } + if !results[1].Failed || results[1].Output != "output\nfailed\n" { + t.Fatalf("wrong result for failing test: %#v", results[1]) + } + if !results[2].Failed || !strings.HasPrefix(results[2].Output, "panic: oh no\n") { + t.Fatalf("wrong result for panicking test: %#v", results[2]) + } +} diff --git a/internal/xcbapi/api.go b/internal/xcbapi/api.go index 7ed49347e..b95b10c22 100644 --- a/internal/xcbapi/api.go +++ b/internal/xcbapi/api.go @@ -870,6 +870,36 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo return result, err } +func newRevertError(result *core.ExecutionResult) *revertError { + reason, errUnpack := abi.UnpackRevert(result.Revert()) + err := errors.New("execution reverted") + if errUnpack == nil { + err = fmt.Errorf("execution reverted: %v", reason) + } + return &revertError{ + error: err, + reason: hexutil.Encode(result.Revert()), + } +} + +// revertError is an API error that encompassas an EVM revertal with JSON error +// code and a binary data blob. +type revertError struct { + error + reason string // revert reason hex encoded +} + +// ErrorCode returns the JSON error code for a revertal. +// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal +func (e *revertError) ErrorCode() int { + return 3 +} + +// ErrorData returns the hex encoded revert reason. +func (e *revertError) ErrorData() interface{} { + return e.reason +} + // Call executes the given transaction on the state for the given block number. // // Additionally, the caller can specify a batch of contract for fields overriding. @@ -885,25 +915,13 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr if err != nil { return nil, err } - return result.Return(), nil -} - -type estimateEnergyError struct { - error string // Concrete error type if it's failed to estimate energy usage - vmerr error // Additional field, it's non-nil if the given transaction is invalid - revert string // Additional field, it's non-empty if the transaction is reverted and reason is provided -} - -func (e estimateEnergyError) Error() string { - errMsg := e.error - if e.vmerr != nil { - errMsg += fmt.Sprintf(" (%v)", e.vmerr) - } - if e.revert != "" { - errMsg += fmt.Sprintf(" (%s)", e.revert) + // If the result contains a revert reason, try to unpack and return it. + if len(result.Revert()) > 0 { + return nil, newRevertError(result) } - return errMsg + return result.Return(), result.Err } + func DoEstimateEnergy(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, energyCap *big.Int) (hexutil.Uint64, error) { // Binary search the energy requirement, as it may be higher than the amount used var ( @@ -972,23 +990,13 @@ func DoEstimateEnergy(ctx context.Context, b Backend, args CallArgs, blockNrOrHa } if failed { if result != nil && result.Err != vm.ErrOutOfEnergy { - var revert string if len(result.Revert()) > 0 { - ret, err := abi.UnpackRevert(result.Revert()) - if err != nil { - revert = hexutil.Encode(result.Revert()) - } else { - revert = ret - } - } - return 0, estimateEnergyError{ - error: "always failing transaction", - vmerr: result.Err, - revert: revert, + return 0, newRevertError(result) } + return 0, result.Err } // Otherwise, the specified energy cap is too low - return 0, estimateEnergyError{error: fmt.Sprintf("energy required exceeds allowance (%d)", cap)} + return 0, fmt.Errorf("energy required exceeds allowance (%d)", cap) } } return hexutil.Uint64(hi), nil diff --git a/les/benchmark.go b/les/benchmark.go index fba22d976..ddacaf53e 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -77,9 +77,8 @@ func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error { func (b *benchmarkBlockHeaders) request(peer *serverPeer, index int) error { if b.byHash { return peer.requestHeadersByHash(0, b.hashes[index], b.amount, b.skip, b.reverse) - } else { - return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse) } + return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse) } // benchmarkBodiesOrReceipts implements requestBenchmark @@ -100,9 +99,8 @@ func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error { func (b *benchmarkBodiesOrReceipts) request(peer *serverPeer, index int) error { if b.receipts { return peer.requestReceipts(0, []common.Hash{b.hashes[index]}) - } else { - return peer.requestBodies(0, []common.Hash{b.hashes[index]}) } + return peer.requestBodies(0, []common.Hash{b.hashes[index]}) } // benchmarkProofsOrCode implements requestBenchmark @@ -121,9 +119,8 @@ func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error { rand.Read(key) if b.code { return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccKey: key}}) - } else { - return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) } + return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) } // benchmarkHelperTrie implements requestBenchmark diff --git a/les/odr_requests.go b/les/odr_requests.go index c3a538839..393ec3201 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -329,9 +329,8 @@ func (r *ChtRequest) CanSend(peer *serverPeer) bool { if r.Untrusted { return peer.headInfo.Number >= r.BlockNum && peer.id == r.PeerId - } else { - return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize } + return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) diff --git a/les/peer.go b/les/peer.go index 30c3d58d5..9750d8784 100644 --- a/les/peer.go +++ b/les/peer.go @@ -64,9 +64,6 @@ const ( // handshakeTimeout is the timeout LES handshake will be treated as failed. handshakeTimeout = 15 * time.Second - - // retrySendCachePeriod is the time interval a caching retry is performed. - retrySendCachePeriod = time.Millisecond * 100 ) const ( @@ -158,24 +155,6 @@ func (p *peerCommons) queueSend(f func()) bool { return p.sendQueue.Queue(f) } -// mustQueueSend starts a for loop and retry the caching if failed. -// If the stopCh is closed, then it returns. -func (p *peerCommons) mustQueueSend(f func()) { - for { - // Check whether the stopCh is closed. - select { - case <-p.closeCh: - return - default: - } - // If the function is successfully cached, return. - if p.canQueue() && p.queueSend(f) { - return - } - time.Sleep(retrySendCachePeriod) - } -} - // String implements fmt.Stringer. func (p *peerCommons) String() string { return fmt.Sprintf("Peer %s [%s]", p.id, fmt.Sprintf("les/%d", p.version)) @@ -907,7 +886,7 @@ func (p *clientPeer) updateCapacity(cap uint64) { var kvList keyValueList kvList = kvList.add("flowControl/MRR", cap) kvList = kvList.add("flowControl/BL", cap*bufLimitRatio) - p.mustQueueSend(func() { p.sendAnnounce(announceData{Update: kvList}) }) + p.queueSend(func() { p.sendAnnounce(announceData{Update: kvList}) }) } // freezeClient temporarily puts the client in a frozen state which means all diff --git a/les/peer_test.go b/les/peer_test.go index eafedba4e..b442542c8 100644 --- a/les/peer_test.go +++ b/les/peer_test.go @@ -79,7 +79,7 @@ func TestPeerSubscription(t *testing.T) { // Generate a random id and create the peer var id enode.ID rand.Read(id[:]) - peer := newServerPeer(2, NetworkId, false, p2p.NewPeer(id, "name", nil), nil) + peer := newServerPeer(2, DevNetworkId, false, p2p.NewPeer(id, "name", nil), nil) peers.register(peer) checkIds([]string{peer.id}) @@ -98,8 +98,8 @@ func TestHandshake(t *testing.T) { var id enode.ID rand.Read(id[:]) - peer1 := newClientPeer(2, NetworkId, p2p.NewPeer(id, "name", nil), net) - peer2 := newServerPeer(2, NetworkId, true, p2p.NewPeer(id, "name", nil), app) + peer1 := newClientPeer(2, DevNetworkId, p2p.NewPeer(id, "name", nil), net) + peer2 := newServerPeer(2, DevNetworkId, true, p2p.NewPeer(id, "name", nil), app) var ( errCh1 = make(chan error, 1) diff --git a/les/protocol.go b/les/protocol.go index 6b5d21dac..88bd31218 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -48,6 +48,7 @@ var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24} const ( NetworkId = 1 + DevNetworkId = 1337 ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message ) diff --git a/les/server_handler.go b/les/server_handler.go index a215c14c0..77e20dbc1 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -265,7 +265,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { h.server.clientPool.requestCost(p, realCost) } if reply != nil { - p.mustQueueSend(func() { + p.queueSend(func() { if err := reply.send(bv); err != nil { select { case p.errCh <- err: @@ -585,6 +585,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { var ( lastBHash common.Hash root common.Hash + header *types.Header ) reqCnt := len(req.Reqs) if accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) { @@ -599,10 +600,6 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { return } // Look up the root hash belonging to the request - var ( - header *types.Header - trie state.Trie - ) if request.BHash != lastBHash { root, lastBHash = common.Hash{}, request.BHash @@ -629,6 +626,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { // Open the account or storage trie for the request statedb := h.blockchain.StateCache() + var trie state.Trie switch len(request.AccKey) { case 0: // No account key specified, open an account trie diff --git a/les/serverpool.go b/les/serverpool.go index 20d282a5a..fbe87f4b5 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -112,9 +112,8 @@ var ( } enc, err := rlp.EncodeToBytes(&ne) return enc, err - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") }, func(enc []byte) (interface{}, error) { var ne nodeHistoryEnc @@ -166,7 +165,7 @@ func newServerPool(db xcbdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, d if oldState.Equals(sfWaitDialTimeout) && newState.IsEmpty() { // dial timeout, no connection s.setRedialWait(n, dialCost, dialWaitStep) - s.ns.SetState(n, nodestate.Flags{}, sfDialing, 0) + s.ns.SetStateSub(n, nodestate.Flags{}, sfDialing, 0) } }) @@ -193,10 +192,10 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod if rand.Intn(maxQueryFails*2) < int(fails) { // skip pre-negotiation with increasing chance, max 50% // this ensures that the client can operate even if UDP is not working at all - s.ns.SetState(n, sfCanDial, nodestate.Flags{}, time.Second*10) + s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) // set canDial before resetting queried so that FillSet will not read more // candidates unnecessarily - s.ns.SetState(n, nodestate.Flags{}, sfQueried, 0) + s.ns.SetStateSub(n, nodestate.Flags{}, sfQueried, 0) return } go func() { @@ -206,12 +205,15 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod } else { atomic.StoreUint32(&s.queryFails, 0) } - if q == 1 { - s.ns.SetState(n, sfCanDial, nodestate.Flags{}, time.Second*10) - } else { - s.setRedialWait(n, queryCost, queryWaitStep) - } - s.ns.SetState(n, nodestate.Flags{}, sfQueried, 0) + s.ns.Operation(func() { + // we are no longer running in the operation that the callback belongs to, start a new one because of setRedialWait + if q == 1 { + s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) + } else { + s.setRedialWait(n, queryCost, queryWaitStep) + } + s.ns.SetStateSub(n, nodestate.Flags{}, sfQueried, 0) + }) }() } }) @@ -240,18 +242,20 @@ func (s *serverPool) start() { } } unixTime := s.unixTime() - s.ns.ForEach(sfHasValue, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - s.calculateWeight(node) - if n, ok := s.ns.GetField(node, sfiNodeHistory).(nodeHistory); ok && n.redialWaitEnd > unixTime { - wait := n.redialWaitEnd - unixTime - lastWait := n.redialWaitEnd - n.redialWaitStart - if wait > lastWait { - // if the time until expiration is larger than the last suggested - // waiting time then the system clock was probably adjusted - wait = lastWait + s.ns.Operation(func() { + s.ns.ForEach(sfHasValue, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { + s.calculateWeight(node) + if n, ok := s.ns.GetField(node, sfiNodeHistory).(nodeHistory); ok && n.redialWaitEnd > unixTime { + wait := n.redialWaitEnd - unixTime + lastWait := n.redialWaitEnd - n.redialWaitStart + if wait > lastWait { + // if the time until expiration is larger than the last suggested + // waiting time then the system clock was probably adjusted + wait = lastWait + } + s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, time.Duration(wait)*time.Second) } - s.ns.SetState(node, sfRedialWait, nodestate.Flags{}, time.Duration(wait)*time.Second) - } + }) }) } @@ -261,9 +265,11 @@ func (s *serverPool) stop() { if s.fillSet != nil { s.fillSet.Close() } - s.ns.ForEach(sfConnected, nodestate.Flags{}, func(n *enode.Node, state nodestate.Flags) { - // recalculate weight of connected nodes in order to update hasValue flag if necessary - s.calculateWeight(n) + s.ns.Operation(func() { + s.ns.ForEach(sfConnected, nodestate.Flags{}, func(n *enode.Node, state nodestate.Flags) { + // recalculate weight of connected nodes in order to update hasValue flag if necessary + s.calculateWeight(n) + }) }) s.ns.Stop() } @@ -279,9 +285,11 @@ func (s *serverPool) registerPeer(p *serverPeer) { // unregisterPeer implements serverPeerSubscriber func (s *serverPool) unregisterPeer(p *serverPeer) { - s.setRedialWait(p.Node(), dialCost, dialWaitStep) - s.ns.SetState(p.Node(), nodestate.Flags{}, sfConnected, 0) - s.ns.SetField(p.Node(), sfiConnectedStats, nil) + s.ns.Operation(func() { + s.setRedialWait(p.Node(), dialCost, dialWaitStep) + s.ns.SetStateSub(p.Node(), nodestate.Flags{}, sfConnected, 0) + s.ns.SetFieldSub(p.Node(), sfiConnectedStats, nil) + }) s.vt.Unregister(p.ID()) p.setValueTracker(nil, nil) } @@ -380,14 +388,16 @@ func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl // updateWeight calculates the node weight and updates the nodeWeight field and the // hasValue flag. It also saves the node state if necessary. +// Note: this function should run inside a NodeStateMachine operation func (s *serverPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) { weight := uint64(totalValue * nodeWeightMul / float64(totalDialCost)) if weight >= nodeWeightThreshold { - s.ns.SetState(node, sfHasValue, nodestate.Flags{}, 0) - s.ns.SetField(node, sfiNodeWeight, weight) + s.ns.SetStateSub(node, sfHasValue, nodestate.Flags{}, 0) + s.ns.SetFieldSub(node, sfiNodeWeight, weight) } else { - s.ns.SetState(node, nodestate.Flags{}, sfHasValue, 0) - s.ns.SetField(node, sfiNodeWeight, nil) + s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0) + s.ns.SetFieldSub(node, sfiNodeWeight, nil) + s.ns.SetFieldSub(node, sfiNodeHistory, nil) } s.ns.Persist(node) // saved if node history or hasValue changed } @@ -400,6 +410,7 @@ func (s *serverPool) updateWeight(node *enode.Node, totalValue float64, totalDia // a significant amount of service value again its waiting time is quickly reduced or reset // to the minimum. // Note: node weight is also recalculated and updated by this function. +// Note 2: this function should run inside a NodeStateMachine operation func (s *serverPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) { n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) sessionValue, totalValue := s.serviceValue(node) @@ -450,21 +461,22 @@ func (s *serverPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep if wait < waitThreshold { n.redialWaitStart = unixTime n.redialWaitEnd = unixTime + int64(nextTimeout) - s.ns.SetField(node, sfiNodeHistory, n) - s.ns.SetState(node, sfRedialWait, nodestate.Flags{}, wait) + s.ns.SetFieldSub(node, sfiNodeHistory, n) + s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, wait) s.updateWeight(node, totalValue, totalDialCost) } else { // discard known node statistics if waiting time is very long because the node // hasn't been responsive for a very long time - s.ns.SetField(node, sfiNodeHistory, nil) - s.ns.SetField(node, sfiNodeWeight, nil) - s.ns.SetState(node, nodestate.Flags{}, sfHasValue, 0) + s.ns.SetFieldSub(node, sfiNodeHistory, nil) + s.ns.SetFieldSub(node, sfiNodeWeight, nil) + s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0) } } // calculateWeight calculates and sets the node weight without altering the node history. // This function should be called during startup and shutdown only, otherwise setRedialWait // will keep the weights updated as the underlying statistics are adjusted. +// Note: this function should run inside a NodeStateMachine operation func (s *serverPool) calculateWeight(node *enode.Node) { n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) _, totalValue := s.serviceValue(node) diff --git a/les/serverpool_test.go b/les/serverpool_test.go index c0597b77f..86cb4a38a 100644 --- a/les/serverpool_test.go +++ b/les/serverpool_test.go @@ -118,31 +118,28 @@ func (s *serverPoolTest) start() { s.clock.Sleep(time.Second * 5) s.endWait() return -1 - } else { - switch idx % 3 { - case 0: - // pre-neg returns true only if connection is possible - if canConnect { - return 1 - } else { - return 0 - } - case 1: - // pre-neg returns true but connection might still fail + } + switch idx % 3 { + case 0: + // pre-neg returns true only if connection is possible + if canConnect { + return 1 + } + return 0 + case 1: + // pre-neg returns true but connection might still fail + return 1 + case 2: + // pre-neg returns true if connection is possible, otherwise timeout (node unresponsive) + if canConnect { return 1 - case 2: - // pre-neg returns true if connection is possible, otherwise timeout (node unresponsive) - if canConnect { - return 1 - } else { - s.beginWait() - s.clock.Sleep(time.Second * 5) - s.endWait() - return -1 - } } + s.beginWait() + s.clock.Sleep(time.Second * 5) + s.endWait() return -1 } + return -1 } } diff --git a/les/sync_test.go b/les/sync_test.go index dace3ef41..e10d20e0d 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -83,7 +83,8 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { //TODO: T data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.SHA3(data), signerKey) //sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + auth, _ := bind.NewKeyedTransactorWithNetworkID(signerKey, big.NewInt(1337)) + if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { t.Error("register checkpoint failed", err) } server.backend.Commit() @@ -166,7 +167,8 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.SHA3(data), signerKey) //sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + auth, _ := bind.NewKeyedTransactorWithNetworkID(signerKey, big.NewInt(1337)) + if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { t.Error("register checkpoint failed", err) } server.backend.Commit() diff --git a/les/test_helper.go b/les/test_helper.go index 0f26c0aa4..350aeb626 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -116,7 +116,8 @@ func prepare(n int, backend *backends.SimulatedBackend) { switch i { case 0: // deploy checkpoint contract - registrarAddr, _, _, _ = contract.DeployCheckpointOracle(bind.NewKeyedTransactor(bankKey), backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) + auth, _ := bind.NewKeyedTransactorWithNetworkID(bankKey, big.NewInt(1337)) + registrarAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) // bankUser transfers some core to user1 nonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxEnergy, nil, nil), signer, bankKey) @@ -178,9 +179,10 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index evmux = new(event.TypeMux) engine = cryptore.NewFaker() gspec = core.Genesis{ - Config: params.AllCryptoreProtocolChanges, + Config: params.DevChainConfig, Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, EnergyLimit: 100000000, + Coinbase: core.DefaultCoinbaseMainnet, } oracle *checkpointoracle.CheckpointOracle ) @@ -235,7 +237,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db xcbdb.Database, peers *clientPeerSet, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend) { var ( gspec = core.Genesis{ - Config: params.AllCryptoreProtocolChanges, + Config: params.DevChainConfig, Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, EnergyLimit: 100000000, } diff --git a/les/ulc_test.go b/les/ulc_test.go index acfed4921..a595239e7 100644 --- a/les/ulc_test.go +++ b/les/ulc_test.go @@ -94,8 +94,8 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr var id enode.ID rand.Read(id[:]) - peer1 := newServerPeer(protocol, NetworkId, true, p2p.NewPeer(serverId, "", nil), net) // Mark server as trusted - peer2 := newClientPeer(protocol, NetworkId, p2p.NewPeer(id, "", nil), app) + peer1 := newServerPeer(protocol, DevNetworkId, true, p2p.NewPeer(serverId, "", nil), net) // Mark server as trusted + peer2 := newClientPeer(protocol, DevNetworkId, p2p.NewPeer(id, "", nil), app) // Start the peerLight on a new thread errc1 := make(chan error, 1) diff --git a/mobile/bind.go b/mobile/bind.go index 2147a1007..4051b09cd 100644 --- a/mobile/bind.go +++ b/mobile/bind.go @@ -40,7 +40,7 @@ type MobileSigner struct { } func (s *MobileSigner) Sign(addr *Address, unsignedTx *Transaction) (signedTx *Transaction, _ error) { - sig, err := s.sign(types.NewNucleusSigner(nil), addr.address, unsignedTx.tx) + sig, err := s.sign(addr.address, unsignedTx.tx) if err != nil { return nil, err } @@ -82,12 +82,16 @@ func NewTransactOpts() *TransactOpts { // NewKeyedTransactOpts is a utility method to easily create a transaction signer // from a single private key. -func NewKeyedTransactOpts(keyJson []byte, passphrase string) (*TransactOpts, error) { +func NewKeyedTransactOpts(keyJson []byte, passphrase string, chainID *big.Int) (*TransactOpts, error) { key, err := keystore.DecryptKey(keyJson, passphrase) if err != nil { return nil, err } - return &TransactOpts{*bind.NewKeyedTransactor(key.PrivateKey)}, nil + auth, err := bind.NewKeyedTransactorWithNetworkID(key.PrivateKey, chainID) + if err != nil { + return nil, err + } + return &TransactOpts{*auth}, nil } func (opts *TransactOpts) GetFrom() *Address { return &Address{opts.opts.From} } @@ -106,7 +110,7 @@ func (opts *TransactOpts) GetEnergyLimit() int64 { return int64(opts.opts.Ener func (opts *TransactOpts) SetFrom(from *Address) { opts.opts.From = from.address } func (opts *TransactOpts) SetNonce(nonce int64) { opts.opts.Nonce = big.NewInt(nonce) } func (opts *TransactOpts) SetSigner(s Signer) { - opts.opts.Signer = func(signer types.Signer, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + opts.opts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { sig, err := s.Sign(&Address{addr}, &Transaction{tx}) if err != nil { return nil, err diff --git a/p2p/discover/node.go b/p2p/discover/node.go index 981f2e093..9aa62453f 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -48,17 +48,6 @@ func (e encPubkey) id() enode.ID { return enode.ID(crypto.SHA3Hash(e[:])) } -// recoverNodeKey computes the public key used to sign the -// given hash from the signature. -func recoverNodeKey(hash, sig []byte) (key encPubkey, err error) { - pubkey, err := crypto.Ecrecover(hash, sig) - if err != nil { - return key, err - } - copy(key[:], pubkey[:]) - return key, nil -} - func wrapNode(n *enode.Node) *node { return &node{Node: *n} } diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index a94337e1a..0a4c2c44d 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -19,6 +19,7 @@ package discover import ( "fmt" "github.com/core-coin/go-core/common" + "github.com/core-coin/go-core/p2p/discover/v4wire" eddsa "github.com/core-coin/go-goldilocks" "net" "sort" @@ -136,15 +137,15 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) { func serveDevin(test *udpTest, devin *preminedDevin) { for done := false; !done; { - done = test.waitPacketOut(func(p packetV4, to *net.UDPAddr, hash []byte) { + done = test.waitPacketOut(func(p v4wire.Packet, to *net.UDPAddr, hash []byte) { n, key := devin.nodeByAddr(to) switch p.(type) { - case *pingV4: - test.packetInFrom(nil, key, to, &pongV4{Expiration: futureExp, ReplyTok: hash}) - case *findnodeV4: + case *v4wire.Ping: + test.packetInFrom(nil, key, to, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash}) + case *v4wire.Findnode: dist := enode.LogDist(n.ID(), devin.target.id()) nodes := devin.nodesAtDistance(dist - 1) - test.packetInFrom(nil, key, to, &neighborsV4{Expiration: futureExp, Nodes: nodes}) + test.packetInFrom(nil, key, to, &v4wire.Neighbors{Expiration: futureExp, Nodes: nodes}) } }) } @@ -271,8 +272,8 @@ func (tn *preminedDevin) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *eddsa.Priv return tn.node(dist, index), key } -func (tn *preminedDevin) nodesAtDistance(dist int) []rpcNode { - result := make([]rpcNode, len(tn.dists[dist])) +func (tn *preminedDevin) nodesAtDistance(dist int) []v4wire.Node { + result := make([]v4wire.Node, len(tn.dists[dist])) for i := range result { result[i] = nodeToRPC(wrapNode(tn.node(dist, i))) } diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index d93bf42d7..6a8c3a2f3 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -23,7 +23,7 @@ import ( crand "crypto/rand" "errors" "fmt" - "github.com/core-coin/go-core/common" + "github.com/core-coin/go-core/p2p/discover/v4wire" "io" "net" "sync" @@ -34,15 +34,12 @@ import ( "github.com/core-coin/go-core/crypto" "github.com/core-coin/go-core/log" "github.com/core-coin/go-core/p2p/enode" - "github.com/core-coin/go-core/p2p/enr" "github.com/core-coin/go-core/p2p/netutil" "github.com/core-coin/go-core/rlp" ) // Errors var ( - errPacketTooSmall = errors.New("too small") - errBadHash = errors.New("bad hash") errExpired = errors.New("expired") errUnsolicitedReply = errors.New("unsolicited reply") errUnknownNode = errors.New("unknown node") @@ -68,135 +65,6 @@ const ( maxPacketSize = 1280 ) -// RPC packet types -const ( - p_pingV4 = iota + 1 // zero is 'reserved' - p_pongV4 - p_findnodeV4 - p_neighborsV4 - p_enrRequestV4 - p_enrResponseV4 -) - -// RPC request structures -type ( - pingV4 struct { - senderKey *eddsa.PublicKey // filled in by preverify - - Version uint - From, To rpcEndpoint - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // pongV4 is the reply to pingV4. - pongV4 struct { - // This field should mirror the UDP envelope address - // of the ping packet, which provides a way to discover the - // the external address (after NAT). - To rpcEndpoint - - ReplyTok []byte // This contains the hash of the ping packet. - Expiration uint64 // Absolute timestamp at which the packet becomes invalid. - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // findnodeV4 is a query for nodes close to the given target. - findnodeV4 struct { - Target encPubkey - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // neighborsV4 is the reply to findnodeV4. - neighborsV4 struct { - Nodes []rpcNode - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // enrRequestV4 queries for the remote node's record. - enrRequestV4 struct { - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // enrResponseV4 is the reply to enrRequestV4. - enrResponseV4 struct { - ReplyTok []byte // Hash of the enrRequest packet. - Record enr.Record - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - rpcNode struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - ID encPubkey - } - - rpcEndpoint struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - } -) - -// packetV4 is implemented by all v4 protocol messages. -type packetV4 interface { - // preverify checks whether the packet is valid and should be handled at all. - preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error - // handle handles the packet. - handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) - // packet name and type for logging purposes. - name() string - kind() byte -} - -func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { - ip := net.IP{} - if ip4 := addr.IP.To4(); ip4 != nil { - ip = ip4 - } else if ip6 := addr.IP.To16(); ip6 != nil { - ip = ip6 - } - return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} -} - -func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) { - if rn.UDP <= 1024 { - return nil, errLowPort - } - if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { - return nil, err - } - if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { - return nil, errors.New("not contained in netrestrict whitelist") - } - key, err := decodePubkey(rn.ID) - if err != nil { - return nil, err - } - n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))) - err = n.ValidateComplete() - return n, err -} - -func nodeToRPC(n *node) rpcNode { - var key eddsa.PublicKey - var ekey encPubkey - if err := n.Load((*enode.Secp256k1)(&key)); err == nil { - ekey = encodePubkey(&key) - } - return rpcNode{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())} -} - // UDPv4 implements the v4 wire protocol. type UDPv4 struct { conn UDPConn @@ -245,16 +113,16 @@ type replyMatcher struct { // reply contains the most recent reply. This field is safe for reading after errc has // received a value. - reply packetV4 + reply v4wire.Packet } -type replyMatchFunc func(interface{}) (matched bool, requestDone bool) +type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool) // reply is a reply packet from a certain node. type reply struct { from enode.ID ip net.IP - data packetV4 + data v4wire.Packet // loop indicates whether there was // a matching request by sending on this channel. matched chan<- bool @@ -334,10 +202,10 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node { return n } -func (t *UDPv4) ourEndpoint() rpcEndpoint { +func (t *UDPv4) ourEndpoint() v4wire.Endpoint { n := t.Self() a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()} - return makeEndpoint(a, uint16(n.TCP())) + return v4wire.NewEndpoint(a, uint16(n.TCP())) } // Ping sends a ping message to the given node. @@ -350,7 +218,7 @@ func (t *UDPv4) Ping(n *enode.Node) error { func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) { rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil) if err = <-rm.errc; err == nil { - seq = seqFromTail(rm.reply.(*pongV4).Rest) + seq = rm.reply.(*v4wire.Pong).ENRSeq() } return seq, err } @@ -359,7 +227,7 @@ func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) { // when the reply arrives. func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher { req := t.makePing(toaddr) - packet, hash, err := t.encode(t.priv, req) + packet, hash, err := v4wire.Encode(t.priv, req) if err != nil { errc := make(chan error, 1) errc <- err @@ -367,8 +235,8 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r } // Add a matcher for the reply to the pending reply queue. Pongs are matched if they // reference the ping we're about to send. - rm := t.pending(toid, toaddr.IP, p_pongV4, func(p interface{}) (matched bool, requestDone bool) { - matched = bytes.Equal(p.(*pongV4).ReplyTok, hash) + rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) { + matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash) if matched && callback != nil { callback() } @@ -376,16 +244,16 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r }) // Send the packet. t.localNode.UDPContact(toaddr) - t.write(toaddr, toid, req.name(), packet) + t.write(toaddr, toid, req.Name(), packet) return rm } -func (t *UDPv4) makePing(toaddr *net.UDPAddr) *pingV4 { +func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping { seq, _ := rlp.EncodeToBytes(t.localNode.Node().Seq()) - return &pingV4{ + return &v4wire.Ping{ Version: 4, From: t.ourEndpoint(), - To: makeEndpoint(toaddr, 0), + To: v4wire.NewEndpoint(toaddr, 0), Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: []rlp.RawValue{seq}, } @@ -425,23 +293,24 @@ func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup { func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup { target := enode.ID(crypto.SHA3Hash(targetKey[:])) + ekey := v4wire.Pubkey(targetKey) it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) { - return t.findnode(n.ID(), n.addr(), targetKey) + return t.findnode(n.ID(), n.addr(), ekey) }) return it } // findnode sends a findnode request to the given node and waits until // the node has sent up to k neighbors. -func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { +func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) { t.ensureBond(toid, toaddr) // Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is // active until enough nodes have been received. nodes := make([]*node, 0, bucketSize) nreceived := 0 - rm := t.pending(toid, toaddr.IP, p_neighborsV4, func(r interface{}) (matched bool, requestDone bool) { - reply := r.(*neighborsV4) + rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) { + reply := r.(*v4wire.Neighbors) for _, rn := range reply.Nodes { nreceived++ n, err := t.nodeFromRPC(toaddr, rn) @@ -453,7 +322,7 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ( } return true, nreceived >= bucketSize }) - t.send(toaddr, toid, &findnodeV4{ + t.send(toaddr, toid, &v4wire.Findnode{ Target: target, Expiration: uint64(time.Now().Add(expiration).Unix()), }) @@ -474,26 +343,26 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()} t.ensureBond(n.ID(), addr) - req := &enrRequestV4{ + req := &v4wire.ENRRequest{ Expiration: uint64(time.Now().Add(expiration).Unix()), } - packet, hash, err := t.encode(t.priv, req) + packet, hash, err := v4wire.Encode(t.priv, req) if err != nil { return nil, err } // Add a matcher for the reply to the pending reply queue. Responses are matched if // they reference the request we're about to send. - rm := t.pending(n.ID(), addr.IP, p_enrResponseV4, func(r interface{}) (matched bool, requestDone bool) { - matched = bytes.Equal(r.(*enrResponseV4).ReplyTok, hash) + rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) { + matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash) return matched, matched }) // Send the packet and wait for the reply. - t.write(addr, n.ID(), req.name(), packet) + t.write(addr, n.ID(), req.Name(), packet) if err := <-rm.errc; err != nil { return nil, err } // Verify the response record. - respN, err := enode.New(enode.ValidSchemes, &rm.reply.(*enrResponseV4).Record) + respN, err := enode.New(enode.ValidSchemes, &rm.reply.(*v4wire.ENRResponse).Record) if err != nil { return nil, err } @@ -525,7 +394,7 @@ func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchF // handleReply dispatches a reply packet, invoking reply matchers. It returns // whether any matcher considered the packet acceptable. -func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req packetV4) bool { +func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool { matched := make(chan bool, 1) select { case t.gotreply <- reply{from, fromIP, req, matched}: @@ -591,7 +460,7 @@ func (t *UDPv4) loop() { var matched bool // whether any replyMatcher considered the reply acceptable. for el := plist.Front(); el != nil; el = el.Next() { p := el.Value.(*replyMatcher) - if p.from == r.from && p.ptype == r.data.kind() && p.ip.Equal(r.ip) { + if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) { ok, requestDone := p.callback(r.data) matched = matched || ok p.reply = r.data @@ -630,44 +499,12 @@ func (t *UDPv4) loop() { } } -const ( - macSize = 256 / 8 - sigSize = crypto.ExtendedSignatureLength - headSize = macSize + sigSize // space of packet frame data -) - -var ( - headSpace = make([]byte, headSize) - - // Neighbors replies are sent across multiple packets to - // stay below the packet size limit. We compute the maximum number - // of entries by stuffing a packet until it grows too large. - maxNeighbors int -) - -func init() { - p := neighborsV4{Expiration: ^uint64(0)} - maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} - for n := 0; ; n++ { - p.Nodes = append(p.Nodes, maxSizeNode) - size, _, err := rlp.EncodeToReader(p) - if err != nil { - // If this ever happens, it will be caught by the unit tests. - panic("cannot encode: " + err.Error()) - } - if headSize+size+1 >= maxPacketSize { - maxNeighbors = n - break - } - } -} - -func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req packetV4) ([]byte, error) { - packet, hash, err := t.encode(t.priv, req) +func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) { + packet, hash, err := v4wire.Encode(t.priv, req) if err != nil { return hash, err } - return hash, t.write(toaddr, toid, req.name(), packet) + return hash, t.write(toaddr, toid, req.Name(), packet) } func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error { @@ -676,53 +513,6 @@ func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet [] return err } -func (t *UDPv4) encode(priv *eddsa.PrivateKey, req packetV4) (packet, hash []byte, err error) { - name := req.name() - b := new(bytes.Buffer) - b.Write(headSpace) - b.WriteByte(req.kind()) - if err := rlp.Encode(b, req); err != nil { - t.log.Error(fmt.Sprintf("Can't encode %s packet", name), "err", err) - return nil, nil, err - } - packet = b.Bytes() - sig, err := crypto.Sign(crypto.SHA3(packet[headSize:]), priv) - if err != nil { - t.log.Error(fmt.Sprintf("Can't sign %s packet", name), "err", err) - return nil, nil, err - } - copy(packet[macSize:], sig) - // add the hash to the front. Note: this doesn't protect the - // packet in any way. Our public key will be part of this hash in - // The future. - hash = crypto.SHA3(packet[macSize:]) - copy(packet, hash) - return packet, hash, nil -} -func (t *UDPv4) Encode(priv *eddsa.PrivateKey, req packetV4) (packet, hash []byte, err error) { - name := req.name() - b := new(bytes.Buffer) - b.Write(headSpace) - b.WriteByte(req.kind()) - if err := rlp.Encode(b, req); err != nil { - t.log.Error(fmt.Sprintf("Can't encode %s packet", name), "err", err) - return nil, nil, err - } - packet = b.Bytes() - sig, err := crypto.Sign(crypto.SHA3(packet[headSize:]), priv) - if err != nil { - t.log.Error(fmt.Sprintf("Can't sign %s packet", name), "err", err) - return nil, nil, err - } - copy(packet[macSize:], sig) - // add the hash to the front. Note: this doesn't protect the - // packet in any way. Our public key will be part of this hash in - // The future. - hash = crypto.SHA3(packet[macSize:]) - copy(packet, hash) - return packet, hash, nil -} - // readLoop runs in its own goroutine. it handles incoming UDP packets. func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) { defer t.wg.Done() @@ -754,60 +544,23 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) { } func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error { - packet, fromKey, hash, err := decodeV4(buf) + rawpacket, fromKey, hash, err := v4wire.Decode(buf) if err != nil { t.log.Debug("Bad discv4 packet", "addr", from, "err", err) return err } - fromID := fromKey.id() - if err == nil { - err = packet.preverify(t, from, fromID, fromKey) + packet := t.wrapPacket(rawpacket) + fromID := fromKey.ID() + if err == nil && packet.preverify != nil { + err = packet.preverify(packet, from, fromID, fromKey) } - t.log.Trace("<< "+packet.name(), "id", fromID, "addr", from, "err", err) - if err == nil { - packet.handle(t, from, fromID, hash) + t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err) + if err == nil && packet.handle != nil { + packet.handle(packet, from, fromID, hash) } return err } -func decodeV4(buf []byte) (packetV4, encPubkey, []byte, error) { - if len(buf) < headSize+1 { - return nil, encPubkey{}, nil, errPacketTooSmall - } - hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] - shouldhash := crypto.SHA3(buf[macSize:]) - if !bytes.Equal(hash, shouldhash) { - fmt.Println(common.Bytes2Hex(hash)) - fmt.Println(common.Bytes2Hex(shouldhash)) - return nil, encPubkey{}, nil, errBadHash - } - fromKey, err := recoverNodeKey(crypto.SHA3(buf[headSize:]), sig) - if err != nil { - return nil, fromKey, hash, err - } - - var req packetV4 - switch ptype := sigdata[0]; ptype { - case p_pingV4: - req = new(pingV4) - case p_pongV4: - req = new(pongV4) - case p_findnodeV4: - req = new(findnodeV4) - case p_neighborsV4: - req = new(neighborsV4) - case p_enrRequestV4: - req = new(enrRequestV4) - case p_enrResponseV4: - req = new(enrResponseV4) - default: - return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) - } - s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) - err = s.Decode(req) - return req, fromKey, hash, err -} - // checkBond checks if the given node has a recent enough endpoint proof. func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool { return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration @@ -825,49 +578,99 @@ func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) { } } -// expired checks whether the given UNIX time stamp is in the past. -func expired(ts uint64) bool { - return time.Unix(int64(ts), 0).Before(time.Now()) +func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) { + if rn.UDP <= 1024 { + return nil, errLowPort + } + if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { + return nil, err + } + if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { + return nil, errors.New("not contained in netrestrict whitelist") + } + key, err := v4wire.DecodePubkey(rn.ID) + if err != nil { + return nil, err + } + n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))) + err = n.ValidateComplete() + return n, err } -func seqFromTail(tail []rlp.RawValue) uint64 { - if len(tail) == 0 { - return 0 - } - var seq uint64 - rlp.DecodeBytes(tail[0], &seq) - return seq +func nodeToRPC(n *node) v4wire.Node { + var key eddsa.PublicKey + var ekey v4wire.Pubkey + if err := n.Load((*enode.Secp256k1)(&key)); err == nil { + ekey = v4wire.EncodePubkey(&key) + } + return v4wire.Node{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())} +} + +// wrapPacket returns the handler functions applicable to a packet. +func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 { + var h packetHandlerV4 + h.Packet = p + switch p.(type) { + case *v4wire.Ping: + h.preverify = t.verifyPing + h.handle = t.handlePing + case *v4wire.Pong: + h.preverify = t.verifyPong + case *v4wire.Findnode: + h.preverify = t.verifyFindnode + h.handle = t.handleFindnode + case *v4wire.Neighbors: + h.preverify = t.verifyNeighbors + case *v4wire.ENRRequest: + h.preverify = t.verifyENRRequest + h.handle = t.handleENRRequest + case *v4wire.ENRResponse: + h.preverify = t.verifyENRResponse + } + return &h +} + +// packetHandlerV4 wraps a packet with handler functions. +type packetHandlerV4 struct { + v4wire.Packet + senderKey *eddsa.PublicKey // used for ping + + // preverify checks whether the packet is valid and should be handled at all. + preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error + // handle handles the packet. + handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) } // PING/v4 -func (req *pingV4) name() string { return "PING/v4" } -func (req *pingV4) kind() byte { return p_pingV4 } +func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.Ping) -func (req *pingV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { - return errExpired - } - key, err := decodePubkey(fromKey) + senderKey, err := v4wire.DecodePubkey(fromKey) if err != nil { - return errors.New("invalid public key") + return err + } + if v4wire.Expired(req.Expiration) { + return errExpired } - req.senderKey = key + h.senderKey = senderKey return nil } -func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { +func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) { + req := h.Packet.(*v4wire.Ping) + // Reply. seq, _ := rlp.EncodeToBytes(t.localNode.Node().Seq()) - t.send(from, fromID, &pongV4{ - To: makeEndpoint(from, req.From.TCP), + t.send(from, fromID, &v4wire.Pong{ + To: v4wire.NewEndpoint(from, req.From.TCP), ReplyTok: mac, Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: []rlp.RawValue{seq}, }) // Ping back if our last pong on file is too far in the past. - n := wrapNode(enode.NewV4(req.senderKey, from.IP, int(req.From.TCP), from.Port)) + n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port)) if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration { t.sendPing(fromID, from, func() { t.tab.addVerifiedNode(n) @@ -875,7 +678,6 @@ func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []by } else { t.tab.addVerifiedNode(n) } - // Update node database and endpoint predictor. t.db.UpdateLastPingReceived(n.ID(), from.IP, time.Now()) t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) @@ -883,31 +685,26 @@ func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []by // PONG/v4 -func (req *pongV4) name() string { return "PONG/v4" } -func (req *pongV4) kind() byte { return p_pongV4 } +func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.Pong) -func (req *pongV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { + if v4wire.Expired(req.Expiration) { return errExpired } if !t.handleReply(fromID, from.IP, req) { return errUnsolicitedReply } - return nil -} - -func (req *pongV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) t.db.UpdateLastPongReceived(fromID, from.IP, time.Now()) + return nil } // FINDNODE/v4 -func (req *findnodeV4) name() string { return "FINDNODE/v4" } -func (req *findnodeV4) kind() byte { return p_findnodeV4 } +func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.Findnode) -func (req *findnodeV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { + if v4wire.Expired(req.Expiration) { return errExpired } if !t.checkBond(fromID, from.IP) { @@ -922,20 +719,22 @@ func (req *findnodeV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, f return nil } -func (req *findnodeV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { +func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) { + req := h.Packet.(*v4wire.Findnode) + // Determine closest nodes. target := enode.ID(crypto.SHA3Hash(req.Target[:])) closest := t.tab.findnodeByID(target, bucketSize, true).entries // Send neighbors in chunks with at most maxNeighbors per packet // to stay below the packet size limit. - p := neighborsV4{Expiration: uint64(time.Now().Add(expiration).Unix())} + p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())} var sent bool for _, n := range closest { if netutil.CheckRelayIP(from.IP, n.IP()) == nil { p.Nodes = append(p.Nodes, nodeToRPC(n)) } - if len(p.Nodes) == maxNeighbors { + if len(p.Nodes) == v4wire.MaxNeighbors { t.send(from, fromID, &p) p.Nodes = p.Nodes[:0] sent = true @@ -948,29 +747,24 @@ func (req *findnodeV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac // NEIGHBORS/v4 -func (req *neighborsV4) name() string { return "NEIGHBORS/v4" } -func (req *neighborsV4) kind() byte { return p_neighborsV4 } +func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.Neighbors) -func (req *neighborsV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { + if v4wire.Expired(req.Expiration) { return errExpired } - if !t.handleReply(fromID, from.IP, req) { + if !t.handleReply(fromID, from.IP, h.Packet) { return errUnsolicitedReply } return nil } -func (req *neighborsV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { -} - // ENRREQUEST/v4 -func (req *enrRequestV4) name() string { return "ENRREQUEST/v4" } -func (req *enrRequestV4) kind() byte { return p_enrRequestV4 } +func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.ENRRequest) -func (req *enrRequestV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { + if v4wire.Expired(req.Expiration) { return errExpired } if !t.checkBond(fromID, from.IP) { @@ -979,8 +773,8 @@ func (req *enrRequestV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, return nil } -func (req *enrRequestV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { - t.send(from, fromID, &enrResponseV4{ +func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) { + t.send(from, fromID, &v4wire.ENRResponse{ ReplyTok: mac, Record: *t.localNode.Node().Record(), }) @@ -988,15 +782,9 @@ func (req *enrRequestV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, ma // ENRRESPONSE/v4 -func (req *enrResponseV4) name() string { return "ENRRESPONSE/v4" } -func (req *enrResponseV4) kind() byte { return p_enrResponseV4 } - -func (req *enrResponseV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if !t.handleReply(fromID, from.IP, req) { +func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + if !t.handleReply(fromID, from.IP, h.Packet) { return errUnsolicitedReply } return nil } - -func (req *enrResponseV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { -} diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 397519f25..fe748e75e 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -20,9 +20,8 @@ import ( "bytes" crand "crypto/rand" "encoding/binary" - "encoding/hex" "errors" - "fmt" + "github.com/core-coin/go-core/p2p/discover/v4wire" "io" "math/rand" "net" @@ -31,24 +30,20 @@ import ( "testing" "time" - "github.com/core-coin/go-core/common" - "github.com/core-coin/go-core/crypto" "github.com/core-coin/go-core/internal/testlog" "github.com/core-coin/go-core/log" "github.com/core-coin/go-core/p2p/enode" "github.com/core-coin/go-core/p2p/enr" - "github.com/core-coin/go-core/rlp" eddsa "github.com/core-coin/go-goldilocks" - "github.com/davecgh/go-spew/spew" ) // shared test variables var ( futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) - testTarget = encPubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} - testRemote = rpcEndpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} - testLocalAnnounced = rpcEndpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} - testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} + testTarget = v4wire.Pubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} + testRemote = v4wire.Endpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} + testLocalAnnounced = v4wire.Endpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} + testLocal = v4wire.Endpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} ) type udpTest struct { @@ -89,19 +84,19 @@ func (test *udpTest) close() { } // handles a packet as if it had been sent to the transport. -func (test *udpTest) packetIn(wantError error, data packetV4) { +func (test *udpTest) packetIn(wantError error, data v4wire.Packet) { test.t.Helper() test.packetInFrom(wantError, test.remotekey, test.remoteaddr, data) } // handles a packet as if it had been sent to the transport by the key/endpoint. -func (test *udpTest) packetInFrom(wantError error, key *eddsa.PrivateKey, addr *net.UDPAddr, data packetV4) { +func (test *udpTest) packetInFrom(wantError error, key *eddsa.PrivateKey, addr *net.UDPAddr, data v4wire.Packet) { test.t.Helper() - enc, _, err := test.udp.encode(key, data) + enc, _, err := v4wire.Encode(key, data) if err != nil { - test.t.Errorf("%s encode error: %v", data.name(), err) + test.t.Errorf("%s encode error: %v", data.Name(), err) } test.sent = append(test.sent, enc) if err = test.udp.handlePacket(addr, enc); err != wantError { @@ -121,7 +116,7 @@ func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) { test.t.Error("packet receive error:", err) return false } - p, _, hash, err := decodeV4(dgram.data) + p, _, hash, err := v4wire.Decode(dgram.data) if err != nil { test.t.Errorf("sent packet decode error: %v", err) return false @@ -140,10 +135,10 @@ func TestUDPv4_packetErrors(t *testing.T) { test := newUDPTest(t) defer test.close() - test.packetIn(errExpired, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4}) - test.packetIn(errUnsolicitedReply, &pongV4{ReplyTok: []byte{}, Expiration: futureExp}) - test.packetIn(errUnknownNode, &findnodeV4{Expiration: futureExp}) - test.packetIn(errUnsolicitedReply, &neighborsV4{Expiration: futureExp}) + test.packetIn(errExpired, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4}) + test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: []byte{}, Expiration: futureExp}) + test.packetIn(errUnknownNode, &v4wire.Findnode{Expiration: futureExp}) + test.packetIn(errUnsolicitedReply, &v4wire.Neighbors{Expiration: futureExp}) } func TestUDPv4_pingTimeout(t *testing.T) { @@ -162,13 +157,8 @@ func TestUDPv4_pingTimeout(t *testing.T) { type testPacket byte -func (req testPacket) kind() byte { return byte(req) } -func (req testPacket) name() string { return "" } -func (req testPacket) preverify(*UDPv4, *net.UDPAddr, enode.ID, encPubkey) error { - return nil -} -func (req testPacket) handle(*UDPv4, *net.UDPAddr, enode.ID, []byte) { -} +func (req testPacket) Kind() byte { return byte(req) } +func (req testPacket) Name() string { return "" } func TestUDPv4_responseTimeouts(t *testing.T) { t.Parallel() @@ -193,7 +183,7 @@ func TestUDPv4_responseTimeouts(t *testing.T) { // within the timeout window. p := &replyMatcher{ ptype: byte(rand.Intn(255)), - callback: func(interface{}) (bool, bool) { return true, true }, + callback: func(v4wire.Packet) (bool, bool) { return true, true }, } binary.BigEndian.PutUint64(p.from[:], uint64(i)) if p.ptype <= 128 { @@ -249,7 +239,7 @@ func TestUDPv4_findnodeTimeout(t *testing.T) { toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} toid := enode.ID{1, 2, 3, 4} - target := encPubkey{4, 5, 6, 7} + target := v4wire.Pubkey{4, 5, 6, 7} result, err := test.udp.findnode(toid, toaddr, target) if err != errTimeout { t.Error("expected timeout error, got", err) @@ -266,7 +256,7 @@ func TestUDPv4_findnode(t *testing.T) { // put a few nodes into the table. their exact // distribution shouldn't matter much, although we need to // take care not to overflow any bucket. - nodes := &nodesByDistance{target: testTarget.id()} + nodes := &nodesByDistance{target: testTarget.ID()} live := make(map[enode.ID]bool) numCandidates := 2 * bucketSize for i := 0; i < numCandidates; i++ { @@ -286,32 +276,32 @@ func TestUDPv4_findnode(t *testing.T) { // ensure there's a bond with the test node, // findnode won't be accepted otherwise. pub := eddsa.Ed448DerivePublicKey(*test.remotekey) - remoteID := encodePubkey(&pub).id() + remoteID := v4wire.EncodePubkey(&pub).ID() test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now()) // check that closest neighbors are returned. - expected := test.table.findnodeByID(testTarget.id(), bucketSize, true) - test.packetIn(nil, &findnodeV4{Target: testTarget, Expiration: futureExp}) + expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true) + test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp}) waitNeighbors := func(want []*node) { - test.waitPacketOut(func(p *neighborsV4, to *net.UDPAddr, hash []byte) { + test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) { if len(p.Nodes) != len(want) { t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) } for i, n := range p.Nodes { - if n.ID.id() != want[i].ID() { + if n.ID.ID() != want[i].ID() { t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, n, expected.entries[i]) } - if !live[n.ID.id()] { - t.Errorf("result includes dead node %v", n.ID.id()) + if !live[n.ID.ID()] { + t.Errorf("result includes dead node %v", n.ID.ID()) } } }) } // Receive replies. want := expected.entries - if len(want) > maxNeighbors { - waitNeighbors(want[:maxNeighbors]) - want = want[maxNeighbors:] + if len(want) > v4wire.MaxNeighbors { + waitNeighbors(want[:v4wire.MaxNeighbors]) + want = want[v4wire.MaxNeighbors:] } waitNeighbors(want) } @@ -337,7 +327,7 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { // wait for the findnode to be sent. // after it is sent, the transport is waiting for a reply - test.waitPacketOut(func(p *findnodeV4, to *net.UDPAddr, hash []byte) { + test.waitPacketOut(func(p *v4wire.Findnode, to *net.UDPAddr, hash []byte) { if p.Target != testTarget { t.Errorf("wrong target: got %v, want %v", p.Target, testTarget) } @@ -350,12 +340,12 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042deaa@10.0.1.36:30301?discport=17")), wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e84aa@10.0.1.16:30303")), } - rpclist := make([]rpcNode, len(list)) + rpclist := make([]v4wire.Node, len(list)) for i := range list { rpclist[i] = nodeToRPC(list[i]) } - test.packetIn(nil, &neighborsV4{Expiration: futureExp, Nodes: rpclist[:2]}) - test.packetIn(nil, &neighborsV4{Expiration: futureExp, Nodes: rpclist[2:]}) + test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[:2]}) + test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[2:]}) // check that the sent neighbors are all returned by findnode select { @@ -379,10 +369,10 @@ func TestUDPv4_pingMatch(t *testing.T) { randToken := make([]byte, 32) crand.Read(randToken) - test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) - test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {}) - test.waitPacketOut(func(*pingV4, *net.UDPAddr, []byte) {}) - test.packetIn(errUnsolicitedReply, &pongV4{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp}) + test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) + test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {}) + test.waitPacketOut(func(*v4wire.Ping, *net.UDPAddr, []byte) {}) + test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp}) } // This test checks that reply matching of pong verifies the sender IP address. @@ -390,12 +380,12 @@ func TestUDPv4_pingMatchIP(t *testing.T) { test := newUDPTest(t) defer test.close() - test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) - test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {}) + test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) + test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {}) - test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) { + test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) { wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000} - test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &pongV4{ + test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{ ReplyTok: hash, To: testLocalAnnounced, Expiration: futureExp, @@ -410,15 +400,15 @@ func TestUDPv4_successfulPing(t *testing.T) { defer test.close() // The remote side sends a ping packet to initiate the exchange. - go test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) + go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) // The ping is replied to. - test.waitPacketOut(func(p *pongV4, to *net.UDPAddr, hash []byte) { - pinghash := test.sent[0][:macSize] + test.waitPacketOut(func(p *v4wire.Pong, to *net.UDPAddr, hash []byte) { + pinghash := test.sent[0][:32] if !bytes.Equal(p.ReplyTok, pinghash) { t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash) } - wantTo := rpcEndpoint{ + wantTo := v4wire.Endpoint{ // The mirrored UDP address is the UDP packet sender IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), // The mirrored TCP port is the one from the ping packet @@ -430,11 +420,11 @@ func TestUDPv4_successfulPing(t *testing.T) { }) // Remote is unknown, the table pings back. - test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) { + test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) { if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) { t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint()) } - wantTo := rpcEndpoint{ + wantTo := v4wire.Endpoint{ // The mirrored UDP address is the UDP packet sender. IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), @@ -443,7 +433,7 @@ func TestUDPv4_successfulPing(t *testing.T) { if !reflect.DeepEqual(p.To, wantTo) { t.Errorf("got ping.To %v, want %v", p.To, wantTo) } - test.packetIn(nil, &pongV4{ReplyTok: hash, Expiration: futureExp}) + test.packetIn(nil, &v4wire.Pong{ReplyTok: hash, Expiration: futureExp}) }) // The node should be added to the table shortly after getting the @@ -478,25 +468,25 @@ func TestUDPv4_CIP868(t *testing.T) { wantNode := test.udp.localNode.Node() // ENR requests aren't allowed before endpoint proof. - test.packetIn(errUnknownNode, &enrRequestV4{Expiration: futureExp}) + test.packetIn(errUnknownNode, &v4wire.ENRRequest{Expiration: futureExp}) // Perform endpoint proof and check for sequence number in packet tail. - test.packetIn(nil, &pingV4{Expiration: futureExp}) - test.waitPacketOut(func(p *pongV4, addr *net.UDPAddr, hash []byte) { - if seq := seqFromTail(p.Rest); seq != wantNode.Seq() { - t.Errorf("wrong sequence number in pong: %d, want %d", seq, wantNode.Seq()) + test.packetIn(nil, &v4wire.Ping{Expiration: futureExp}) + test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) { + if p.ENRSeq() != wantNode.Seq() { + t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq(), wantNode.Seq()) } }) - test.waitPacketOut(func(p *pingV4, addr *net.UDPAddr, hash []byte) { - if seq := seqFromTail(p.Rest); seq != wantNode.Seq() { - t.Errorf("wrong sequence number in ping: %d, want %d", seq, wantNode.Seq()) + test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) { + if p.ENRSeq() != wantNode.Seq() { + t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq(), wantNode.Seq()) } - test.packetIn(nil, &pongV4{Expiration: futureExp, ReplyTok: hash}) + test.packetIn(nil, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash}) }) // Request should work now. - test.packetIn(nil, &enrRequestV4{Expiration: futureExp}) - test.waitPacketOut(func(p *enrResponseV4, addr *net.UDPAddr, hash []byte) { + test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp}) + test.waitPacketOut(func(p *v4wire.ENRResponse, addr *net.UDPAddr, hash []byte) { n, err := enode.New(enode.ValidSchemes, &p.Record) if err != nil { t.Fatalf("invalid record: %v", err) @@ -507,201 +497,6 @@ func TestUDPv4_CIP868(t *testing.T) { }) } -// CIP-8 test vectors. -var testPackets = []struct { - input string - wantPacket interface{} -}{ - { - input: "3c8ead3bd8396db64aaccebf7c40aa194307a14bb42519967b01a8869b0db7566b8163632a0312ec5808fe0f9dec2cf85698a6ce4c6579217abd426f82ecf68e32a57789721cefa6162acf509c6f2280f673e22afe10d142800c53e7d03fac7a2c93b16a4c5d7e35e3b01ed8139259f9e24026ea396261ef674efccb7f7e95f96dff4d63ed5a8fc4090c5dae66f1c7a40900c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", - wantPacket: &pingV4{ - Version: 4, - From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, - Expiration: 1136239445, - Rest: []rlp.RawValue{}, - }, - }, - { - input: "6242b5a46b9ba8837f3708a59777a4b8640a6cacc6fc1b8ffcbe4dde7b49e09463596abad6d91e706cb20797e88406055c2abc2aa32cfa347b3b8cf7ced2e04c5219c357f78b0a1fc903e6e99c192274186a5211add7845680cf21813043bb7d2d3366a508c5c7f30aad679c85197b58ee0563f4624deaf64be27d94110307df17e074c9812b9c12e22b68c855e68af90500c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8001ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102", - wantPacket: &pingV4{ - Version: 4, - From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x01}, {0x02}}, - }, - }, - { - input: "a86cccff39536829aa3b26c496da3b12ad595cef598e0184a22f75300ae9c5ff0a5e1b6ed5ce199d9cc84bd12db0a176151c85731956fb2c0486fe2471a79db42fde99edec4e0c80f89910850429cc7a1e1c10c64c5e15dc0062b890d7ede928da241587560a49ded0e089aebe059b70f8f214373df2cb4ac08c4baa9c6a4905d6e71961153cc168aae86d20992d88cc3100c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c50102030405", - wantPacket: &pingV4{ - Version: 555, - From: rpcEndpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}}, - }, - }, - { - input: "da0b91e002acf3b850697491b6962eba12f5f89712532a72150ae4cff18845d75a325b440d96fef78cc4a30420bc60121608df35cc0fcfd604a5bf6ecfe337b403556140cc68faac23674f49864d51e662f98ea224c83dbb00ec5152af3b9694809d0b7344c861f89a712d492f4d64ffd9b9a081cfbf86e1c61522e3277e4b31da4eaed733df66b73565e4ca4d22cdc42f00c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8002f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506", - wantPacket: &pongV4{ - To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, - ReplyTok: common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"), - Expiration: 1136239445, - Rest: []rlp.RawValue{{0xC6, 0x01, 0x02, 0x03, 0xC2, 0x04, 0x05}, {0x06}}, - }, - }, - { - input: "fa6a7f74b9ce9b1d848b1bd9a2e650b39e2123056f7ad0d3afbe85b3cb081ad597227a42a4f0b9434a337163d3ddcd3f393f08f2d8c567fbbb8c1e4df568436bc732df994d6d68ff227844cda9767cebfecaa3a7c12af1ec00635fe52f5835a5734ad8e75bc4b5db3c28146cd51ac07a2c0941b6ad6646ea2e950fb0ba3dcb43ed48ebd4acf24a998ccad34d0b74017b1400c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8003f847b8394aa8946ebf664270106abd7aea3a27e66124c04deb391de0997be90f973d5369fd287442b79e7553b642aaca22159567b503c36e12891847aa8443b9a35582999983999999", - wantPacket: &findnodeV4{ - Target: hexEncPubkey("4aa8946ebf664270106abd7aea3a27e66124c04deb391de0997be90f973d5369fd287442b79e7553b642aaca22159567b503c36e12891847aa"), - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, - }, - }, - { - input: "4ecd2ff52df0f82632b9000d152c561a0437e5819b9d78c31dcf40f7a93c2560f3deaedc5462c71712377c08ec657ffadd3a5de8a45079833563cbf925ed3e2da9f6a2a186c860b75ada6ef1b6147cab49548ccf35dd6c6d80a1c3ac5053c7be1a8d979bf6c1368dcdead23859f6533881b41bb72c7c85b913cf96cecc458be99f5a13253bbd1b9807a035a89e98b5bb1000c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8004f9013ff90134f846846321163782115c82115db8394aa8946ebf664270106abd7aea3a27e66124c04deb391de0997be90f973d5369fd287442b79e7553b642aaca22159567b503c36e12891847aaf84284010203040101b8395acf8e211e3d3e2ba310afa91edc15389a9cb2e59525774646dc46030d18d880b5f0ef616f842231a355e725589dd45a2f2677d028b6fe46aaf8529020010db83c4d001500000000abcdef12820d05820d05b8394b9fcfcbeb73288841583b311b5ac405e8cba059953db4f7cfa3008a7b41099f8713ba6195ed0168607342675f77d26d5a6088e540401ac2aaf8529020010db885a308d313198a2e037073488203e78203e8b839d3c777805908f662ce69981006a68da37b3e2105ab9a96b28b79832f7f40715e77d1c589f14bc914d7915b8976317c78603b732a7c7f5453aa8443b9a355010203", - wantPacket: &neighborsV4{ - Nodes: []rpcNode{ - { - ID: hexEncPubkey("4aa8946ebf664270106abd7aea3a27e66124c04deb391de0997be90f973d5369fd287442b79e7553b642aaca22159567b503c36e12891847aa"), - IP: net.ParseIP("99.33.22.55").To4(), - UDP: 4444, - TCP: 4445, - }, - { - ID: hexEncPubkey("5acf8e211e3d3e2ba310afa91edc15389a9cb2e59525774646dc46030d18d880b5f0ef616f842231a355e725589dd45a2f2677d028b6fe46aa"), - IP: net.ParseIP("1.2.3.4").To4(), - UDP: 1, - TCP: 1, - }, - { - ID: hexEncPubkey("4b9fcfcbeb73288841583b311b5ac405e8cba059953db4f7cfa3008a7b41099f8713ba6195ed0168607342675f77d26d5a6088e540401ac2aa"), - IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), - UDP: 3333, - TCP: 3333, - }, - { - ID: hexEncPubkey("d3c777805908f662ce69981006a68da37b3e2105ab9a96b28b79832f7f40715e77d1c589f14bc914d7915b8976317c78603b732a7c7f5453aa"), - IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), - UDP: 999, - TCP: 1000, - }, - }, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x01}, {0x02}, {0x03}}, - }, - }, -} - -func TestUDPv4_forwardCompatibility(t *testing.T) { - testkey, _ := crypto.HexToEDDSA("835bbff17efac2c97895784041c507959cdb9e45c599cc205e453a962c11c09ac8834f6524d0842cc469db2afcc0424ca4afc42968d3441846") - pub := eddsa.Ed448DerivePublicKey(*testkey) - wantNodeKey := encodePubkey(&pub) - for _, test := range testPackets { - input, err := hex.DecodeString(test.input) - if err != nil { - t.Fatalf("invalid hex: %s", test.input) - } - packet, nodekey, _, err := decodeV4(input) - if err != nil { - t.Errorf("did not accept packet %s\n%v", test.input, err) - continue - } - if !reflect.DeepEqual(packet, test.wantPacket) { - t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket)) - } - if nodekey != wantNodeKey { - t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey) - } - } -} - -// This test verifies that a small network of nodes can boot up into a healthy state. -func TestUDPv4_smallNetConvergence(t *testing.T) { - t.Parallel() - - // Start the network. - nodes := make([]*UDPv4, 4) - for i := range nodes { - var cfg Config - if i > 0 { - bn := nodes[0].Self() - cfg.Bootnodes = []*enode.Node{bn} - } - nodes[i] = startLocalhostV4(t, cfg) - defer nodes[i].Close() - } - - // Run through the iterator on all nodes until - // they have all found each other. - status := make(chan error, len(nodes)) - for i := range nodes { - node := nodes[i] - go func() { - found := make(map[enode.ID]bool, len(nodes)) - it := node.RandomNodes() - for it.Next() { - found[it.Node().ID()] = true - if len(found) == len(nodes) { - status <- nil - return - } - } - status <- fmt.Errorf("node %s didn't find all nodes", node.Self().ID().TerminalString()) - }() - } - - // Wait for all status reports. - timeout := time.NewTimer(30 * time.Second) - defer timeout.Stop() - for received := 0; received < len(nodes); { - select { - case <-timeout.C: - for _, node := range nodes { - node.Close() - } - case err := <-status: - received++ - if err != nil { - t.Error("ERROR:", err) - return - } - } - } -} - -func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 { - t.Helper() - - cfg.PrivateKey = newkey() - db, _ := enode.OpenDB("") - ln := enode.NewLocalNode(db, cfg.PrivateKey) - - // Prefix logs with node ID. - lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString()) - lfmt := log.TerminalFormat(false) - cfg.Log = testlog.Logger(t, log.LvlTrace) - cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error { - t.Logf("%s %s", lprefix, lfmt.Format(r)) - return nil - })) - - // Listen. - socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}}) - if err != nil { - t.Fatal(err) - } - realaddr := socket.LocalAddr().(*net.UDPAddr) - ln.SetStaticIP(realaddr.IP) - ln.SetFallbackUDP(realaddr.Port) - udp, err := ListenV4(socket, ln, cfg) - if err != nil { - t.Fatal(err) - } - return udp -} - // dgramPipe is a fake UDP socket. It queues all sent datagrams. type dgramPipe struct { mu *sync.Mutex diff --git a/p2p/discover/v4wire/4wire_test.go b/p2p/discover/v4wire/4wire_test.go new file mode 100644 index 000000000..f7edd23a9 --- /dev/null +++ b/p2p/discover/v4wire/4wire_test.go @@ -0,0 +1,151 @@ +// Copyright 2022 by the Authors +// This file is part of the go-core library. +// +// The go-core 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-core 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-core library. If not, see . + +package v4wire + +import ( + "encoding/hex" + "github.com/core-coin/go-core/common" + "github.com/core-coin/go-core/crypto" + "github.com/core-coin/go-core/rlp" + eddsa "github.com/core-coin/go-goldilocks" + "github.com/davecgh/go-spew/spew" + "net" + "reflect" + "testing" +) + +// CIP-8 test vectors. +var testPackets = []struct { + input string + wantPacket interface{} +}{ + { + input: "3c8ead3bd8396db64aaccebf7c40aa194307a14bb42519967b01a8869b0db7566b8163632a0312ec5808fe0f9dec2cf85698a6ce4c6579217abd426f82ecf68e32a57789721cefa6162acf509c6f2280f673e22afe10d142800c53e7d03fac7a2c93b16a4c5d7e35e3b01ed8139259f9e24026ea396261ef674efccb7f7e95f96dff4d63ed5a8fc4090c5dae66f1c7a40900c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", + wantPacket: &Ping{ + Version: 4, + From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, + To: Endpoint{net.ParseIP("::1"), 2222, 3333}, + Expiration: 1136239445, + Rest: []rlp.RawValue{}, + }, + }, + { + input: "6242b5a46b9ba8837f3708a59777a4b8640a6cacc6fc1b8ffcbe4dde7b49e09463596abad6d91e706cb20797e88406055c2abc2aa32cfa347b3b8cf7ced2e04c5219c357f78b0a1fc903e6e99c192274186a5211add7845680cf21813043bb7d2d3366a508c5c7f30aad679c85197b58ee0563f4624deaf64be27d94110307df17e074c9812b9c12e22b68c855e68af90500c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8001ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102", + wantPacket: &Ping{ + Version: 4, + From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, + To: Endpoint{net.ParseIP("::1"), 2222, 3333}, + Expiration: 1136239445, + Rest: []rlp.RawValue{{0x01}, {0x02}}, + }, + }, + { + input: "a86cccff39536829aa3b26c496da3b12ad595cef598e0184a22f75300ae9c5ff0a5e1b6ed5ce199d9cc84bd12db0a176151c85731956fb2c0486fe2471a79db42fde99edec4e0c80f89910850429cc7a1e1c10c64c5e15dc0062b890d7ede928da241587560a49ded0e089aebe059b70f8f214373df2cb4ac08c4baa9c6a4905d6e71961153cc168aae86d20992d88cc3100c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c50102030405", + wantPacket: &Ping{ + Version: 555, + From: Endpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544}, + To: Endpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, + Expiration: 1136239445, + Rest: []rlp.RawValue{{0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}}, + }, + }, + { + input: "da0b91e002acf3b850697491b6962eba12f5f89712532a72150ae4cff18845d75a325b440d96fef78cc4a30420bc60121608df35cc0fcfd604a5bf6ecfe337b403556140cc68faac23674f49864d51e662f98ea224c83dbb00ec5152af3b9694809d0b7344c861f89a712d492f4d64ffd9b9a081cfbf86e1c61522e3277e4b31da4eaed733df66b73565e4ca4d22cdc42f00c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8002f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506", + wantPacket: &Pong{ + To: Endpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, + ReplyTok: common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"), + Expiration: 1136239445, + Rest: []rlp.RawValue{{0xC6, 0x01, 0x02, 0x03, 0xC2, 0x04, 0x05}, {0x06}}, + }, + }, + { + input: "fa6a7f74b9ce9b1d848b1bd9a2e650b39e2123056f7ad0d3afbe85b3cb081ad597227a42a4f0b9434a337163d3ddcd3f393f08f2d8c567fbbb8c1e4df568436bc732df994d6d68ff227844cda9767cebfecaa3a7c12af1ec00635fe52f5835a5734ad8e75bc4b5db3c28146cd51ac07a2c0941b6ad6646ea2e950fb0ba3dcb43ed48ebd4acf24a998ccad34d0b74017b1400c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8003f847b8394aa8946ebf664270106abd7aea3a27e66124c04deb391de0997be90f973d5369fd287442b79e7553b642aaca22159567b503c36e12891847aa8443b9a35582999983999999", + wantPacket: &Findnode{ + Target: hexPubkey("4aa8946ebf664270106abd7aea3a27e66124c04deb391de0997be90f973d5369fd287442b79e7553b642aaca22159567b503c36e12891847aa"), + Expiration: 1136239445, + Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, + }, + }, + { + input: "4ecd2ff52df0f82632b9000d152c561a0437e5819b9d78c31dcf40f7a93c2560f3deaedc5462c71712377c08ec657ffadd3a5de8a45079833563cbf925ed3e2da9f6a2a186c860b75ada6ef1b6147cab49548ccf35dd6c6d80a1c3ac5053c7be1a8d979bf6c1368dcdead23859f6533881b41bb72c7c85b913cf96cecc458be99f5a13253bbd1b9807a035a89e98b5bb1000c0d4a39c59cc720a122fdb9e28d57979c19bf64988e42c2b1bf916d009a26fcc1feacd90b610f7a9ff614320035138a4739ed0e4e877cb1f8004f9013ff90134f846846321163782115c82115db8394aa8946ebf664270106abd7aea3a27e66124c04deb391de0997be90f973d5369fd287442b79e7553b642aaca22159567b503c36e12891847aaf84284010203040101b8395acf8e211e3d3e2ba310afa91edc15389a9cb2e59525774646dc46030d18d880b5f0ef616f842231a355e725589dd45a2f2677d028b6fe46aaf8529020010db83c4d001500000000abcdef12820d05820d05b8394b9fcfcbeb73288841583b311b5ac405e8cba059953db4f7cfa3008a7b41099f8713ba6195ed0168607342675f77d26d5a6088e540401ac2aaf8529020010db885a308d313198a2e037073488203e78203e8b839d3c777805908f662ce69981006a68da37b3e2105ab9a96b28b79832f7f40715e77d1c589f14bc914d7915b8976317c78603b732a7c7f5453aa8443b9a355010203", + wantPacket: &Neighbors{ + Nodes: []Node{ + { + ID: hexPubkey("4aa8946ebf664270106abd7aea3a27e66124c04deb391de0997be90f973d5369fd287442b79e7553b642aaca22159567b503c36e12891847aa"), + IP: net.ParseIP("99.33.22.55").To4(), + UDP: 4444, + TCP: 4445, + }, + { + ID: hexPubkey("5acf8e211e3d3e2ba310afa91edc15389a9cb2e59525774646dc46030d18d880b5f0ef616f842231a355e725589dd45a2f2677d028b6fe46aa"), + IP: net.ParseIP("1.2.3.4").To4(), + UDP: 1, + TCP: 1, + }, + { + ID: hexPubkey("4b9fcfcbeb73288841583b311b5ac405e8cba059953db4f7cfa3008a7b41099f8713ba6195ed0168607342675f77d26d5a6088e540401ac2aa"), + IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), + UDP: 3333, + TCP: 3333, + }, + { + ID: hexPubkey("d3c777805908f662ce69981006a68da37b3e2105ab9a96b28b79832f7f40715e77d1c589f14bc914d7915b8976317c78603b732a7c7f5453aa"), + IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), + UDP: 999, + TCP: 1000, + }, + }, + Expiration: 1136239445, + Rest: []rlp.RawValue{{0x01}, {0x02}, {0x03}}, + }, + }, +} + +func TestUDPv4_forwardCompatibility(t *testing.T) { + testkey, _ := crypto.HexToEDDSA("835bbff17efac2c97895784041c507959cdb9e45c599cc205e453a962c11c09ac8834f6524d0842cc469db2afcc0424ca4afc42968d3441846") + pub := eddsa.Ed448DerivePublicKey(*testkey) + wantNodeKey := EncodePubkey(&pub) + for _, test := range testPackets { + input, err := hex.DecodeString(test.input) + if err != nil { + t.Fatalf("invalid hex: %s", test.input) + } + packet, nodekey, _, err := Decode(input) + if err != nil { + t.Errorf("did not accept packet %s\n%v", test.input, err) + continue + } + if !reflect.DeepEqual(packet, test.wantPacket) { + t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket)) + } + if nodekey != wantNodeKey { + t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey) + } + } +} + +func hexPubkey(h string) (ret Pubkey) { + b, err := hex.DecodeString(h) + if err != nil { + panic(err) + } + if len(b) != len(ret) { + panic("invalid length") + } + copy(ret[:], b) + return ret +} diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go new file mode 100644 index 000000000..7f7569e1e --- /dev/null +++ b/p2p/discover/v4wire/v4wire.go @@ -0,0 +1,288 @@ +// Copyright 2022 by the Authors +// This file is part of the go-core library. +// +// The go-core 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-core 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-core library. If not, see . + +// Package v4wire implements the Discovery v4 Wire Protocol. +package v4wire + +import ( + "bytes" + "errors" + "fmt" + eddsa "github.com/core-coin/go-goldilocks" + "net" + "time" + + "github.com/core-coin/go-core/crypto" + "github.com/core-coin/go-core/p2p/enode" + "github.com/core-coin/go-core/p2p/enr" + "github.com/core-coin/go-core/rlp" +) + +// RPC packet types +const ( + PingPacket = iota + 1 // zero is 'reserved' + PongPacket + FindnodePacket + NeighborsPacket + ENRRequestPacket + ENRResponsePacket +) + +// RPC request structures +type ( + Ping struct { + Version uint + From, To Endpoint + Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // Pong is the reply to ping. + Pong struct { + // This field should mirror the UDP envelope address + // of the ping packet, which provides a way to discover the + // the external address (after NAT). + To Endpoint + ReplyTok []byte // This contains the hash of the ping packet. + Expiration uint64 // Absolute timestamp at which the packet becomes invalid. + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // Findnode is a query for nodes close to the given target. + Findnode struct { + Target Pubkey + Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // Neighbors is the reply to findnode. + Neighbors struct { + Nodes []Node + Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // enrRequest queries for the remote node's record. + ENRRequest struct { + Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // enrResponse is the reply to enrRequest. + ENRResponse struct { + ReplyTok []byte // Hash of the enrRequest packet. + Record enr.Record + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } +) + +// This number is the maximum number of neighbor nodes in a Neigbors packet. +const MaxNeighbors = 12 + +// This code computes the MaxNeighbors constant value. + +// func init() { +// var maxNeighbors int +// p := Neighbors{Expiration: ^uint64(0)} +// maxSizeNode := Node{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} +// for n := 0; ; n++ { +// p.Nodes = append(p.Nodes, maxSizeNode) +// size, _, err := rlp.EncodeToReader(p) +// if err != nil { +// // If this ever happens, it will be caught by the unit tests. +// panic("cannot encode: " + err.Error()) +// } +// if headSize+size+1 >= 1280 { +// maxNeighbors = n +// break +// } +// } +// fmt.Println("maxNeighbors", maxNeighbors) +// } + +// Pubkey represents an encoded 57 eddsa public key. +type Pubkey [57]byte + +// ID returns the node ID corresponding to the public key. +func (e Pubkey) ID() enode.ID { + return enode.ID(crypto.SHA3Hash(e[:])) +} + +// Node represents information about a node. +type Node struct { + IP net.IP // len 4 for IPv4 or 16 for IPv6 + UDP uint16 // for discovery protocol + TCP uint16 // for RLPx protocol + ID Pubkey +} + +// Endpoint represents a network endpoint. +type Endpoint struct { + IP net.IP // len 4 for IPv4 or 16 for IPv6 + UDP uint16 // for discovery protocol + TCP uint16 // for RLPx protocol +} + +// NewEndpoint creates an endpoint. +func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint { + ip := net.IP{} + if ip4 := addr.IP.To4(); ip4 != nil { + ip = ip4 + } else if ip6 := addr.IP.To16(); ip6 != nil { + ip = ip6 + } + return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} +} + +type Packet interface { + // packet name and type for logging purposes. + Name() string + Kind() byte +} + +func (req *Ping) Name() string { return "PING/v4" } +func (req *Ping) Kind() byte { return PingPacket } +func (req *Ping) ENRSeq() uint64 { return seqFromTail(req.Rest) } + +func (req *Pong) Name() string { return "PONG/v4" } +func (req *Pong) Kind() byte { return PongPacket } +func (req *Pong) ENRSeq() uint64 { return seqFromTail(req.Rest) } + +func (req *Findnode) Name() string { return "FINDNODE/v4" } +func (req *Findnode) Kind() byte { return FindnodePacket } + +func (req *Neighbors) Name() string { return "NEIGHBORS/v4" } +func (req *Neighbors) Kind() byte { return NeighborsPacket } + +func (req *ENRRequest) Name() string { return "ENRREQUEST/v4" } +func (req *ENRRequest) Kind() byte { return ENRRequestPacket } + +func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" } +func (req *ENRResponse) Kind() byte { return ENRResponsePacket } + +// Expired checks whether the given UNIX time stamp is in the past. +func Expired(ts uint64) bool { + return time.Unix(int64(ts), 0).Before(time.Now()) +} + +func seqFromTail(tail []rlp.RawValue) uint64 { + if len(tail) == 0 { + return 0 + } + var seq uint64 + rlp.DecodeBytes(tail[0], &seq) + return seq +} + +// Encoder/decoder. + +const ( + macSize = 32 + sigSize = crypto.ExtendedSignatureLength + headSize = macSize + sigSize // space of packet frame data +) + +var ( + ErrPacketTooSmall = errors.New("too small") + ErrBadHash = errors.New("bad hash") + ErrBadPoint = errors.New("invalid curve point") +) + +var headSpace = make([]byte, headSize) + +// Decode reads a discovery v4 packet. +func Decode(input []byte) (Packet, Pubkey, []byte, error) { + if len(input) < headSize+1 { + return nil, Pubkey{}, nil, ErrPacketTooSmall + } + hash, sig, sigdata := input[:macSize], input[macSize:headSize], input[headSize:] + shouldhash := crypto.SHA3(input[macSize:]) + if !bytes.Equal(hash, shouldhash) { + return nil, Pubkey{}, nil, ErrBadHash + } + fromKey, err := recoverNodeKey(crypto.SHA3(input[headSize:]), sig) + if err != nil { + return nil, fromKey, hash, err + } + + var req Packet + switch ptype := sigdata[0]; ptype { + case PingPacket: + req = new(Ping) + case PongPacket: + req = new(Pong) + case FindnodePacket: + req = new(Findnode) + case NeighborsPacket: + req = new(Neighbors) + case ENRRequestPacket: + req = new(ENRRequest) + case ENRResponsePacket: + req = new(ENRResponse) + default: + return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) + } + s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) + err = s.Decode(req) + return req, fromKey, hash, err +} + +// Encode encodes a discovery packet. +func Encode(priv *eddsa.PrivateKey, req Packet) (packet, hash []byte, err error) { + b := new(bytes.Buffer) + b.Write(headSpace) + b.WriteByte(req.Kind()) + if err := rlp.Encode(b, req); err != nil { + return nil, nil, err + } + packet = b.Bytes() + sig, err := crypto.Sign(crypto.SHA3(packet[headSize:]), priv) + if err != nil { + return nil, nil, err + } + copy(packet[macSize:], sig) + // Add the hash to the front. Note: this doesn't protect the packet in any way. + hash = crypto.SHA3(packet[macSize:]) + copy(packet, hash) + return packet, hash, nil +} + +// recoverNodeKey computes the public key used to sign the given hash from the signature. +func recoverNodeKey(hash, sig []byte) (key Pubkey, err error) { + pubkey, err := crypto.Ecrecover(hash, sig) + if err != nil { + return key, err + } + copy(key[:], pubkey[:]) + return key, nil +} + +// EncodePubkey encodes a eddsa public key. +func EncodePubkey(pub *eddsa.PublicKey) (key Pubkey) { + copy(key[:], pub[:]) + return key +} + +// DecodePubkey reads an encoded eddsa public key. +func DecodePubkey(e Pubkey) (*eddsa.PublicKey, error) { + return crypto.UnmarshalPubkey(e[:]) +} diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index cac1c4179..e6768262d 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -250,9 +250,8 @@ func (n *autodisc) String() string { defer n.mu.Unlock() if n.found == nil { return n.what - } else { - return n.found.String() } + return n.found.String() } // wait blocks until auto-discovery has been performed. diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go index 2922a2481..a4cba4377 100644 --- a/p2p/nodestate/nodestate.go +++ b/p2p/nodestate/nodestate.go @@ -31,34 +31,46 @@ import ( "unsafe" ) +var ( + ErrInvalidField = errors.New("invalid field type") + ErrClosed = errors.New("already closed") +) + type ( - // NodeStateMachine connects different system components operating on subsets of - // network nodes. Node states are represented by 64 bit vectors with each bit assigned - // to a state flag. Each state flag has a descriptor structure and the mapping is - // created automatically. It is possible to subscribe to subsets of state flags and - // receive a callback if one of the nodes has a relevant state flag changed. - // Callbacks can also modify further flags of the same node or other nodes. State - // updates only return after all immediate effects throughout the system have happened - // (deadlocks should be avoided by design of the implemented state logic). The caller - // can also add timeouts assigned to a certain node and a subset of state flags. - // If the timeout elapses, the flags are reset. If all relevant flags are reset then - // the timer is dropped. State flags with no timeout are persisted in the database - // if the flag descriptor enables saving. If a node has no state flags set at any - // moment then it is discarded. - // - // Extra node fields can also be registered so system components can also store more - // complex state for each node that is relevant to them, without creating a custom - // peer set. Fields can be shared across multiple components if they all know the - // field ID. Subscription to fields is also possible. Persistent fields should have - // an encoder and a decoder function. + // NodeStateMachine implements a network node-related event subscription system. + // It can assign binary state flags and fields of arbitrary type to each node and allows + // subscriptions to flag/field changes which can also modify further flags and fields, + // potentially triggering further subscriptions. An operation includes an initial change + // and all resulting subsequent changes and always ends in a consistent global state. + // It is initiated by a "top level" SetState/SetField call that blocks (also blocking other + // top-level functions) until the operation is finished. Callbacks making further changes + // should use the non-blocking SetStateSub/SetFieldSub functions. The tree of events + // resulting from the initial changes is traversed in a breadth-first order, ensuring for + // each subscription callback that all other callbacks caused by the same change triggering + // the current callback are processed before anything is triggered by the changes made in the + // current callback. In practice this logic ensures that all subscriptions "see" events in + // the logical order, callbacks are never called concurrently and "back and forth" effects + // are also possible. The state machine design should ensure that infinite event cycles + // cannot happen. + // The caller can also add timeouts assigned to a certain node and a subset of state flags. + // If the timeout elapses, the flags are reset. If all relevant flags are reset then the timer + // is dropped. State flags with no timeout are persisted in the database if the flag + // descriptor enables saving. If a node has no state flags set at any moment then it is discarded. + // Note: in order to avoid mutex deadlocks the callbacks should never lock a mutex that + // might be locked when the top level SetState/SetField functions are called. If a function + // potentially performs state/field changes then it is recommended to mention this fact in the + // function description, along with whether it should run inside an operation callback. NodeStateMachine struct { - started, stopped bool + started, closed bool lock sync.Mutex clock mclock.Clock db xcbdb.KeyValueStore dbNodeKey []byte nodes map[enode.ID]*nodeInfo offlineCallbackList []offlineCallback + opFlag bool // an operation has started + opWait *sync.Cond // signaled when the operation ends + opPending []func() // pending callback list of the current operation // Registered state flags or fields. Modifications are allowed // only when the node state machine has not been started. @@ -127,11 +139,12 @@ type ( // nodeInfo contains node state, fields and state timeouts nodeInfo struct { - node *enode.Node - state bitMask - timeouts []*nodeStateTimeout - fields []interface{} - db, dirty bool + node *enode.Node + state bitMask + timeouts []*nodeStateTimeout + fields []interface{} + fieldCount int + db, dirty bool } nodeInfoEnc struct { @@ -157,7 +170,7 @@ type ( } offlineCallback struct { - node *enode.Node + node *nodeInfo state bitMask fields []interface{} } @@ -318,10 +331,11 @@ func NewNodeStateMachine(db xcbdb.KeyValueStore, dbKey []byte, clock mclock.Cloc nodes: make(map[enode.ID]*nodeInfo), fields: make([]*fieldInfo, len(setup.fields)), } + ns.opWait = sync.NewCond(&ns.lock) stateNameMap := make(map[string]int) for index, flag := range setup.flags { if _, ok := stateNameMap[flag.name]; ok { - panic("Node state flag name collision") + panic("Node state flag name collision: " + flag.name) } stateNameMap[flag.name] = index if flag.persistent { @@ -331,7 +345,7 @@ func NewNodeStateMachine(db xcbdb.KeyValueStore, dbKey []byte, clock mclock.Cloc fieldNameMap := make(map[string]int) for index, field := range setup.fields { if _, ok := fieldNameMap[field.name]; ok { - panic("Node field name collision") + panic("Node field name collision: " + field.name) } ns.fields[index] = &fieldInfo{fieldDefinition: field} fieldNameMap[field.name] = index @@ -356,10 +370,12 @@ func (ns *NodeStateMachine) fieldIndex(field Field) int { } // SubscribeState adds a node state subscription. The callback is called while the state -// machine mutex is not held and it is allowed to make further state updates. All immediate -// changes throughout the system are processed in the same thread/goroutine. It is the -// responsibility of the implemented state logic to avoid deadlocks caused by the callbacks, -// infinite toggling of flags or hazardous/non-deterministic state changes. +// machine mutex is not held and it is allowed to make further state updates using the +// non-blocking SetStateSub/SetFieldSub functions. All callbacks of an operation are running +// from the thread/goroutine of the initial caller and parallel operations are not permitted. +// Therefore the callback is never called concurrently. It is the responsibility of the +// implemented state logic to avoid deadlocks and to reach a stable state in a finite amount +// of steps. // State subscriptions should be installed before loading the node database or making the // first state update. func (ns *NodeStateMachine) SubscribeState(flags Flags, callback StateCallback) { @@ -407,26 +423,33 @@ func (ns *NodeStateMachine) Start() { if ns.db != nil { ns.loadFromDb() } - ns.lock.Unlock() + + ns.opStart() ns.offlineCallbacks(true) + ns.opFinish() + ns.lock.Unlock() } // Stop stops the state machine and saves its state if a database was supplied func (ns *NodeStateMachine) Stop() { ns.lock.Lock() + defer ns.lock.Unlock() + + ns.checkStarted() + if !ns.opStart() { + panic("already closed") + } for _, node := range ns.nodes { fields := make([]interface{}, len(node.fields)) copy(fields, node.fields) - ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node.node, node.state, fields}) + ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields}) } - ns.stopped = true if ns.db != nil { ns.saveToDb() - ns.lock.Unlock() - } else { - ns.lock.Unlock() } ns.offlineCallbacks(false) + ns.closed = true + ns.opFinish() } // loadFromDb loads persisted node states from the database @@ -476,6 +499,7 @@ func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) { if decode := ns.fields[i].decode; decode != nil { if field, err := decode(encField); err == nil { node.fields[i] = field + node.fieldCount++ } else { log.Error("Failed to decode node field", "id", id, "field name", ns.fields[i].name, "error", err) return @@ -490,7 +514,7 @@ func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) { node.state = enc.State fields := make([]interface{}, len(node.fields)) copy(fields, node.fields) - ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node.node, node.state, fields}) + ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields}) log.Debug("Loaded node state", "id", id, "state", Flags{mask: enc.State, setup: ns.setup}) } @@ -504,14 +528,6 @@ func (ns *NodeStateMachine) saveNode(id enode.ID, node *nodeInfo) error { for _, t := range node.timeouts { storedState &= ^t.mask } - if storedState == 0 { - if node.db { - node.db = false - ns.deleteNode(id) - } - node.dirty = false - return nil - } enc := nodeInfoEnc{ Enr: *node.node.Record(), @@ -536,6 +552,14 @@ func (ns *NodeStateMachine) saveNode(id enode.ID, node *nodeInfo) error { enc.Fields[i] = blob lastIndex = i } + if storedState == 0 && lastIndex == -1 { + if node.db { + node.db = false + ns.deleteNode(id) + } + node.dirty = false + return nil + } enc.Fields = enc.Fields[:lastIndex+1] data, err := rlp.EncodeToBytes(&enc) if err != nil { @@ -594,23 +618,36 @@ func (ns *NodeStateMachine) Persist(n *enode.Node) error { return nil } -// SetState updates the given node state flags and processes all resulting callbacks. -// It only returns after all subsequent immediate changes (including those changed by the -// callbacks) have been processed. If a flag with a timeout is set again, the operation -// removes or replaces the existing timeout. -func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) { +// SetState updates the given node state flags and blocks until the operation is finished. +// If a flag with a timeout is set again, the operation removes or replaces the existing timeout. +func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) error { ns.lock.Lock() - ns.checkStarted() - if ns.stopped { - ns.lock.Unlock() - return + defer ns.lock.Unlock() + + if !ns.opStart() { + return ErrClosed } + ns.setState(n, setFlags, resetFlags, timeout) + ns.opFinish() + return nil +} +// SetStateSub updates the given node state flags without blocking (should be called +// from a subscription/operation callback). +func (ns *NodeStateMachine) SetStateSub(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) { + ns.lock.Lock() + defer ns.lock.Unlock() + + ns.opCheck() + ns.setState(n, setFlags, resetFlags, timeout) +} + +func (ns *NodeStateMachine) setState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) { + ns.checkStarted() set, reset := ns.stateMask(setFlags), ns.stateMask(resetFlags) id, node := ns.updateEnode(n) if node == nil { if set == 0 { - ns.lock.Unlock() return } node = ns.newNode(n) @@ -620,21 +657,18 @@ func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, newState := (node.state & (^reset)) | set changed := oldState ^ newState node.state = newState - // Remove the timeout callbacks for all reset and set flags, // even they are not existent(it's noop). ns.removeTimeouts(node, set|reset) - // Register the timeout callback if the new state is not empty - // and timeout itself is required. - if timeout != 0 && newState != 0 { + // Register the timeout callback if required + if timeout != 0 && set != 0 { ns.addTimeout(n, set, timeout) } if newState == oldState { - ns.lock.Unlock() return } - if newState == 0 { + if newState == 0 && node.fieldCount == 0 { delete(ns.nodes, id) if node.db { ns.deleteNode(id) @@ -644,68 +678,118 @@ func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, node.dirty = true } } - ns.lock.Unlock() - // call state update subscription callbacks without holding the mutex - for _, sub := range ns.stateSubs { - if changed&sub.mask != 0 { - sub.callback(n, Flags{mask: oldState & sub.mask, setup: ns.setup}, Flags{mask: newState & sub.mask, setup: ns.setup}) - } - } - if newState == 0 { - // call field subscriptions for discarded fields - for i, v := range node.fields { - if v != nil { - f := ns.fields[i] - if len(f.subs) > 0 { - for _, cb := range f.subs { - cb(n, Flags{setup: ns.setup}, v, nil) - } - } + callback := func() { + for _, sub := range ns.stateSubs { + if changed&sub.mask != 0 { + sub.callback(n, Flags{mask: oldState & sub.mask, setup: ns.setup}, Flags{mask: newState & sub.mask, setup: ns.setup}) } } } + ns.opPending = append(ns.opPending, callback) +} + +// opCheck checks whether an operation is active +func (ns *NodeStateMachine) opCheck() { + if !ns.opFlag { + panic("Operation has not started") + } +} + +// opStart waits until other operations are finished and starts a new one +func (ns *NodeStateMachine) opStart() bool { + for ns.opFlag { + ns.opWait.Wait() + } + if ns.closed { + return false + } + ns.opFlag = true + return true +} + +// opFinish finishes the current operation by running all pending callbacks. +// Callbacks resulting from a state/field change performed in a previous callback are always +// put at the end of the pending list and therefore processed after all callbacks resulting +// from the previous state/field change. +func (ns *NodeStateMachine) opFinish() { + for len(ns.opPending) != 0 { + list := ns.opPending + ns.lock.Unlock() + for _, cb := range list { + cb() + } + ns.lock.Lock() + ns.opPending = ns.opPending[len(list):] + } + ns.opPending = nil + ns.opFlag = false + ns.opWait.Broadcast() +} + +// Operation calls the given function as an operation callback. This allows the caller +// to start an operation with multiple initial changes. The same rules apply as for +// subscription callbacks. +func (ns *NodeStateMachine) Operation(fn func()) error { + ns.lock.Lock() + started := ns.opStart() + ns.lock.Unlock() + if !started { + return ErrClosed + } + fn() + ns.lock.Lock() + ns.opFinish() + ns.lock.Unlock() + return nil } // offlineCallbacks calls state update callbacks at startup or shutdown func (ns *NodeStateMachine) offlineCallbacks(start bool) { for _, cb := range ns.offlineCallbackList { - for _, sub := range ns.stateSubs { - offState := offlineState & sub.mask - onState := cb.state & sub.mask - if offState != onState { + cb := cb + callback := func() { + for _, sub := range ns.stateSubs { + offState := offlineState & sub.mask + onState := cb.state & sub.mask + if offState == onState { + continue + } if start { - sub.callback(cb.node, Flags{mask: offState, setup: ns.setup}, Flags{mask: onState, setup: ns.setup}) + sub.callback(cb.node.node, Flags{mask: offState, setup: ns.setup}, Flags{mask: onState, setup: ns.setup}) } else { - sub.callback(cb.node, Flags{mask: onState, setup: ns.setup}, Flags{mask: offState, setup: ns.setup}) + sub.callback(cb.node.node, Flags{mask: onState, setup: ns.setup}, Flags{mask: offState, setup: ns.setup}) } } - } - for i, f := range cb.fields { - if f != nil && ns.fields[i].subs != nil { + for i, f := range cb.fields { + if f == nil || ns.fields[i].subs == nil { + continue + } for _, fsub := range ns.fields[i].subs { if start { - fsub(cb.node, Flags{mask: offlineState, setup: ns.setup}, nil, f) + fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, nil, f) } else { - fsub(cb.node, Flags{mask: offlineState, setup: ns.setup}, f, nil) + fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, f, nil) } } } } + ns.opPending = append(ns.opPending, callback) } ns.offlineCallbackList = nil } // AddTimeout adds a node state timeout associated to the given state flag(s). // After the specified time interval, the relevant states will be reset. -func (ns *NodeStateMachine) AddTimeout(n *enode.Node, flags Flags, timeout time.Duration) { +func (ns *NodeStateMachine) AddTimeout(n *enode.Node, flags Flags, timeout time.Duration) error { ns.lock.Lock() defer ns.lock.Unlock() ns.checkStarted() - if ns.stopped { - return + if ns.closed { + return ErrClosed } ns.addTimeout(n, ns.stateMask(flags), timeout) + return nil } // addTimeout adds a node state timeout associated to the given state flag(s). @@ -754,13 +838,15 @@ func (ns *NodeStateMachine) removeTimeouts(node *nodeInfo, mask bitMask) { } } -// GetField retrieves the given field of the given node +// GetField retrieves the given field of the given node. Note that when used in a +// subscription callback the result can be out of sync with the state change represented +// by the callback parameters so extra safety checks might be necessary. func (ns *NodeStateMachine) GetField(n *enode.Node, field Field) interface{} { ns.lock.Lock() defer ns.lock.Unlock() ns.checkStarted() - if ns.stopped { + if ns.closed { return nil } if _, node := ns.updateEnode(n); node != nil { @@ -769,48 +855,80 @@ func (ns *NodeStateMachine) GetField(n *enode.Node, field Field) interface{} { return nil } -// SetField sets the given field of the given node +// SetField sets the given field of the given node and blocks until the operation is finished func (ns *NodeStateMachine) SetField(n *enode.Node, field Field, value interface{}) error { ns.lock.Lock() - ns.checkStarted() - if ns.stopped { - ns.lock.Unlock() - return nil + defer ns.lock.Unlock() + + if !ns.opStart() { + return ErrClosed } - _, node := ns.updateEnode(n) + err := ns.setField(n, field, value) + ns.opFinish() + return err +} + +// SetFieldSub sets the given field of the given node without blocking (should be called +// from a subscription/operation callback). +func (ns *NodeStateMachine) SetFieldSub(n *enode.Node, field Field, value interface{}) error { + ns.lock.Lock() + defer ns.lock.Unlock() + + ns.opCheck() + return ns.setField(n, field, value) +} + +func (ns *NodeStateMachine) setField(n *enode.Node, field Field, value interface{}) error { + ns.checkStarted() + id, node := ns.updateEnode(n) if node == nil { - ns.lock.Unlock() - return nil + if value == nil { + return nil + } + node = ns.newNode(n) + ns.nodes[id] = node } fieldIndex := ns.fieldIndex(field) f := ns.fields[fieldIndex] if value != nil && reflect.TypeOf(value) != f.ftype { log.Error("Invalid field type", "type", reflect.TypeOf(value), "required", f.ftype) - ns.lock.Unlock() - return errors.New("invalid field type") + return ErrInvalidField } oldValue := node.fields[fieldIndex] if value == oldValue { - ns.lock.Unlock() return nil } + if oldValue != nil { + node.fieldCount-- + } + if value != nil { + node.fieldCount++ + } node.fields[fieldIndex] = value - if f.encode != nil { - node.dirty = true + if node.state == 0 && node.fieldCount == 0 { + delete(ns.nodes, id) + if node.db { + ns.deleteNode(id) + } + } else { + if f.encode != nil { + node.dirty = true + } } - state := node.state - ns.lock.Unlock() - if len(f.subs) > 0 { + callback := func() { for _, cb := range f.subs { cb(n, Flags{mask: state, setup: ns.setup}, oldValue, value) } } + ns.opPending = append(ns.opPending, callback) return nil } // ForEach calls the callback for each node having all of the required and none of the -// disabled flags set +// disabled flags set. +// Note that this callback is not an operation callback but ForEach can be called from an +// Operation callback or Operation can also be called from a ForEach callback if necessary. func (ns *NodeStateMachine) ForEach(requireFlags, disableFlags Flags, cb func(n *enode.Node, state Flags)) { ns.lock.Lock() ns.checkStarted() diff --git a/p2p/nodestate/nodestate_test.go b/p2p/nodestate/nodestate_test.go index 8ce08d420..3259741d9 100644 --- a/p2p/nodestate/nodestate_test.go +++ b/p2p/nodestate/nodestate_test.go @@ -130,8 +130,13 @@ func TestSetField(t *testing.T) { // Set field before setting state ns.SetField(testNode(1), fields[0], "hello world") field := ns.GetField(testNode(1), fields[0]) + if field == nil { + t.Fatalf("Field should be set before setting states") + } + ns.SetField(testNode(1), fields[0], nil) + field = ns.GetField(testNode(1), fields[0]) if field != nil { - t.Fatalf("Field shouldn't be set before setting states") + t.Fatalf("Field should be unset") } // Set field after setting state ns.SetState(testNode(1), flags[0], Flags{}, 0) @@ -152,23 +157,6 @@ func TestSetField(t *testing.T) { } } -func TestUnsetField(t *testing.T) { - mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{} - - s, flags, fields := testSetup([]bool{false}, []reflect.Type{reflect.TypeOf("")}) - ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s) - - ns.Start() - - ns.SetState(testNode(1), flags[0], Flags{}, time.Second) - ns.SetField(testNode(1), fields[0], "hello world") - - ns.SetState(testNode(1), Flags{}, flags[0], 0) - if field := ns.GetField(testNode(1), fields[0]); field != nil { - t.Fatalf("Field should be unset") - } -} - func TestSetState(t *testing.T) { mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{} @@ -235,9 +223,8 @@ func uint64FieldEnc(field interface{}) ([]byte, error) { if u, ok := field.(uint64); ok { enc, err := rlp.EncodeToBytes(&u) return enc, err - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") } func uint64FieldDec(enc []byte) (interface{}, error) { @@ -249,9 +236,8 @@ func uint64FieldDec(enc []byte) (interface{}, error) { func stringFieldEnc(field interface{}) ([]byte, error) { if s, ok := field.(string); ok { return []byte(s), nil - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") } func stringFieldDec(enc []byte) (interface{}, error) { @@ -322,6 +308,7 @@ func TestFieldSub(t *testing.T) { ns2.Start() check(s.OfflineFlag(), nil, uint64(100)) ns2.SetState(testNode(1), Flags{}, flags[0], 0) + ns2.SetField(testNode(1), fields[0], nil) check(Flags{}, uint64(100), nil) ns2.Stop() } @@ -370,3 +357,34 @@ func TestDuplicatedFlags(t *testing.T) { clock.Run(2 * time.Second) check(flags[0], Flags{}, true) } + +func TestCallbackOrder(t *testing.T) { + mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{} + + s, flags, _ := testSetup([]bool{false, false, false, false}, nil) + ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s) + + ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) { + if newState.Equals(flags[0]) { + ns.SetStateSub(n, flags[1], Flags{}, 0) + ns.SetStateSub(n, flags[2], Flags{}, 0) + } + }) + ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) { + if newState.Equals(flags[1]) { + ns.SetStateSub(n, flags[3], Flags{}, 0) + } + }) + lastState := Flags{} + ns.SubscribeState(MergeFlags(flags[1], flags[2], flags[3]), func(n *enode.Node, oldState, newState Flags) { + if !oldState.Equals(lastState) { + t.Fatalf("Wrong callback order") + } + lastState = newState + }) + + ns.Start() + defer ns.Stop() + + ns.SetState(testNode(1), flags[0], Flags{}, 0) +} diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 1e5fbdae7..cba6e4864 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -454,9 +454,8 @@ func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID { if len(excludeIDs) > 0 { // Return the difference of nodeIDs and excludeIDs return filterIDs(nodeIDs, excludeIDs) - } else { - return nodeIDs } + return nodeIDs } // GetNodes returns the existing nodes. @@ -472,9 +471,8 @@ func (net *Network) getNodes(excludeIDs []enode.ID) []*Node { if len(excludeIDs) > 0 { nodeIDs := net.getNodeIDs(excludeIDs) return net.getNodesByID(nodeIDs) - } else { - return net.Nodes } + return net.Nodes } // GetNodesByID returns existing nodes with the given enode.IDs. @@ -1098,7 +1096,6 @@ func (net *Network) executeNodeEvent(e *Event) error { func (net *Network) executeConnEvent(e *Event) error { if e.Conn.Up { return net.Connect(e.Conn.One, e.Conn.Other) - } else { - return net.Disconnect(e.Conn.One, e.Conn.Other) } + return net.Disconnect(e.Conn.One, e.Conn.Other) } diff --git a/params/config.go b/params/config.go index 5d63312bd..d73c07248 100644 --- a/params/config.go +++ b/params/config.go @@ -103,6 +103,7 @@ var ( AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} TestChainConfig = &ChainConfig{big.NewInt(2), nil, new(CryptoreConfig), nil} + DevChainConfig = &ChainConfig{big.NewInt(1337), nil, new(CryptoreConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 77f612648..0ccc1249a 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -37,9 +37,8 @@ func (e *testEncoder) EncodeRLP(w io.Writer) error { } if e.err != nil { return e.err - } else { - w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) } + w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) return nil } diff --git a/rpc/client.go b/rpc/client.go index 3ad8f05f1..314c22b5e 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -404,9 +404,8 @@ func (c *Client) Notify(ctx context.Context, method string, args ...interface{}) if c.isHTTP { return c.sendHTTP(ctx, op, msg) - } else { - return c.send(ctx, op, msg) } + return c.send(ctx, op, msg) } // XcbSubscribe registers a subscripion under the "xcb" namespace. diff --git a/rpc/client_test.go b/rpc/client_test.go index 7674b65bf..90d29d67a 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -67,6 +67,33 @@ func TestClientResponseType(t *testing.T) { } } +// This test checks that server-returned errors with code and data come out of Client.Call. +func TestClientErrorData(t *testing.T) { + server := newTestServer() + defer server.Stop() + client := DialInProc(server) + defer client.Close() + + var resp interface{} + err := client.Call(&resp, "test_returnError") + if err == nil { + t.Fatal("expected error") + } + + // Check code. + if e, ok := err.(Error); !ok { + t.Fatalf("client did not return rpc.Error, got %#v", e) + } else if e.ErrorCode() != (testError{}.ErrorCode()) { + t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), testError{}.ErrorCode()) + } + // Check data. + if e, ok := err.(DataError); !ok { + t.Fatalf("client did not return rpc.DataError, got %#v", e) + } else if e.ErrorData() != (testError{}.ErrorData()) { + t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData()) + } +} + func TestClientBatchRequest(t *testing.T) { server := newTestServer() defer server.Stop() diff --git a/rpc/errors.go b/rpc/errors.go index e5cf0aa04..023ebd193 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -18,6 +18,15 @@ package rpc import "fmt" +var ( + _ Error = new(methodNotFoundError) + _ Error = new(subscriptionNotFoundError) + _ Error = new(parseError) + _ Error = new(invalidRequestError) + _ Error = new(invalidMessageError) + _ Error = new(invalidParamsError) +) + const defaultErrorCode = -32000 type methodNotFoundError struct{ method string } diff --git a/rpc/handler.go b/rpc/handler.go index fc427f802..427a5e235 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -296,10 +296,16 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess return nil case msg.isCall(): resp := h.handleCall(ctx, msg) + var ctx []interface{} + ctx = append(ctx, "reqid", idForLog{msg.ID}, "t", time.Since(start)) if resp.Error != nil { - h.log.Warn("Served "+msg.Method, "reqid", idForLog{msg.ID}, "t", time.Since(start), "err", resp.Error.Message) + ctx = append(ctx, "err", resp.Error.Message) + if resp.Error.Data != nil { + ctx = append(ctx, "errdata", resp.Error.Data) + } + h.log.Warn("Served "+msg.Method, ctx...) } else { - h.log.Debug("Served "+msg.Method, "reqid", idForLog{msg.ID}, "t", time.Since(start)) + h.log.Debug("Served "+msg.Method, ctx...) } return resp case msg.hasValidID(): diff --git a/rpc/json.go b/rpc/json.go index b9711a4b1..7270113fa 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -115,6 +115,10 @@ func errorMessage(err error) *jsonrpcMessage { if ok { msg.Error.Code = ec.ErrorCode() } + de, ok := err.(DataError) + if ok { + msg.Error.Data = de.ErrorData() + } return msg } @@ -135,6 +139,10 @@ func (err *jsonError) ErrorCode() int { return err.Code } +func (err *jsonError) ErrorData() interface{} { + return err.Data +} + // Conn is a subset of the methods of net.Conn which are sufficient for ServerCodec. type Conn interface { io.ReadWriteCloser diff --git a/rpc/server_test.go b/rpc/server_test.go index deff64fe6..5a09630ba 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -45,7 +45,7 @@ func TestServerRegisterName(t *testing.T) { t.Fatalf("Expected service calc to be registered") } - wantCallbacks := 8 + wantCallbacks := 9 if len(svc.callbacks) != wantCallbacks { t.Errorf("Expected %d callbacks for service 'service', got %d", wantCallbacks, len(svc.callbacks)) } diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go index 2eaf95501..54d821bef 100644 --- a/rpc/testservice_test.go +++ b/rpc/testservice_test.go @@ -63,6 +63,12 @@ type echoResult struct { Args *echoArgs } +type testError struct{} + +func (testError) Error() string { return "testError" } +func (testError) ErrorCode() int { return 444 } +func (testError) ErrorData() interface{} { return "testError data" } + func (s *testService) NoArgsRets() {} func (s *testService) Echo(str string, i int, args *echoArgs) echoResult { @@ -99,6 +105,10 @@ func (s *testService) InvalidRets3() (string, string, error) { return "", "", nil } +func (s *testService) ReturnError() error { + return testError{} +} + func (s *testService) CallMeBack(ctx context.Context, method string, args []interface{}) (interface{}, error) { c, ok := ClientFromContext(ctx) if !ok { diff --git a/rpc/types.go b/rpc/types.go index 335e4c8fa..c2e4ca9e7 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -42,6 +42,12 @@ type Error interface { ErrorCode() int // returns the code } +// A DataError contains some data in addition to the error message. +type DataError interface { + Error() string // returns the message + ErrorData() interface{} // returns the error data +} + // ServerCodec implements reading, parsing and writing RPC messages for the server side of // a RPC session. Implementations must be go-routine safe since the codec can be called in // multiple go-routines concurrently. diff --git a/rpc/websocket.go b/rpc/websocket.go index e8c806954..44def4181 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -25,6 +25,7 @@ import ( "os" "strings" "sync" + "time" "github.com/core-coin/go-core/log" mapset "github.com/deckarep/golang-set" @@ -32,8 +33,10 @@ import ( ) const ( - wsReadBuffer = 1024 - wsWriteBuffer = 1024 + wsReadBuffer = 1024 + wsWriteBuffer = 1024 + wsPingInterval = 60 * time.Second + wsPingWriteTimeout = 5 * time.Second ) var wsBufferPool = new(sync.Pool) @@ -227,7 +230,64 @@ func wsClientHeaders(endpoint, origin string) (string, http.Header, error) { return endpointURL.String(), header, nil } +type websocketCodec struct { + *jsonCodec + conn *websocket.Conn + + wg sync.WaitGroup + pingReset chan struct{} +} + func newWebsocketCodec(conn *websocket.Conn) ServerCodec { conn.SetReadLimit(maxRequestContentLength) - return NewFuncCodec(conn, conn.WriteJSON, conn.ReadJSON) + wc := &websocketCodec{ + jsonCodec: NewFuncCodec(conn, conn.WriteJSON, conn.ReadJSON).(*jsonCodec), + conn: conn, + pingReset: make(chan struct{}, 1), + } + wc.wg.Add(1) + go wc.pingLoop() + return wc +} + +func (wc *websocketCodec) close() { + wc.jsonCodec.close() + wc.wg.Wait() +} + +func (wc *websocketCodec) writeJSON(ctx context.Context, v interface{}) error { + err := wc.jsonCodec.writeJSON(ctx, v) + if err == nil { + // Notify pingLoop to delay the next idle ping. + select { + case wc.pingReset <- struct{}{}: + default: + } + } + return err +} + +// pingLoop sends periodic ping frames when the connection is idle. +func (wc *websocketCodec) pingLoop() { + var timer = time.NewTimer(wsPingInterval) + defer wc.wg.Done() + defer timer.Stop() + + for { + select { + case <-wc.closed(): + return + case <-wc.pingReset: + if !timer.Stop() { + <-timer.C + } + timer.Reset(wsPingInterval) + case <-timer.C: + wc.jsonCodec.encMu.Lock() + wc.conn.SetWriteDeadline(time.Now().Add(wsPingWriteTimeout)) + wc.conn.WriteMessage(websocket.PingMessage, nil) + wc.jsonCodec.encMu.Unlock() + timer.Reset(wsPingInterval) + } + } } diff --git a/trie/committer.go b/trie/committer.go index b2d25c470..37d1f7fc3 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -274,6 +274,5 @@ func estimateSize(n node) int { return 1 + len(n) default: panic(fmt.Sprintf("node type %T", n)) - } } diff --git a/xcb/api_tracer.go b/xcb/api_tracer.go index 893041b25..e6736361c 100644 --- a/xcb/api_tracer.go +++ b/xcb/api_tracer.go @@ -801,10 +801,15 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v // Depending on the tracer type, format and return the output switch tracer := tracer.(type) { case *vm.StructLogger: + // If the result contains a revert reason, return it. + returnVal := fmt.Sprintf("%x", result.Return()) + if len(result.Revert()) > 0 { + returnVal = fmt.Sprintf("%x", result.Revert()) + } return &xcbapi.ExecutionResult{ Energy: result.UsedEnergy, Failed: result.Failed(), - ReturnValue: fmt.Sprintf("%x", result.Return()), + ReturnValue: returnVal, StructLogs: xcbapi.FormatLogs(tracer.StructLogs()), }, nil