package jail_test import ( "encoding/json" "os" "reflect" "strconv" "strings" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" "github.com/status-im/status-go/geth" "github.com/status-im/status-go/geth/jail" "github.com/status-im/status-go/geth/params" ) const ( whisperMessage1 = `test message 1 (K1 -> K2, signed+encrypted, from us)` whisperMessage2 = `test message 2 (K1 -> K1, signed+encrypted to ourselves)` whisperMessage3 = `test message 3 (K1 -> "", signed broadcast)` whisperMessage4 = `test message 4 ("" -> "", anon broadcast)` whisperMessage5 = `test message 5 ("" -> K1, encrypted anon broadcast)` whisperMessage6 = `test message 6 (K2 -> K1, signed+encrypted, to us)` chatID = "testChat" statusJSFilePath = "testdata/status.js" txSendFolder = "testdata/tx-send/" ) var testConfig *geth.TestConfig func TestMain(m *testing.M) { // load shared test configuration var err error testConfig, err = geth.LoadTestConfig() if err != nil { panic(err) } // run tests retCode := m.Run() //time.Sleep(25 * time.Second) // to give some time to propagate txs to the rest of the network os.Exit(retCode) } func TestJailUnInited(t *testing.T) { errorWrapper := func(err error) string { return `{"error":"` + err.Error() + `"}` } expectedError := errorWrapper(jail.ErrInvalidJail) var jailInstance *jail.Jail response := jailInstance.Parse(chatID, ``) if response != expectedError { t.Errorf("error expected, but got: %v", response) } response = jailInstance.Call(chatID, `["commands", "testCommand"]`, `{"val": 12}`) if response != expectedError { t.Errorf("error expected, but got: %v", response) } _, err := jailInstance.GetVM(chatID) if err != jail.ErrInvalidJail { t.Errorf("error expected, but got: %v", err) } _, err = jailInstance.RPCClient() if err != jail.ErrInvalidJail { t.Errorf("error expected, but got: %v", err) } // now make sure that if Init is called, then Parse doesn't produce any error jailInstance = jail.Init(``) if jailInstance == nil { t.Error("jail instance shouldn't be nil at this point") return } statusJS := geth.LoadFromFile(statusJSFilePath) + `; _status_catalog.commands["testCommand"] = function (params) { return params.val * params.val; };` response = jailInstance.Parse(chatID, statusJS) expectedResponse := `{"result": {"commands":{},"responses":{}}}` if response != expectedResponse { t.Errorf("unexpected response received: %v", response) } // however, we still expect issue voiced if somebody tries to execute code with Call response = jailInstance.Call(chatID, `["commands", "testCommand"]`, `{"val": 12}`) if response != errorWrapper(geth.ErrInvalidGethNode) { t.Errorf("error expected, but got: %v", response) } // make sure that Call() succeeds when node is started err = geth.PrepareTestNode() if err != nil { t.Error(err) return } response = jailInstance.Call(chatID, `["commands", "testCommand"]`, `{"val": 12}`) expectedResponse = `{"result": 144}` if response != expectedResponse { t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response) return } } func TestJailInit(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } initCode := ` var _status_catalog = { foo: 'bar' }; ` jailInstance := jail.Init(initCode) extraCode := ` var extraFunc = function (x) { return x * x; }; ` response := jailInstance.Parse("newChat", extraCode) expectedResponse := `{"result": {"foo":"bar"}}` if !reflect.DeepEqual(expectedResponse, response) { t.Error("Expected output not returned from jail.Parse()") return } } func TestJailFunctionCall(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } jailInstance := jail.Init("") // load Status JS and add test command to it statusJS := geth.LoadFromFile(statusJSFilePath) + `; _status_catalog.commands["testCommand"] = function (params) { return params.val * params.val; };` jailInstance.Parse(chatID, statusJS) // call with wrong chat id response := jailInstance.Call("chatIdNonExistent", "", "") expectedError := `{"error":"Cell[chatIdNonExistent] doesn't exist."}` if response != expectedError { t.Errorf("expected error is not returned: expected %s, got %s", expectedError, response) return } // call extraFunc() response = jailInstance.Call(chatID, `["commands", "testCommand"]`, `{"val": 12}`) expectedResponse := `{"result": 144}` if response != expectedResponse { t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response) return } } func TestJailRPCSend(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } jailInstance := jail.Init("") // load Status JS and add test command to it statusJS := geth.LoadFromFile(statusJSFilePath) jailInstance.Parse(chatID, statusJS) // obtain VM for a given chat (to send custom JS to jailed version of Send()) vm, err := jailInstance.GetVM(chatID) if err != nil { t.Errorf("cannot get VM: %v", err) return } // internally (since we replaced `web3.send` with `jail.Send`) // all requests to web3 are forwarded to `jail.Send` _, err = vm.Run(` var balance = web3.eth.getBalance("` + testConfig.Account1.Address + `"); var sendResult = web3.fromWei(balance, "ether") `) if err != nil { t.Errorf("cannot run custom code on VM: %v", err) return } value, err := vm.Get("sendResult") if err != nil { t.Errorf("cannot obtain result of balance check operation: %v", err) return } balance, err := value.ToFloat() if err != nil { t.Errorf("cannot obtain result of balance check operation: %v", err) return } if balance < 100 { t.Error("wrong balance (there should be lots of test Ether on that account)") return } t.Logf("Balance of %.2f ETH found on '%s' account", balance, testConfig.Account1.Address) } func TestJailSendQueuedTransaction(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } // log into account from which transactions will be sent if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil { t.Errorf("cannot select account: %v", testConfig.Account1.Address) return } txParams := `{ "from": "` + testConfig.Account1.Address + `", "to": "0xf82da7547534045b4e00442bc89e16186cf8c272", "value": "0.000001" }` txCompletedSuccessfully := make(chan struct{}) txCompletedCounter := make(chan struct{}) txHashes := make(chan common.Hash) // replace transaction notification handler requireMessageId := false 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{}) messageId, ok := event["message_id"].(string) if !ok { t.Error("Message id is required, but not found") return } if requireMessageId { if len(messageId) == 0 { t.Error("Message id is required, but not provided") return } } else { if len(messageId) != 0 { t.Error("Message id is not required, but provided") return } } t.Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string)) var txHash common.Hash if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil { t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err) } else { t.Logf("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex()) } txCompletedSuccessfully <- struct{}{} // so that timeout is aborted txHashes <- txHash txCompletedCounter <- struct{}{} } }) type testCommand struct { command string params string expectedResponse string } type testCase struct { name string file string requireMessageId bool commands []testCommand } tests := []testCase{ { // no context or message id name: "Case 1: no message id or context in inited JS", file: "no-message-id-or-context.js", requireMessageId: false, commands: []testCommand{ { `["commands", "send"]`, txParams, `{"result": {"transaction-hash":"TX_HASH"}}`, }, { `["commands", "getBalance"]`, `{"address": "` + testConfig.Account1.Address + `"}`, `{"result": {"balance":42}}`, }, }, }, { // context is present in inited JS (but no message id is there) name: "Case 2: context is present in inited JS (but no message id is there)", file: "context-no-message-id.js", requireMessageId: false, commands: []testCommand{ { `["commands", "send"]`, txParams, `{"result": {"context":{"` + geth.SendTransactionRequest + `":true},"result":{"transaction-hash":"TX_HASH"}}}`, }, { `["commands", "getBalance"]`, `{"address": "` + testConfig.Account1.Address + `"}`, `{"result": {"context":{},"result":{"balance":42}}}`, // note emtpy (but present) context! }, }, }, { // message id is present in inited JS, but no context is there name: "Case 3: message id is present, context is not present", file: "message-id-no-context.js", requireMessageId: true, commands: []testCommand{ { `["commands", "send"]`, txParams, `{"result": {"transaction-hash":"TX_HASH"}}`, }, { `["commands", "getBalance"]`, `{"address": "` + testConfig.Account1.Address + `"}`, `{"result": {"balance":42}}`, // note emtpy context! }, }, }, { // both message id and context are present in inited JS (this UC is what we normally expect to see) name: "Case 4: both message id and context are present", file: "tx-send.js", requireMessageId: true, commands: []testCommand{ { `["commands", "send"]`, txParams, `{"result": {"context":{"eth_sendTransaction":true,"message_id":"foobar"},"result":{"transaction-hash":"TX_HASH"}}}`, }, { `["commands", "getBalance"]`, `{"address": "` + testConfig.Account1.Address + `"}`, `{"result": {"context":{"message_id":"42"},"result":{"balance":42}}}`, // message id in context, but default one is used! }, }, }, } for _, test := range tests { jailInstance := jail.Init(geth.LoadFromFile(txSendFolder + test.file)) geth.PanicAfter(60*time.Second, txCompletedSuccessfully, test.name) jailInstance.Parse(chatID, ``) requireMessageId = test.requireMessageId for _, command := range test.commands { go func(jail *jail.Jail, test testCase, command testCommand) { t.Logf("->%s: %s", test.name, command.command) response := jail.Call(chatID, command.command, command.params) var txHash common.Hash if command.command == `["commands", "send"]` { txHash = <-txHashes } expectedResponse := strings.Replace(command.expectedResponse, "TX_HASH", txHash.Hex(), 1) if response != expectedResponse { t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response) return } }(jailInstance, test, command) } <-txCompletedCounter } } func TestJailMultipleInitSingletonJail(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } jailInstance1 := jail.Init("") jailInstance2 := jail.Init("") jailInstance3 := jail.New() jailInstance4 := jail.GetInstance() if !reflect.DeepEqual(jailInstance1, jailInstance2) { t.Error("singleton property of jail instance is violated") } if !reflect.DeepEqual(jailInstance2, jailInstance3) { t.Error("singleton property of jail instance is violated") } if !reflect.DeepEqual(jailInstance3, jailInstance4) { t.Error("singleton property of jail instance is violated") } } func TestJailGetVM(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } jailInstance := jail.Init("") expectedError := `Cell[nonExistentChat] doesn't exist.` _, err = jailInstance.GetVM("nonExistentChat") if err == nil || err.Error() != expectedError { t.Error("expected error, but call succeeded") } // now let's create VM.. jailInstance.Parse(chatID, ``) // ..and see if VM becomes available _, err = jailInstance.GetVM(chatID) if err != nil { t.Errorf("unexpected error: %v", err) } } func TestIsConnected(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } jailInstance := jail.Init("") jailInstance.Parse(chatID, "") // obtain VM for a given chat (to send custom JS to jailed version of Send()) vm, err := jailInstance.GetVM(chatID) if err != nil { t.Errorf("cannot get VM: %v", err) return } _, err = vm.Run(` var responseValue = web3.isConnected(); responseValue = JSON.stringify(responseValue); `) if err != nil { t.Errorf("cannot run custom code on VM: %v", err) return } 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 := `{"jsonrpc":"2.0","result":true}` if !reflect.DeepEqual(response, expectedResponse) { t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response) return } } func TestLocalStorageSet(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } jailInstance := jail.Init("") jailInstance.Parse(chatID, "") // obtain VM for a given chat (to send custom JS to jailed version of Send()) vm, err := jailInstance.GetVM(chatID) if err != nil { t.Errorf("cannot get VM: %v", err) return } testData := "foobar" opCompletedSuccessfully := make(chan struct{}, 1) // replace transaction notification handler 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 == jail.EventLocalStorageSet { event := envelope.Event.(map[string]interface{}) chatId, ok := event["chat_id"].(string) if !ok { t.Error("Chat id is required, but not found") return } if chatId != chatID { t.Errorf("incorrect chat id: expected %q, got: %q", chatID, chatId) return } actualData, ok := event["data"].(string) if !ok { t.Error("Data field is required, but not found") return } if actualData != testData { t.Errorf("incorrect data: expected %q, got: %q", testData, actualData) return } t.Logf("event processed: %s", jsonEvent) opCompletedSuccessfully <- struct{}{} // so that timeout is aborted } }) _, err = vm.Run(` var responseValue = localStorage.set("` + testData + `"); responseValue = JSON.stringify(responseValue); `) if err != nil { t.Errorf("cannot run custom code on VM: %v", err) return } // make sure that signal is sent (and its parameters are correct) select { case <-opCompletedSuccessfully: // pass case <-time.After(3 * time.Second): t.Error("operation timed out") } responseValue, err := vm.Get("responseValue") if err != nil { t.Errorf("cannot obtain result of localStorage.set(): %v", err) return } response, err := responseValue.ToString() if err != nil { t.Errorf("cannot parse result: %v", err) return } expectedResponse := `{"jsonrpc":"2.0","result":true}` if !reflect.DeepEqual(response, expectedResponse) { t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response) return } } func TestContractDeployment(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } jailInstance := jail.Init("") jailInstance.Parse(chatID, "") // obtain VM for a given chat (to send custom JS to jailed version of Send()) vm, err := jailInstance.GetVM(chatID) 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 shortly): {id: %s}\n", event["id"].(string)) if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil { t.Errorf("cannot select account: %v", testConfig.Account1.Address) return } if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.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: '` + testConfig.Account1.Address + `', data: '0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029', gas: '` + strconv.Itoa(params.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("vm.Get() failed: %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 } } func TestGasEstimation(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } jailInstance := jail.Init("") jailInstance.Parse(chatID, "") // obtain VM for a given chat (to send custom JS to jailed version of Send()) vm, err := jailInstance.GetVM(chatID) 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 immediately): {id: %s}\n", event["id"].(string)) if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil { t.Errorf("cannot select account: %v", testConfig.Account1.Address) return } if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.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: '` + testConfig.Account1.Address + `', data: '0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029', }, 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("vm.Get() failed: %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 } } func TestJailWhisper(t *testing.T) { err := geth.PrepareTestNode() if err != nil { t.Error(err) return } whisperService, err := geth.NodeManagerInstance().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } whisperAPI := whisper.NewPublicWhisperAPI(whisperService) // account1 _, accountKey1, err := geth.AddressToDecryptedAccount(testConfig.Account1.Address, testConfig.Account1.Password) if err != nil { t.Fatal(err) } accountKey1Hex := common.ToHex(crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey)) whisperService.AddIdentity(accountKey1.PrivateKey) if ok, err := whisperAPI.HasIdentity(accountKey1Hex); err != nil || !ok { t.Fatalf("identity not injected: %v", accountKey1Hex) } // account2 _, accountKey2, err := geth.AddressToDecryptedAccount(testConfig.Account2.Address, testConfig.Account2.Password) if err != nil { t.Fatal(err) } accountKey2Hex := common.ToHex(crypto.FromECDSAPub(&accountKey2.PrivateKey.PublicKey)) whisperService.AddIdentity(accountKey2.PrivateKey) if ok, err := whisperAPI.HasIdentity(accountKey2Hex); err != nil || !ok { t.Fatalf("identity not injected: %v", accountKey2Hex) } passedTests := map[string]bool{ whisperMessage1: false, whisperMessage2: false, whisperMessage3: false, whisperMessage4: false, whisperMessage5: false, whisperMessage6: false, } installedFilters := map[string]string{ whisperMessage1: "", whisperMessage2: "", whisperMessage3: "", whisperMessage4: "", whisperMessage5: "", whisperMessage6: "", } jailInstance := jail.Init("") testCases := []struct { name string testCode string useFilter bool }{ { "test 0: ensure correct version of Whisper is used", ` var expectedVersion = '0x5'; if (web3.version.whisper != expectedVersion) { throw 'unexpected shh version, expected: ' + expectedVersion + ', got: ' + web3.version.whisper; } `, false, }, { "test 1: encrypted signed message from us (From != nil && To != nil)", ` var identity1 = '` + accountKey1Hex + `'; if (!web3.shh.hasIdentity(identity1)) { throw 'idenitity "` + accountKey1Hex + `" not found in whisper'; } var identity2 = '` + accountKey2Hex + `'; if (!web3.shh.hasIdentity(identity2)) { throw 'idenitity "` + accountKey2Hex + `" not found in whisper'; } var topic = 'example1'; var payload = '` + whisperMessage1 + `'; // start watching for messages var filter = shh.filter({ from: identity1, to: identity2, topics: [web3.fromAscii(topic)] }); // post message var message = { from: identity1, to: identity2, topics: [web3.fromAscii(topic)], payload: payload, ttl: 20, }; var err = shh.post(message) if (err !== null) { throw 'message not sent: ' + message; } var filterName = '` + whisperMessage1 + `'; var filterId = filter.filterId; if (!filterId) { throw 'filter not installed properly'; } `, true, }, { "test 2: encrypted signed message to yourself (From != nil && To != nil)", ` var identity = '` + accountKey1Hex + `'; if (!web3.shh.hasIdentity(identity)) { throw 'idenitity "` + accountKey1Hex + `" not found in whisper'; } var topic = 'example2'; var payload = '` + whisperMessage2 + `'; // start watching for messages var filter = shh.filter({ from: identity, to: identity, topics: [web3.fromAscii(topic)], }); // post message var message = { from: identity, to: identity, topics: [web3.fromAscii(topic)], payload: payload, ttl: 20, }; var err = shh.post(message) if (err !== null) { throw 'message not sent: ' + message; } var filterName = '` + whisperMessage2 + `'; var filterId = filter.filterId; if (!filterId) { throw 'filter not installed properly'; } `, true, }, { "test 3: signed (known sender) broadcast (From != nil && To == nil)", ` var identity = '` + accountKey1Hex + `'; if (!web3.shh.hasIdentity(identity)) { throw 'idenitity "` + accountKey1Hex + `" not found in whisper'; } var topic = 'example3'; var payload = '` + whisperMessage3 + `'; // generate symmetric key (if doesn't already exist) if (!shh.hasSymKey(topic)) { shh.addSymKey(topic, "0xdeadbeef"); // alternatively: shh.generateSymKey("example3"); // to delete key, rely on: shh.deleteSymKey(topic); } // start watching for messages var filter = shh.filter({ from: identity, topics: [web3.fromAscii(topic)], keyname: topic // you can use some other name for key too }); // post message var message = { from: identity, topics: [web3.fromAscii(topic)], payload: payload, ttl: 20, keyname: topic }; var err = shh.post(message) if (err !== null) { throw 'message not sent: ' + message; } var filterName = '` + whisperMessage3 + `'; var filterId = filter.filterId; if (!filterId) { throw 'filter not installed properly'; } `, true, }, { "test 4: anonymous broadcast (From == nil && To == nil)", ` var topic = 'example4'; var payload = '` + whisperMessage4 + `'; // generate symmetric key (if doesn't already exist) if (!shh.hasSymKey(topic)) { shh.addSymKey(topic, "0xdeadbeef"); // alternatively: shh.generateSymKey("example3"); // to delete key, rely on: shh.deleteSymKey(topic); } // start watching for messages var filter = shh.filter({ topics: [web3.fromAscii(topic)], keyname: topic // you can use some other name for key too }); // post message var message = { topics: [web3.fromAscii(topic)], payload: payload, ttl: 20, keyname: topic }; var err = shh.post(message) if (err !== null) { throw 'message not sent: ' + err; } var filterName = '` + whisperMessage4 + `'; var filterId = filter.filterId; if (!filterId) { throw 'filter not installed properly'; } `, true, }, { "test 5: encrypted anonymous message (From == nil && To != nil)", ` var identity = '` + accountKey2Hex + `'; if (!web3.shh.hasIdentity(identity)) { throw 'idenitity "` + accountKey2Hex + `" not found in whisper'; } var topic = 'example5'; var payload = '` + whisperMessage5 + `'; // start watching for messages var filter = shh.filter({ to: identity, topics: [web3.fromAscii(topic)], }); // post message var message = { to: identity, topics: [web3.fromAscii(topic)], payload: payload, ttl: 20 }; var err = shh.post(message) if (err !== null) { throw 'message not sent: ' + message; } var filterName = '` + whisperMessage5 + `'; var filterId = filter.filterId; if (!filterId) { throw 'filter not installed properly'; } `, true, }, { "test 6: encrypted signed response to us (From != nil && To != nil)", ` var identity1 = '` + accountKey1Hex + `'; if (!web3.shh.hasIdentity(identity1)) { throw 'idenitity "` + accountKey1Hex + `" not found in whisper'; } var identity2 = '` + accountKey2Hex + `'; if (!web3.shh.hasIdentity(identity2)) { throw 'idenitity "` + accountKey2Hex + `" not found in whisper'; } var topic = 'example6'; var payload = '` + whisperMessage6 + `'; // start watching for messages var filter = shh.filter({ from: identity2, to: identity1, topics: [web3.fromAscii(topic)] }); // post message var message = { from: identity2, to: identity1, topics: [web3.fromAscii(topic)], payload: payload, ttl: 20 }; var err = shh.post(message) if (err !== null) { throw 'message not sent: ' + message; } var filterName = '` + whisperMessage6 + `'; var filterId = filter.filterId; if (!filterId) { throw 'filter not installed properly'; } `, true, }, } for _, testCase := range testCases { t.Log(testCase.name) testCaseKey := crypto.Keccak256Hash([]byte(testCase.name)).Hex() jailInstance.Parse(testCaseKey, `var shh = web3.shh;`) vm, err := jailInstance.GetVM(testCaseKey) if err != nil { t.Errorf("cannot get VM: %v", err) return } // post messages if _, err := vm.Run(testCase.testCode); err != nil { t.Error(err) return } if !testCase.useFilter { continue } // update installed filters filterId, err := vm.Get("filterId") if err != nil { t.Errorf("cannot get filterId: %v", err) return } filterName, err := vm.Get("filterName") if err != nil { t.Errorf("cannot get filterName: %v", err) return } if _, ok := installedFilters[filterName.String()]; !ok { t.Fatal("unrecognized filter") } installedFilters[filterName.String()] = filterId.String() } time.Sleep(2 * time.Second) // allow whisper to poll for testKey, filter := range installedFilters { if filter != "" { t.Logf("filter found: %v", filter) for _, message := range whisperAPI.GetFilterChanges(filter) { t.Logf("message found: %s", common.FromHex(message.Payload)) passedTests[testKey] = true } } } for testName, passedTest := range passedTests { if !passedTest { t.Fatalf("test not passed: %v", testName) } } }