diff --git a/integration/common/utils.go b/integration/common/utils.go index 24df954384..b1d59ca624 100644 --- a/integration/common/utils.go +++ b/integration/common/utils.go @@ -9,8 +9,9 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/obscuronet/go-obscuro/go/common/errutil" "github.com/obscuronet/go-obscuro/go/common/retry" - "github.com/obscuronet/go-obscuro/go/obsclient" "github.com/obscuronet/go-obscuro/go/wallet" @@ -60,6 +61,32 @@ func AwaitReceipt(ctx context.Context, client *obsclient.AuthObsClient, txHash g return nil } +func AwaitReceiptEth(ctx context.Context, client *ethclient.Client, txHash gethcommon.Hash, timeout time.Duration) (*types.Receipt, error) { + var receipt *types.Receipt + var err error + startTime := time.Now() + + fmt.Println("Fetching receipt for tx: ", txHash.Hex()) + err = retry.Do(func() error { + receipt, err = client.TransactionReceipt(ctx, txHash) + if err != nil && !errors.Is(err, errutil.ErrNotFound) { + // we only retry for a nil "not found" response. This is a different error, so we bail out of the retry loop + return retry.FailFast(err) + } + fmt.Println("No tx receipt after: ", time.Since(startTime)) + return err + }, retry.NewTimeoutStrategy(timeout, _awaitReceiptPollingInterval)) + if err != nil { + return nil, fmt.Errorf("could not retrieve receipt for transaction %s - %w", txHash.Hex(), err) + } + + if receipt.Status == types.ReceiptStatusFailed { + return nil, fmt.Errorf("receipt had status failed for transaction %s", txHash.Hex()) + } + + return receipt, nil +} + // PrefundWallets sends an amount `alloc` from the faucet wallet to each listed wallet. // The transactions are sent with sequential nonces, starting with `startingNonce`. func PrefundWallets(ctx context.Context, faucetWallet wallet.Wallet, faucetClient *obsclient.AuthObsClient, startingNonce uint64, wallets []wallet.Wallet, alloc *big.Int, timeout time.Duration) { diff --git a/integration/obscurogateway/errors_contract.go b/integration/obscurogateway/errors_contract.go new file mode 100644 index 0000000000..105640d9ae --- /dev/null +++ b/integration/obscurogateway/errors_contract.go @@ -0,0 +1,127 @@ +package faucet + +import ( + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +func init() { //nolint: gochecknoinits + contractABI, err := abi.JSON(strings.NewReader(errorsContractABIString)) + if err != nil { + panic(err) + } + errorsContractABI = contractABI +} + +var ( + errorsContractABI abi.ABI + errorsContractBytecode = "0x60806040526040518060400160405280600381526020017f666f6f0000000000000000000000000000000000000000000000000000000000815250600090816200004a9190620002d9565b503480156200005857600080fd5b50620003c0565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620000e157607f821691505b602082108103620000f757620000f662000099565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620001617fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000122565b6200016d868362000122565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620001ba620001b4620001ae8462000185565b6200018f565b62000185565b9050919050565b6000819050919050565b620001d68362000199565b620001ee620001e582620001c1565b8484546200012f565b825550505050565b600090565b62000205620001f6565b62000212818484620001cb565b505050565b5b818110156200023a576200022e600082620001fb565b60018101905062000218565b5050565b601f82111562000289576200025381620000fd565b6200025e8462000112565b810160208510156200026e578190505b620002866200027d8562000112565b83018262000217565b50505b505050565b600082821c905092915050565b6000620002ae600019846008026200028e565b1980831691505092915050565b6000620002c983836200029b565b9150826002028217905092915050565b620002e4826200005f565b67ffffffffffffffff8111156200030057620002ff6200006a565b5b6200030c8254620000c8565b620003198282856200023e565b600060209050601f8311600181146200035157600084156200033c578287015190505b620003488582620002bb565b865550620003b8565b601f1984166200036186620000fd565b60005b828110156200038b5784890151825560018201915060208501945060208101905062000364565b86831015620003ab5784890151620003a7601f8916826200029b565b8355505b6001600288020188555050505b505050505050565b610a0280620003d06000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063812002241161005b57806381200224146100c6578063852055c1146100e257806398849947146100ec578063e072b5f6146100f65761007d565b80633943380c146100825780634ed3885e146100a05780637e479d4a146100bc575b600080fd5b61008a610114565b60405161009791906103bd565b60405180910390f35b6100ba60048036038101906100b59190610528565b6101a2565b005b6100c46101b5565b005b6100e060048036038101906100db9190610528565b6101f8565b005b6100ea61024f565b005b6100f4610260565b005b6100fe61029b565b60405161010b91906103bd565b60405180910390f35b60008054610121906105a0565b80601f016020809104026020016040519081016040528092919081815260200182805461014d906105a0565b801561019a5780601f1061016f5761010080835404028352916020019161019a565b820191906000526020600020905b81548152906001019060200180831161017d57829003601f168201915b505050505081565b80600090816101b19190610787565b5050565b60006101f6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101ed906108a5565b60405180910390fd5b565b600081510361023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023390610911565b60405180910390fd5b806000908161024b9190610787565b5050565b600061025e5761025d610931565b5b565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610292906109ac565b60405180910390fd5b6060600080546102aa906105a0565b80601f01602080910402602001604051908101604052809291908181526020018280546102d6906105a0565b80156103235780601f106102f857610100808354040283529160200191610323565b820191906000526020600020905b81548152906001019060200180831161030657829003601f168201915b5050505050905090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561036757808201518184015260208101905061034c565b60008484015250505050565b6000601f19601f8301169050919050565b600061038f8261032d565b6103998185610338565b93506103a9818560208601610349565b6103b281610373565b840191505092915050565b600060208201905081810360008301526103d78184610384565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61043582610373565b810181811067ffffffffffffffff82111715610454576104536103fd565b5b80604052505050565b60006104676103df565b9050610473828261042c565b919050565b600067ffffffffffffffff821115610493576104926103fd565b5b61049c82610373565b9050602081019050919050565b82818337600083830152505050565b60006104cb6104c684610478565b61045d565b9050828152602081018484840111156104e7576104e66103f8565b5b6104f28482856104a9565b509392505050565b600082601f83011261050f5761050e6103f3565b5b813561051f8482602086016104b8565b91505092915050565b60006020828403121561053e5761053d6103e9565b5b600082013567ffffffffffffffff81111561055c5761055b6103ee565b5b610568848285016104fa565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806105b857607f821691505b6020821081036105cb576105ca610571565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026106337fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826105f6565b61063d86836105f6565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600061068461067f61067a84610655565b61065f565b610655565b9050919050565b6000819050919050565b61069e83610669565b6106b26106aa8261068b565b848454610603565b825550505050565b600090565b6106c76106ba565b6106d2818484610695565b505050565b5b818110156106f6576106eb6000826106bf565b6001810190506106d8565b5050565b601f82111561073b5761070c816105d1565b610715846105e6565b81016020851015610724578190505b610738610730856105e6565b8301826106d7565b50505b505050565b600082821c905092915050565b600061075e60001984600802610740565b1980831691505092915050565b6000610777838361074d565b9150826002028217905092915050565b6107908261032d565b67ffffffffffffffff8111156107a9576107a86103fd565b5b6107b382546105a0565b6107be8282856106fa565b600060209050601f8311600181146107f157600084156107df578287015190505b6107e9858261076b565b865550610851565b601f1984166107ff866105d1565b60005b8281101561082757848901518255600182019150602085019450602081019050610802565b868310156108445784890151610840601f89168261074d565b8355505b6001600288020188555050505b505050505050565b7f466f726365642072657175697265000000000000000000000000000000000000600082015250565b600061088f600e83610338565b915061089a82610859565b602082019050919050565b600060208201905081810360008301526108be81610882565b9050919050565b7f4e6577206b65792063616e6e6f7420626520656d707479000000000000000000600082015250565b60006108fb601783610338565b9150610906826108c5565b602082019050919050565b6000602082019050818103600083015261092a816108ee565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b7f466f726365642072657665727400000000000000000000000000000000000000600082015250565b6000610996600d83610338565b91506109a182610960565b602082019050919050565b600060208201905081810360008301526109c581610989565b905091905056fea264697066735822122096d8546ec9908bc71f158582f76aba34c9d82114e3afbfc84a3722ce20ae975d64736f6c63430008120033" + errorsContractABIString = `[ + { + "inputs": [], + "name": "force_assert", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "force_require", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "force_revert", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "get_key", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "key", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_key", + "type": "string" + } + ], + "name": "set", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "newKey", + "type": "string" + } + ], + "name": "set_key", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]` +) + +// // SPDX-License-Identifier: GPL-3.0 +//pragma solidity ^0.8.0; +// +//contract Error { +// string public key = "foo"; +// +// function set(string memory _key) public { +// key = _key; +// } +// +// function force_require() public view { +// require(false, "Forced require"); +// } +// +// function force_revert() public view { +// revert("Forced revert"); +// } +// +// function force_assert() public view{ +// assert(false); +// } +// +// function set_key(string memory newKey) public { +// require(bytes(newKey).length != 0, "New key cannot be empty"); +// key = newKey; +// } +// +// function get_key() public view returns (string memory) { +// return key; +// } +//} diff --git a/integration/obscurogateway/obscurogateway_test.go b/integration/obscurogateway/obscurogateway_test.go index 8cf90c42e3..cea8d5dcd5 100644 --- a/integration/obscurogateway/obscurogateway_test.go +++ b/integration/obscurogateway/obscurogateway_test.go @@ -12,6 +12,7 @@ import ( wecommon "github.com/obscuronet/go-obscuro/tools/walletextension/common" + "github.com/ethereum/go-ethereum" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" @@ -21,6 +22,7 @@ import ( "github.com/obscuronet/go-obscuro/go/enclave/genesis" "github.com/obscuronet/go-obscuro/go/wallet" "github.com/obscuronet/go-obscuro/integration" + integrationCommon "github.com/obscuronet/go-obscuro/integration/common" "github.com/obscuronet/go-obscuro/integration/common/testlog" "github.com/obscuronet/go-obscuro/integration/datagenerator" "github.com/obscuronet/go-obscuro/integration/ethereummock" @@ -83,8 +85,9 @@ func TestObscuroGateway(t *testing.T) { // run the tests against the exis for name, test := range map[string]func(*testing.T, string, string){ - "testAreTxsMinted": testAreTxsMinted, - "testErrorHandling": testErrorHandling, + //"testAreTxsMinted": testAreTxsMinted, this breaks the other tests bc, enable once concurency issues are fixed + "testErrorHandling": testErrorHandling, + "testErrorsRevertedArePassed": testErrorsRevertedArePassed, } { t.Run(name, func(t *testing.T) { test(t, httpURL, wsURL) @@ -96,7 +99,7 @@ func TestObscuroGateway(t *testing.T) { assert.NoError(t, err) } -func testAreTxsMinted(t *testing.T, httpURL, wsURL string) { +func testAreTxsMinted(t *testing.T, httpURL, wsURL string) { //nolint: unused // set up the ogClient ogClient := lib.NewObscuroGatewayLibrary(httpURL, wsURL) @@ -167,7 +170,83 @@ func testErrorHandling(t *testing.T, httpURL, wsURL string) { } } -func transferRandomAddr(t *testing.T, client *ethclient.Client, w wallet.Wallet) common.TxHash { +func testErrorsRevertedArePassed(t *testing.T, httpURL, wsURL string) { + // set up the ogClient + ogClient := lib.NewObscuroGatewayLibrary(httpURL, wsURL) + + // join + register against the og + err := ogClient.Join() + require.NoError(t, err) + + w := wallet.NewInMemoryWalletFromConfig(genesis.TestnetPrefundedPK, integration.ObscuroChainID, testlog.Logger()) + err = ogClient.RegisterAccount(w.PrivateKey(), w.Address()) + require.NoError(t, err) + + // use a standard eth client via the og + ethStdClient, err := ethclient.Dial(ogClient.HTTP()) + require.NoError(t, err) + + // check the balance + balance, err := ethStdClient.BalanceAt(context.Background(), w.Address(), nil) + require.NoError(t, err) + require.True(t, big.NewInt(0).Cmp(balance) == -1) + + // deploy errors contract + deployTx := &types.LegacyTx{ + Nonce: w.GetNonceAndIncrement(), + Gas: uint64(1_000_000), + GasPrice: gethcommon.Big1, + Data: gethcommon.FromHex(errorsContractBytecode), + } + + signedTx, err := w.SignTransaction(deployTx) + require.NoError(t, err) + + err = ethStdClient.SendTransaction(context.Background(), signedTx) + require.NoError(t, err) + + receipt, err := integrationCommon.AwaitReceiptEth(context.Background(), ethStdClient, signedTx.Hash(), time.Minute) + require.NoError(t, err) + + pack, _ := errorsContractABI.Pack("force_require") + _, err = ethStdClient.CallContract(context.Background(), ethereum.CallMsg{ + From: w.Address(), + To: &receipt.ContractAddress, + Data: pack, + }, nil) + require.Error(t, err) + require.Equal(t, err.Error(), "execution reverted: Forced require") + + // convert error to WE error + errBytes, err := json.Marshal(err) + require.NoError(t, err) + weError := wecommon.JSONError{} + err = json.Unmarshal(errBytes, &weError) + require.NoError(t, err) + require.Equal(t, weError.Message, "execution reverted: Forced require") + require.Equal(t, weError.Data, "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e466f726365642072657175697265000000000000000000000000000000000000") + require.Equal(t, weError.Code, 3) + + pack, _ = errorsContractABI.Pack("force_revert") + _, err = ethStdClient.CallContract(context.Background(), ethereum.CallMsg{ + From: w.Address(), + To: &receipt.ContractAddress, + Data: pack, + }, nil) + require.Error(t, err) + require.Equal(t, err.Error(), "execution reverted: Forced revert") + + pack, _ = errorsContractABI.Pack("force_assert") + _, err = ethStdClient.CallContract(context.Background(), ethereum.CallMsg{ + From: w.Address(), + To: &receipt.ContractAddress, + Data: pack, + }, nil) + require.Error(t, err) + require.Equal(t, err.Error(), "execution reverted") +} + +func transferRandomAddr(t *testing.T, client *ethclient.Client, w wallet.Wallet) common.TxHash { //nolint: unused ctx := context.Background() toAddr := datagenerator.RandomAddress() nonce, err := client.NonceAt(ctx, w.Address(), nil) @@ -191,32 +270,9 @@ func transferRandomAddr(t *testing.T, client *ethclient.Client, w wallet.Wallet) err = client.SendTransaction(ctx, signedTx) assert.Nil(t, err) - fmt.Printf("Created Tx: %s \n", signedTx.Hash().Hex()) - fmt.Printf("Checking for tx receipt for %s \n", signedTx.Hash()) - var receipt *types.Receipt - for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Second) { - receipt, err = client.TransactionReceipt(ctx, signedTx.Hash()) - if err == nil { - break - } - // - // Currently when a receipt is not available the obscuro node is returning nil instead of err ethereum.NotFound - // once that's fixed this commented block should be removed - //if !errors.Is(err, ethereum.NotFound) { - // t.Fatal(err) - //} - if receipt != nil && receipt.Status == 1 { - break - } - fmt.Printf("no tx receipt after %s - %s\n", time.Since(start), err) - } + _, err = integrationCommon.AwaitReceiptEth(context.Background(), client, signedTx.Hash(), time.Minute) + assert.NoError(t, err) - if receipt == nil { - t.Fatalf("Did not mine the transaction after %s seconds - receipt: %+v", 30*time.Second, receipt) - } - if receipt.Status == 0 { - t.Fatalf("Tx Failed") - } fmt.Println("Successfully minted the transaction - ", signedTx.Hash()) return signedTx.Hash() } diff --git a/tools/walletextension/api/routes.go b/tools/walletextension/api/routes.go index 4832ad279f..a9514d92e1 100644 --- a/tools/walletextension/api/routes.go +++ b/tools/walletextension/api/routes.go @@ -178,8 +178,8 @@ func ethRequestHandler(walletExt *walletextension.WalletExtension, conn userconn handleEthError(request, conn, walletExt.Logger(), err) return } - walletExt.Logger().Info(fmt.Sprintf("Forwarding %s response from Obscuro node: %s", request.Method, rpcResponse)) + walletExt.Logger().Info(fmt.Sprintf("Forwarding %s response from Obscuro node: %s", request.Method, rpcResponse)) if err = conn.WriteResponse(rpcResponse); err != nil { walletExt.Logger().Error("error writing success response", log.ErrKey, err) } diff --git a/tools/walletextension/api/utils.go b/tools/walletextension/api/utils.go index 3f9bb468ae..2dd7e06f69 100644 --- a/tools/walletextension/api/utils.go +++ b/tools/walletextension/api/utils.go @@ -5,10 +5,9 @@ import ( "fmt" "strings" - "github.com/obscuronet/go-obscuro/go/common/log" - gethlog "github.com/ethereum/go-ethereum/log" - + "github.com/obscuronet/go-obscuro/go/common/errutil" + "github.com/obscuronet/go-obscuro/go/common/log" "github.com/obscuronet/go-obscuro/tools/walletextension/accountmanager" "github.com/obscuronet/go-obscuro/tools/walletextension/common" "github.com/obscuronet/go-obscuro/tools/walletextension/userconn" @@ -109,18 +108,27 @@ func handleEthError(req *accountmanager.RPCRequest, conn userconn.UserConn, logg Result: nil, } + if evmError, ok := err.(errutil.EVMSerialisableError); ok { //nolint: errorlint + jsonRPRCError.Error.Data = evmError.Reason + jsonRPRCError.Error.Code = evmError.ErrorCode() + } + errBytes, err := json.Marshal(jsonRPRCError) if err != nil { logger.Error("unable to marshal error - %w", log.ErrKey, err) return } + logger.Info(fmt.Sprintf("Forwarding %s error response from Obscuro node: %s", method, errBytes)) + if err = conn.WriteResponse(errBytes); err != nil { logger.Error("unable to write response back - %w", log.ErrKey, err) } } func handleError(conn userconn.UserConn, logger gethlog.Logger, err error) { + logger.Error("error processing request - Forwarding response to user", log.ErrKey, err) + if err = conn.WriteResponse([]byte(err.Error())); err != nil { logger.Error("unable to write response back", log.ErrKey, err) } diff --git a/tools/walletextension/common/json.go b/tools/walletextension/common/json.go index caaf38330d..2079aef3bc 100644 --- a/tools/walletextension/common/json.go +++ b/tools/walletextension/common/json.go @@ -1,6 +1,8 @@ package common -import "encoding/json" +import ( + "encoding/json" +) // JSONRPCMessage value of this type can a JSON-RPC request, notification, successful response or // error response. Which one it is depends on the fields.