diff --git a/geth/node.go b/geth/node.go index dbd23a893..238eab0c2 100644 --- a/geth/node.go +++ b/geth/node.go @@ -41,6 +41,7 @@ const ( MaxPeers = 25 MaxLightPeers = 20 MaxPendingPeers = 0 + DefaultGas = 180000 ProcessFileDescriptorLimit = uint64(2048) DatabaseCacheSize = 128 // Megabytes of memory allocated to internal caching (min 16MB / database forced) diff --git a/geth/testdata/test.sol b/geth/testdata/test.sol new file mode 100644 index 000000000..49f83a8c6 --- /dev/null +++ b/geth/testdata/test.sol @@ -0,0 +1,7 @@ +pragma solidity ^0.4.9; + +contract Test { + function double(int a) constant returns(int) { + return 2*a; + } +} \ No newline at end of file diff --git a/geth/txqueue.go b/geth/txqueue.go index f96ac76d2..164cfba38 100644 --- a/geth/txqueue.go +++ b/geth/txqueue.go @@ -3,8 +3,6 @@ package geth import ( "context" "encoding/json" - "math/big" - "strconv" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" @@ -231,42 +229,120 @@ func sendTxArgsFromRPCCall(req RPCCall) status.SendTxArgs { return status.SendTxArgs{} } - params, ok := req.Params[0].(map[string]interface{}) + return status.SendTxArgs{ + From: req.parseFromAddress(), + To: req.parseToAddress(), + Value: req.parseValue(), + Data: req.parseData(), + Gas: req.parseGas(), + GasPrice: req.parseGasPrice(), + } +} + +func (r RPCCall) parseFromAddress() common.Address { + params, ok := r.Params[0].(map[string]interface{}) if !ok { - return status.SendTxArgs{} + return common.HexToAddress("0x") } from, ok := params["from"].(string) if !ok { - from = "" + from = "0x" + } + + return common.HexToAddress(from) +} + +func (r RPCCall) parseToAddress() *common.Address { + params, ok := r.Params[0].(map[string]interface{}) + if !ok { + return nil } to, ok := params["to"].(string) if !ok { - to = "" + return nil } - param, ok := params["value"].(string) + address := common.HexToAddress(to) + return &address +} + +func (r RPCCall) parseData() hexutil.Bytes { + params, ok := r.Params[0].(map[string]interface{}) if !ok { - param = "0x0" - } - value, err := strconv.ParseInt(param, 0, 64) - if err != nil { - return status.SendTxArgs{} + return hexutil.Bytes("0x") } data, ok := params["data"].(string) if !ok { - data = "" + data = "0x" } - toAddress := common.HexToAddress(to) - return status.SendTxArgs{ - From: common.HexToAddress(from), - To: &toAddress, - Value: (*hexutil.Big)(big.NewInt(value)), - Data: hexutil.Bytes(data), + byteCode, err := hexutil.Decode(data) + if err != nil { + byteCode = hexutil.Bytes(data) } + + return byteCode +} + +func (r RPCCall) parseValue() *hexutil.Big { + params, ok := r.Params[0].(map[string]interface{}) + if !ok { + return nil + //return (*hexutil.Big)(big.NewInt("0x0")) + } + + inputValue, ok := params["value"].(string) + if !ok { + return nil + } + + parsedValue, err := hexutil.DecodeBig(inputValue) + if err != nil { + return nil + } + + return (*hexutil.Big)(parsedValue) +} + +func (r RPCCall) parseGas() *hexutil.Big { + params, ok := r.Params[0].(map[string]interface{}) + if !ok { + return nil + } + + inputValue, ok := params["gas"].(string) + if !ok { + return nil + } + + parsedValue, err := hexutil.DecodeBig(inputValue) + if err != nil { + return nil + } + + return (*hexutil.Big)(parsedValue) +} + +func (r RPCCall) parseGasPrice() *hexutil.Big { + params, ok := r.Params[0].(map[string]interface{}) + if !ok { + return nil + } + + inputValue, ok := params["gasPrice"].(string) + if !ok { + return nil + } + + parsedValue, err := hexutil.DecodeBig(inputValue) + if err != nil { + return nil + } + + return (*hexutil.Big)(parsedValue) } func parseJSONArray(items string) ([]string, error) { diff --git a/geth/txqueue_test.go b/geth/txqueue_test.go index e5058754b..26d5753bd 100644 --- a/geth/txqueue_test.go +++ b/geth/txqueue_test.go @@ -14,6 +14,113 @@ import ( "github.com/status-im/status-go/geth" ) +func TestQueuedContracts(t *testing.T) { + err := geth.PrepareTestNode() + if err != nil { + t.Error(err) + return + } + + // obtain reference to status backend + lightEthereum, err := geth.NodeManagerInstance().LightEthereumService() + if err != nil { + t.Errorf("Test failed: LES service is not running: %v", err) + return + } + backend := lightEthereum.StatusBackend + + // create an account + sampleAddress, _, _, err := geth.CreateAccount(newAccountPassword) + if err != nil { + t.Errorf("could not create account: %v", err) + return + } + + geth.Logout() + + // make sure you panic if transaction complete doesn't return + completeQueuedTransaction := make(chan struct{}, 1) + geth.PanicAfter(60*time.Second, completeQueuedTransaction, "TestQueuedContracts") + + // replace transaction notification handler + var txHash = common.Hash{} + geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { + var envelope geth.SignalEnvelope + if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil { + t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent) + return + } + if envelope.Type == geth.EventTransactionQueued { + event := envelope.Event.(map[string]interface{}) + t.Logf("transaction queued (will be completed in 5 secs): {id: %s}\n", event["id"].(string)) + time.Sleep(5 * time.Second) + + // the first call will fail (we are not logged in, but trying to complete tx) + if txHash, err = geth.CompleteTransaction(event["id"].(string), testAddressPassword); err != status.ErrInvalidCompleteTxSender { + t.Errorf("expected error on queued transation[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err) + return + } + + // the second call will also fail (we are logged in as different user) + if err := geth.SelectAccount(sampleAddress, newAccountPassword); err != nil { + t.Errorf("cannot select account: %v", sampleAddress) + return + } + if txHash, err = geth.CompleteTransaction(event["id"].(string), testAddressPassword); err != status.ErrInvalidCompleteTxSender { + t.Errorf("expected error on queued transation[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err) + return + } + + // the third call will work as expected (as we are logged in with correct credentials) + if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil { + t.Errorf("cannot select account: %v", testAddress) + return + } + if txHash, err = geth.CompleteTransaction(event["id"].(string), testAddressPassword); err != nil { + t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err) + return + } + + t.Logf("contract transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex()) + completeQueuedTransaction <- struct{}{} // so that timeout is aborted + } + }) + + // this call blocks, up until Complete Transaction is called + byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`) + if err != nil { + t.Error(err) + return + } + txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{ + From: geth.FromAddress(testAddress), + To: nil, // marker, contract creation is expected + //Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), common.Ether)), + Gas: (*hexutil.Big)(big.NewInt(geth.DefaultGas)), + Data: byteCode, + }) + if err != nil { + t.Errorf("Test failed: cannot send transaction: %v", err) + } + + if !reflect.DeepEqual(txHash, txHashCheck) { + t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s", txHashCheck, txHash) + return + } + + time.Sleep(10 * time.Second) + + if reflect.DeepEqual(txHashCheck, common.Hash{}) { + t.Error("Test failed: transaction was never queued or completed") + return + } + + if backend.TransactionQueue().Count() != 0 { + t.Error("tx queue must be empty at this point") + return + } +} + func TestQueuedTransactions(t *testing.T) { err := geth.PrepareTestNode() if err != nil { diff --git a/jail/jail_test.go b/jail/jail_test.go index aaf0e95ee..b70a639ba 100644 --- a/jail/jail_test.go +++ b/jail/jail_test.go @@ -3,6 +3,7 @@ package jail_test import ( "encoding/json" "reflect" + "strconv" "strings" "testing" "time" @@ -562,3 +563,94 @@ func TestLocalStorageSet(t *testing.T) { return } } + +func TestContractDeployment(t *testing.T) { + err := geth.PrepareTestNode() + if err != nil { + t.Error(err) + return + } + + jailInstance := jail.Init("") + jailInstance.Parse(CHAT_ID_CALL, "") + + // obtain VM for a given chat (to send custom JS to jailed version of Send()) + vm, err := jailInstance.GetVM(CHAT_ID_CALL) + if err != nil { + t.Errorf("cannot get VM: %v", err) + return + } + + // make sure you panic if transaction complete doesn't return + completeQueuedTransaction := make(chan struct{}, 1) + geth.PanicAfter(30*time.Second, completeQueuedTransaction, "TestContractDeployment") + + // replace transaction notification handler + var txHash common.Hash + geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { + var envelope geth.SignalEnvelope + if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil { + t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent) + return + } + if envelope.Type == geth.EventTransactionQueued { + event := envelope.Event.(map[string]interface{}) + + t.Logf("Transaction queued (will be completed in 5 secs): {id: %s}\n", event["id"].(string)) + time.Sleep(5 * time.Second) + + if err := geth.SelectAccount(TEST_ADDRESS, TEST_ADDRESS_PASSWORD); err != nil { + t.Errorf("cannot select account: %v", TEST_ADDRESS) + return + } + + if txHash, err = geth.CompleteTransaction(event["id"].(string), TEST_ADDRESS_PASSWORD); err != nil { + t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err) + return + } else { + t.Logf("Contract created: https://testnet.etherscan.io/tx/%s", txHash.Hex()) + } + + close(completeQueuedTransaction) // so that timeout is aborted + } + }) + + _, err = vm.Run(` + var responseValue = null; + var testContract = web3.eth.contract([{"constant":true,"inputs":[{"name":"a","type":"int256"}],"name":"double","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"}]); + var test = testContract.new( + { + from: '` + TEST_ADDRESS + `', + data: '0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029', + gas: '` + strconv.Itoa(geth.DefaultGas) + `' + }, function (e, contract){ + if (!e) { + responseValue = contract.transactionHash + } + }) + `) + if err != nil { + t.Errorf("cannot run custom code on VM: %v", err) + return + } + + <-completeQueuedTransaction + + responseValue, err := vm.Get("responseValue") + if err != nil { + t.Errorf("cannot obtain result of isConnected(): %v", err) + return + } + + response, err := responseValue.ToString() + if err != nil { + t.Errorf("cannot parse result: %v", err) + return + } + + expectedResponse := txHash.Hex() + if !reflect.DeepEqual(response, expectedResponse) { + t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response) + return + } +}