Major code refactoring
- geth package implemented - jail package implemented (and tested) - cgo and xgo (android, ios) builds w/o any issues - better conditional compilation of signals code
This commit is contained in:
parent
5fb4aef1cc
commit
edd8763c3c
|
@ -13,8 +13,6 @@
|
||||||
.ethtest
|
.ethtest
|
||||||
*/**/*tx_database*
|
*/**/*tx_database*
|
||||||
*/**/*dapps*
|
*/**/*dapps*
|
||||||
Godeps/_workspace/pkg
|
|
||||||
Godeps/_workspace/bin
|
|
||||||
|
|
||||||
#*
|
#*
|
||||||
.#*
|
.#*
|
||||||
|
@ -35,8 +33,9 @@ profile.cov
|
||||||
.vagrant
|
.vagrant
|
||||||
|
|
||||||
# tests
|
# tests
|
||||||
src/.ethereumtest/
|
.ethereumtest/
|
||||||
#
|
#
|
||||||
# golang
|
# golang
|
||||||
cover.out
|
coverage.out
|
||||||
cover.html
|
coverage-all.out
|
||||||
|
coverage.html
|
||||||
|
|
39
Makefile
39
Makefile
|
@ -5,7 +5,7 @@ GOBIN = build/bin
|
||||||
GO ?= latest
|
GO ?= latest
|
||||||
|
|
||||||
statusgo:
|
statusgo:
|
||||||
build/env.sh go build -i -o $(GOBIN)/statusgo -v $(shell build/flags.sh) ./src
|
build/env.sh go build -i -o $(GOBIN)/statusgo -v $(shell build/flags.sh) ./cmd/status
|
||||||
@echo "status go compilation done."
|
@echo "status go compilation done."
|
||||||
@echo "Run \"build/bin/statusgo\" to view available commands"
|
@echo "Run \"build/bin/statusgo\" to view available commands"
|
||||||
|
|
||||||
|
@ -14,25 +14,48 @@ statusgo-cross: statusgo-android statusgo-ios
|
||||||
@ls -ld $(GOBIN)/statusgo-*
|
@ls -ld $(GOBIN)/statusgo-*
|
||||||
|
|
||||||
statusgo-android: xgo
|
statusgo-android: xgo
|
||||||
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=android-16/aar -v $(shell build/flags.sh) ./src
|
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=android-16/aar -v $(shell build/flags.sh) ./cmd/status
|
||||||
@echo "Android cross compilation done:"
|
@echo "Android cross compilation done:"
|
||||||
|
|
||||||
statusgo-ios: xgo
|
statusgo-ios: xgo
|
||||||
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/flags.sh) ./src
|
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/flags.sh) ./cmd/status
|
||||||
@echo "iOS framework cross compilation done:"
|
@echo "iOS framework cross compilation done:"
|
||||||
|
|
||||||
statusgo-ios-simulator: xgo
|
statusgo-ios-simulator: xgo
|
||||||
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/flags.sh) ./src
|
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/flags.sh) ./cmd/status
|
||||||
@echo "iOS framework cross compilation done:"
|
@echo "iOS framework cross compilation done:"
|
||||||
|
|
||||||
xgo:
|
xgo:
|
||||||
build/env.sh go get github.com/karalabe/xgo
|
build/env.sh go get github.com/karalabe/xgo
|
||||||
|
|
||||||
test:
|
test-all:
|
||||||
build/env.sh go test -v -coverprofile=cover.out ./src
|
@build/env.sh echo "mode: set" > coverage-all.out
|
||||||
|
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth
|
||||||
|
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
|
||||||
|
build/env.sh go test -coverprofile=coverage.out -covermode=set ./jail
|
||||||
|
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
|
||||||
|
build/env.sh go test -coverprofile=coverage.out -covermode=set ./extkeys
|
||||||
|
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
|
||||||
|
@build/env.sh go tool cover -html=coverage-all.out -o coverage.html
|
||||||
|
@build/env.sh go tool cover -func=coverage-all.out
|
||||||
|
|
||||||
test-cover: test
|
test: test-all
|
||||||
build/env.sh go tool cover -html=cover.out -o cover.html
|
|
||||||
|
test-geth:
|
||||||
|
build/env.sh go test -v -coverprofile=coverage.out ./geth
|
||||||
|
@build/env.sh go tool cover -html=coverage.out -o coverage.html
|
||||||
|
@build/env.sh go tool cover -func=coverage.out
|
||||||
|
|
||||||
|
test-jail:
|
||||||
|
build/env.sh go test -v -coverprofile=coverage.out ./jail
|
||||||
|
@build/env.sh go tool cover -html=coverage.out -o coverage.html
|
||||||
|
@build/env.sh go tool cover -func=coverage.out
|
||||||
|
|
||||||
|
test-extkeys:
|
||||||
|
build/env.sh go test -v -coverprofile=coverage.out ./extkeys
|
||||||
|
@build/env.sh go tool cover -html=coverage.out -o coverage.html
|
||||||
|
@build/env.sh go tool cover -func=coverage.out
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -fr build/bin/*
|
rm -fr build/bin/*
|
||||||
|
rm coverage.out coverage-all.out coverage.html
|
||||||
|
|
|
@ -16,7 +16,7 @@ WS2="$ROOT/build/_workspace/project"
|
||||||
if [ ! -d "$WS1/src" ]; then
|
if [ ! -d "$WS1/src" ]; then
|
||||||
mkdir -p "$WS1"
|
mkdir -p "$WS1"
|
||||||
cd "$WS1"
|
cd "$WS1"
|
||||||
ln -s "$ROOT/src/vendor" src
|
ln -s "$ROOT/vendor" src
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,39 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
extern bool StatusServiceSignalEvent(const char *jsonEvent);
|
||||||
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ethereum/go-ethereum/whisper"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/whisper"
|
||||||
|
"github.com/status-im/status-go/geth"
|
||||||
|
"github.com/status-im/status-go/jail"
|
||||||
)
|
)
|
||||||
|
|
||||||
var emptyError = ""
|
// export TriggerTestSignal
|
||||||
|
func TriggerTestSignal() {
|
||||||
|
C.StatusServiceSignalEvent(C.CString(`{"answer": 42}`))
|
||||||
|
}
|
||||||
|
|
||||||
//export CreateAccount
|
//export CreateAccount
|
||||||
func CreateAccount(password *C.char) *C.char {
|
func CreateAccount(password *C.char) *C.char {
|
||||||
|
|
||||||
// This is equivalent to creating an account from the command line,
|
// This is equivalent to creating an account from the command line,
|
||||||
// just modified to handle the function arg passing
|
// just modified to handle the function arg passing
|
||||||
address, pubKey, mnemonic, err := createAccount(C.GoString(password))
|
address, pubKey, mnemonic, err := geth.CreateAccount(C.GoString(password))
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := AccountInfo{
|
out := geth.AccountInfo{
|
||||||
Address: address,
|
Address: address,
|
||||||
PubKey: pubKey,
|
PubKey: pubKey,
|
||||||
Mnemonic: mnemonic,
|
Mnemonic: mnemonic,
|
||||||
|
@ -37,15 +47,15 @@ func CreateAccount(password *C.char) *C.char {
|
||||||
//export CreateChildAccount
|
//export CreateChildAccount
|
||||||
func CreateChildAccount(parentAddress, password *C.char) *C.char {
|
func CreateChildAccount(parentAddress, password *C.char) *C.char {
|
||||||
|
|
||||||
address, pubKey, err := createChildAccount(C.GoString(parentAddress), C.GoString(password))
|
address, pubKey, err := geth.CreateChildAccount(C.GoString(parentAddress), C.GoString(password))
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := AccountInfo{
|
out := geth.AccountInfo{
|
||||||
Address: address,
|
Address: address,
|
||||||
PubKey: pubKey,
|
PubKey: pubKey,
|
||||||
Error: errString,
|
Error: errString,
|
||||||
|
@ -58,15 +68,15 @@ func CreateChildAccount(parentAddress, password *C.char) *C.char {
|
||||||
//export RecoverAccount
|
//export RecoverAccount
|
||||||
func RecoverAccount(password, mnemonic *C.char) *C.char {
|
func RecoverAccount(password, mnemonic *C.char) *C.char {
|
||||||
|
|
||||||
address, pubKey, err := recoverAccount(C.GoString(password), C.GoString(mnemonic))
|
address, pubKey, err := geth.RecoverAccount(C.GoString(password), C.GoString(mnemonic))
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := AccountInfo{
|
out := geth.AccountInfo{
|
||||||
Address: address,
|
Address: address,
|
||||||
PubKey: pubKey,
|
PubKey: pubKey,
|
||||||
Mnemonic: C.GoString(mnemonic),
|
Mnemonic: C.GoString(mnemonic),
|
||||||
|
@ -81,15 +91,15 @@ func RecoverAccount(password, mnemonic *C.char) *C.char {
|
||||||
func Login(address, password *C.char) *C.char {
|
func Login(address, password *C.char) *C.char {
|
||||||
// loads a key file (for a given address), tries to decrypt it using the password, to verify ownership
|
// loads a key file (for a given address), tries to decrypt it using the password, to verify ownership
|
||||||
// if verified, purges all the previous identities from Whisper, and injects verified key as shh identity
|
// if verified, purges all the previous identities from Whisper, and injects verified key as shh identity
|
||||||
err := selectAccount(C.GoString(address), C.GoString(password))
|
err := geth.SelectAccount(C.GoString(address), C.GoString(password))
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := JSONError{
|
out := geth.JSONError{
|
||||||
Error: errString,
|
Error: errString,
|
||||||
}
|
}
|
||||||
outBytes, _ := json.Marshal(&out)
|
outBytes, _ := json.Marshal(&out)
|
||||||
|
@ -101,15 +111,15 @@ func Login(address, password *C.char) *C.char {
|
||||||
func Logout() *C.char {
|
func Logout() *C.char {
|
||||||
|
|
||||||
// This is equivalent to clearing whisper identities
|
// This is equivalent to clearing whisper identities
|
||||||
err := logout()
|
err := geth.Logout()
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := JSONError{
|
out := geth.JSONError{
|
||||||
Error: errString,
|
Error: errString,
|
||||||
}
|
}
|
||||||
outBytes, _ := json.Marshal(&out)
|
outBytes, _ := json.Marshal(&out)
|
||||||
|
@ -123,15 +133,15 @@ func UnlockAccount(address, password *C.char, seconds int) *C.char {
|
||||||
// This is equivalent to unlocking an account from the command line,
|
// This is equivalent to unlocking an account from the command line,
|
||||||
// just modified to unlock the account for the currently running geth node
|
// just modified to unlock the account for the currently running geth node
|
||||||
// based on the provided arguments
|
// based on the provided arguments
|
||||||
err := unlockAccount(C.GoString(address), C.GoString(password), seconds)
|
err := geth.UnlockAccount(C.GoString(address), C.GoString(password), seconds)
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := JSONError{
|
out := geth.JSONError{
|
||||||
Error: errString,
|
Error: errString,
|
||||||
}
|
}
|
||||||
outBytes, _ := json.Marshal(&out)
|
outBytes, _ := json.Marshal(&out)
|
||||||
|
@ -141,15 +151,15 @@ func UnlockAccount(address, password *C.char, seconds int) *C.char {
|
||||||
|
|
||||||
//export CompleteTransaction
|
//export CompleteTransaction
|
||||||
func CompleteTransaction(id, password *C.char) *C.char {
|
func CompleteTransaction(id, password *C.char) *C.char {
|
||||||
txHash, err := completeTransaction(C.GoString(id), C.GoString(password))
|
txHash, err := geth.CompleteTransaction(C.GoString(id), C.GoString(password))
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := CompleteTransactionResult{
|
out := geth.CompleteTransactionResult{
|
||||||
Hash: txHash.Hex(),
|
Hash: txHash.Hex(),
|
||||||
Error: errString,
|
Error: errString,
|
||||||
}
|
}
|
||||||
|
@ -160,17 +170,16 @@ func CompleteTransaction(id, password *C.char) *C.char {
|
||||||
|
|
||||||
//export StartNode
|
//export StartNode
|
||||||
func StartNode(datadir *C.char) *C.char {
|
func StartNode(datadir *C.char) *C.char {
|
||||||
|
|
||||||
// This starts a geth node with the given datadir
|
// This starts a geth node with the given datadir
|
||||||
err := createAndStartNode(C.GoString(datadir))
|
err := geth.CreateAndRunNode(C.GoString(datadir), geth.RPCPort)
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := JSONError{
|
out := geth.JSONError{
|
||||||
Error: errString,
|
Error: errString,
|
||||||
}
|
}
|
||||||
outBytes, _ := json.Marshal(&out)
|
outBytes, _ := json.Marshal(&out)
|
||||||
|
@ -178,33 +187,33 @@ func StartNode(datadir *C.char) *C.char {
|
||||||
return C.CString(string(outBytes))
|
return C.CString(string(outBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
//export parse
|
//export InitJail
|
||||||
func parse(chatId *C.char, js *C.char) *C.char {
|
func InitJail(js *C.char) {
|
||||||
res := Parse(C.GoString(chatId), C.GoString(js))
|
jail.Init(C.GoString(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export Parse
|
||||||
|
func Parse(chatId *C.char, js *C.char) *C.char {
|
||||||
|
res := jail.GetInstance().Parse(C.GoString(chatId), C.GoString(js))
|
||||||
return C.CString(res)
|
return C.CString(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export call
|
//export Call
|
||||||
func call(chatId *C.char, path *C.char, params *C.char) *C.char {
|
func Call(chatId *C.char, path *C.char, params *C.char) *C.char {
|
||||||
res := Call(C.GoString(chatId), C.GoString(path), C.GoString(params))
|
res := jail.GetInstance().Call(C.GoString(chatId), C.GoString(path), C.GoString(params))
|
||||||
return C.CString(res)
|
return C.CString(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export initJail
|
//export AddPeer
|
||||||
func initJail(js *C.char) {
|
func AddPeer(url *C.char) *C.char {
|
||||||
Init(C.GoString(js))
|
success, err := geth.GetNodeManager().AddPeer(C.GoString(url))
|
||||||
}
|
errString := ""
|
||||||
|
|
||||||
//export addPeer
|
|
||||||
func addPeer(url *C.char) *C.char {
|
|
||||||
success, err := doAddPeer(C.GoString(url))
|
|
||||||
errString := emptyError
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := AddPeerResult{
|
out := geth.AddPeerResult{
|
||||||
Success: success,
|
Success: success,
|
||||||
Error: errString,
|
Error: errString,
|
||||||
}
|
}
|
||||||
|
@ -213,24 +222,24 @@ func addPeer(url *C.char) *C.char {
|
||||||
return C.CString(string(outBytes))
|
return C.CString(string(outBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
//export addWhisperFilter
|
//export AddWhisperFilter
|
||||||
func addWhisperFilter(filterJson *C.char) *C.char {
|
func AddWhisperFilter(filterJson *C.char) *C.char {
|
||||||
|
|
||||||
var id int
|
var id int
|
||||||
var filter whisper.NewFilterArgs
|
var filter whisper.NewFilterArgs
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(filterJson)), &filter)
|
err := json.Unmarshal([]byte(C.GoString(filterJson)), &filter)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
id = doAddWhisperFilter(filter)
|
id = geth.AddWhisperFilter(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
errString := emptyError
|
errString := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
out := AddWhisperFilterResult{
|
out := geth.AddWhisperFilterResult{
|
||||||
Id: id,
|
Id: id,
|
||||||
Error: errString,
|
Error: errString,
|
||||||
}
|
}
|
||||||
|
@ -240,14 +249,12 @@ func addWhisperFilter(filterJson *C.char) *C.char {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export removeWhisperFilter
|
//export RemoveWhisperFilter
|
||||||
func removeWhisperFilter(idFilter int) {
|
func RemoveWhisperFilter(idFilter int) {
|
||||||
|
geth.RemoveWhisperFilter(idFilter)
|
||||||
doRemoveWhisperFilter(idFilter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export clearWhisperFilters
|
//export ClearWhisperFilters
|
||||||
func clearWhisperFilters() {
|
func ClearWhisperFilters() {
|
||||||
|
geth.ClearWhisperFilters()
|
||||||
doClearWhisperFilters()
|
|
||||||
}
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func copyFile(dst, src string) error {
|
|
||||||
s, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
d, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer d.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(d, s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/status-im/status-go/extkeys"
|
"github.com/status-im/status-go/extkeys"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,16 @@
|
||||||
package main
|
package geth
|
||||||
|
|
||||||
/*
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
extern bool GethServiceSignalEvent( const char *jsonEvent );
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/les/status"
|
"github.com/status-im/status-go/extkeys"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
|
||||||
errextra "github.com/pkg/errors"
|
|
||||||
"github.com/status-im/status-go/src/extkeys"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidGethNode = errors.New("no running node detected for account unlock")
|
|
||||||
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
|
|
||||||
ErrInvalidAccountManager = errors.New("could not retrieve account manager")
|
|
||||||
ErrAddressToAccountMappingFailure = errors.New("cannot retreive a valid account for a given address")
|
ErrAddressToAccountMappingFailure = errors.New("cannot retreive a valid account for a given address")
|
||||||
ErrAccountToKeyMappingFailure = errors.New("cannot retreive a valid key for a given account")
|
ErrAccountToKeyMappingFailure = errors.New("cannot retreive a valid key for a given account")
|
||||||
ErrUnlockCalled = errors.New("no need to unlock accounts, login instead")
|
ErrUnlockCalled = errors.New("no need to unlock accounts, login instead")
|
||||||
|
@ -32,32 +18,25 @@ var (
|
||||||
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
|
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
|
||||||
ErrWhisperNoIdentityFound = errors.New("failed to locate identity previously injected into Whisper")
|
ErrWhisperNoIdentityFound = errors.New("failed to locate identity previously injected into Whisper")
|
||||||
ErrNoAccountSelected = errors.New("no account has been selected, please login")
|
ErrNoAccountSelected = errors.New("no account has been selected, please login")
|
||||||
|
ErrInvalidMasterKeyCreated = errors.New("can not create master extended key")
|
||||||
)
|
)
|
||||||
|
|
||||||
// createAccount creates an internal geth account
|
// createAccount creates an internal geth account
|
||||||
// BIP44-compatible keys are generated: CKD#1 is stored as account key, CKD#2 stored as sub-account root
|
// BIP44-compatible keys are generated: CKD#1 is stored as account key, CKD#2 stored as sub-account root
|
||||||
// Public key of CKD#1 is returned, with CKD#2 securely encoded into account key file (to be used for
|
// Public key of CKD#1 is returned, with CKD#2 securely encoded into account key file (to be used for
|
||||||
// sub-account derivations)
|
// sub-account derivations)
|
||||||
func createAccount(password string) (address, pubKey, mnemonic string, err error) {
|
func CreateAccount(password string) (address, pubKey, mnemonic string, err error) {
|
||||||
if currentNode == nil {
|
|
||||||
return "", "", "", ErrInvalidGethNode
|
|
||||||
}
|
|
||||||
|
|
||||||
if accountManager == nil {
|
|
||||||
return "", "", "", ErrInvalidAccountManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate mnemonic phrase
|
// generate mnemonic phrase
|
||||||
m := extkeys.NewMnemonic()
|
m := extkeys.NewMnemonic(extkeys.Salt)
|
||||||
mnemonic, err = m.MnemonicPhrase(128, extkeys.EnglishLanguage)
|
mnemonic, err = m.MnemonicPhrase(128, extkeys.EnglishLanguage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", errextra.Wrap(err, "Can not create mnemonic seed")
|
return "", "", "", fmt.Errorf("can not create mnemonic seed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate extended master key (see BIP32)
|
// generate extended master key (see BIP32)
|
||||||
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", errextra.Wrap(err, "Can not create master extended key")
|
return "", "", "", fmt.Errorf("can not create master extended key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// import created key into account keystore
|
// import created key into account keystore
|
||||||
|
@ -72,17 +51,15 @@ func createAccount(password string) (address, pubKey, mnemonic string, err error
|
||||||
// createChildAccount creates sub-account for an account identified by parent address.
|
// createChildAccount creates sub-account for an account identified by parent address.
|
||||||
// CKD#2 is used as root for master accounts (when parentAddress is "").
|
// CKD#2 is used as root for master accounts (when parentAddress is "").
|
||||||
// Otherwise (when parentAddress != ""), child is derived directly from parent.
|
// Otherwise (when parentAddress != ""), child is derived directly from parent.
|
||||||
func createChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
func CreateChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
||||||
if currentNode == nil {
|
nodeManager := GetNodeManager()
|
||||||
return "", "", ErrInvalidGethNode
|
accountManager, err := nodeManager.AccountManager()
|
||||||
}
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
if accountManager == nil {
|
|
||||||
return "", "", ErrInvalidAccountManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if parentAddress == "" { // by default derive from currently selected account
|
if parentAddress == "" { // by default derive from currently selected account
|
||||||
parentAddress = selectedAddress
|
parentAddress = nodeManager.SelectedAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
if parentAddress == "" {
|
if parentAddress == "" {
|
||||||
|
@ -123,20 +100,12 @@ func createChildAccount(parentAddress, password string) (address, pubKey string,
|
||||||
|
|
||||||
// recoverAccount re-creates master key using given details.
|
// recoverAccount re-creates master key using given details.
|
||||||
// Once master key is re-generated, it is inserted into keystore (if not already there).
|
// Once master key is re-generated, it is inserted into keystore (if not already there).
|
||||||
func recoverAccount(password, mnemonic string) (address, pubKey string, err error) {
|
func RecoverAccount(password, mnemonic string) (address, pubKey string, err error) {
|
||||||
if currentNode == nil {
|
|
||||||
return "", "", ErrInvalidGethNode
|
|
||||||
}
|
|
||||||
|
|
||||||
if accountManager == nil {
|
|
||||||
return "", "", ErrInvalidAccountManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-create extended key (see BIP32)
|
// re-create extended key (see BIP32)
|
||||||
m := extkeys.NewMnemonic()
|
m := extkeys.NewMnemonic(extkeys.Salt)
|
||||||
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", errextra.Wrap(err, "Can not create master extended key")
|
return "", "", ErrInvalidMasterKeyCreated
|
||||||
}
|
}
|
||||||
|
|
||||||
// import re-created key into account keystore
|
// import re-created key into account keystore
|
||||||
|
@ -151,14 +120,13 @@ func recoverAccount(password, mnemonic string) (address, pubKey string, err erro
|
||||||
// selectAccount selects current account, by verifying that address has corresponding account which can be decrypted
|
// selectAccount selects current account, by verifying that address has corresponding account which can be decrypted
|
||||||
// using provided password. Once verification is done, decrypted key is injected into Whisper (as a single identity,
|
// using provided password. Once verification is done, decrypted key is injected into Whisper (as a single identity,
|
||||||
// all previous identities are removed).
|
// all previous identities are removed).
|
||||||
func selectAccount(address, password string) error {
|
func SelectAccount(address, password string) error {
|
||||||
if currentNode == nil {
|
nodeManager := GetNodeManager()
|
||||||
return ErrInvalidGethNode
|
accountManager, err := nodeManager.AccountManager()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if accountManager == nil {
|
|
||||||
return ErrInvalidAccountManager
|
|
||||||
}
|
|
||||||
account, err := utils.MakeAddress(accountManager, address)
|
account, err := utils.MakeAddress(accountManager, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrAddressToAccountMappingFailure
|
return ErrAddressToAccountMappingFailure
|
||||||
|
@ -169,34 +137,35 @@ func selectAccount(address, password string) error {
|
||||||
return fmt.Errorf("%s: %v", ErrAccountToKeyMappingFailure.Error(), err)
|
return fmt.Errorf("%s: %v", ErrAccountToKeyMappingFailure.Error(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if whisperService == nil {
|
whisperService, err := nodeManager.WhisperService()
|
||||||
return ErrInvalidWhisperService
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := whisperService.InjectIdentity(accountKey.PrivateKey); err != nil {
|
if err := whisperService.InjectIdentity(accountKey.PrivateKey); err != nil {
|
||||||
return ErrWhisperIdentityInjectionFailure
|
return ErrWhisperIdentityInjectionFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
// persist address for easier recovery of currently selected key (from Whisper)
|
// persist address for easier recovery of currently selected key (from Whisper)
|
||||||
selectedAddress = address
|
nodeManager.SelectedAddress = address
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// logout clears whisper identities
|
// logout clears whisper identities
|
||||||
func logout() error {
|
func Logout() error {
|
||||||
if currentNode == nil {
|
nodeManager := GetNodeManager()
|
||||||
return ErrInvalidGethNode
|
whisperService, err := nodeManager.WhisperService()
|
||||||
}
|
if err != nil {
|
||||||
if whisperService == nil {
|
return err
|
||||||
return ErrInvalidWhisperService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := whisperService.ClearIdentities()
|
err = whisperService.ClearIdentities()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %v", ErrWhisperClearIdentitiesFailure, err)
|
return fmt.Errorf("%s: %v", ErrWhisperClearIdentitiesFailure, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedAddress = ""
|
nodeManager.SelectedAddress = ""
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -204,85 +173,31 @@ func logout() error {
|
||||||
// unlockAccount unlocks an existing account for a certain duration and
|
// unlockAccount unlocks an existing account for a certain duration and
|
||||||
// inject the account as a whisper identity if the account was created as
|
// inject the account as a whisper identity if the account was created as
|
||||||
// a whisper enabled account
|
// a whisper enabled account
|
||||||
func unlockAccount(address, password string, seconds int) error {
|
func UnlockAccount(address, password string, seconds int) error {
|
||||||
if currentNode == nil {
|
|
||||||
return ErrInvalidGethNode
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrUnlockCalled
|
return ErrUnlockCalled
|
||||||
}
|
}
|
||||||
|
|
||||||
// importExtendedKey processes incoming extended key, extracts required info and creates corresponding account key.
|
// importExtendedKey processes incoming extended key, extracts required info and creates corresponding account key.
|
||||||
// Once account key is formed, that key is put (if not already) into keystore i.e. key is *encoded* into key file.
|
// Once account key is formed, that key is put (if not already) into keystore i.e. key is *encoded* into key file.
|
||||||
func importExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {
|
func importExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {
|
||||||
|
accountManager, err := GetNodeManager().AccountManager()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
// imports extended key, create key file (if necessary)
|
// imports extended key, create key file (if necessary)
|
||||||
account, err := accountManager.ImportExtendedKey(extKey, password)
|
account, err := accountManager.ImportExtendedKey(extKey, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", errextra.Wrap(err, "Account manager could not create the account")
|
return "", "", err
|
||||||
}
|
}
|
||||||
address = fmt.Sprintf("%x", account.Address)
|
address = fmt.Sprintf("%x", account.Address)
|
||||||
|
|
||||||
// obtain public key to return
|
// obtain public key to return
|
||||||
account, key, err := accountManager.AccountDecryptedKey(account, password)
|
account, key, err := accountManager.AccountDecryptedKey(account, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return address, "", errextra.Wrap(err, "Could not recover the key")
|
return address, "", err
|
||||||
}
|
}
|
||||||
pubKey = common.ToHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
pubKey = common.ToHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// createAndStartNode creates a node entity and starts the
|
|
||||||
// node running locally
|
|
||||||
func createAndStartNode(inputDir string) error {
|
|
||||||
|
|
||||||
currentNode = MakeNode(inputDir)
|
|
||||||
if currentNode != nil {
|
|
||||||
RunNode(currentNode)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("Could not create the in-memory node object")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func doAddPeer(url string) (bool, error) {
|
|
||||||
server := currentNode.Server()
|
|
||||||
if server == nil {
|
|
||||||
return false, errors.New("node not started")
|
|
||||||
}
|
|
||||||
// Try to add the url as a static peer and return
|
|
||||||
node, err := discover.ParseNode(url)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("invalid enode: %v", err)
|
|
||||||
}
|
|
||||||
server.AddPeer(node)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func onSendTransactionRequest(queuedTx status.QueuedTx) {
|
|
||||||
event := GethEvent{
|
|
||||||
Type: "sendTransactionQueued",
|
|
||||||
Event: SendTransactionEvent{
|
|
||||||
Id: string(queuedTx.Id),
|
|
||||||
Args: queuedTx.Args,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
body, _ := json.Marshal(&event)
|
|
||||||
C.GethServiceSignalEvent(C.CString(string(body)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func completeTransaction(id, password string) (common.Hash, error) {
|
|
||||||
if currentNode != nil {
|
|
||||||
if lightEthereum != nil {
|
|
||||||
backend := lightEthereum.StatusBackend
|
|
||||||
|
|
||||||
return backend.CompleteQueuedTransaction(status.QueuedTxId(id), password)
|
|
||||||
}
|
|
||||||
|
|
||||||
return common.Hash{}, errors.New("can not retrieve LES service")
|
|
||||||
}
|
|
||||||
|
|
||||||
return common.Hash{}, errors.New("can not complete transaction: no running node detected")
|
|
||||||
}
|
|
|
@ -0,0 +1,308 @@
|
||||||
|
package geth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
"github.com/status-im/status-go/geth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateChildAccount(t *testing.T) {
|
||||||
|
err := geth.PrepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accountManager, err := geth.GetNodeManager().AccountManager()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an account
|
||||||
|
address, pubKey, mnemonic, err := geth.CreateAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||||
|
|
||||||
|
account, err := utils.MakeAddress(accountManager, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("can not get account from address: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// obtain decrypted key, and make sure that extended key (which will be used as root for sub-accounts) is present
|
||||||
|
account, key, err := accountManager.AccountDecryptedKey(account, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("can not obtain decrypted account key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.ExtendedKey == nil {
|
||||||
|
t.Error("CKD#2 has not been generated for new account")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// try creating sub-account, w/o selecting main account i.e. w/o login to main account
|
||||||
|
_, _, err = geth.CreateChildAccount("", newAccountPassword)
|
||||||
|
if !reflect.DeepEqual(err, geth.ErrNoAccountSelected) {
|
||||||
|
t.Errorf("expected error is not returned (tried to create sub-account w/o login): %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = geth.SelectAccount(address, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to create sub-account with wrong password
|
||||||
|
_, _, err = geth.CreateChildAccount("", "wrong password")
|
||||||
|
if !reflect.DeepEqual(err, errors.New("cannot retreive a valid key for a given account: could not decrypt key with given passphrase")) {
|
||||||
|
t.Errorf("expected error is not returned (tried to create sub-account with wrong password): %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create sub-account (from implicit parent)
|
||||||
|
subAccount1, subPubKey1, err := geth.CreateChildAccount("", newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("cannot create sub-account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that sub-account index automatically progresses
|
||||||
|
subAccount2, subPubKey2, err := geth.CreateChildAccount("", newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("cannot create sub-account: %v", err)
|
||||||
|
}
|
||||||
|
if subAccount1 == subAccount2 || subPubKey1 == subPubKey2 {
|
||||||
|
t.Error("sub-account index auto-increament failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create sub-account (from explicit parent)
|
||||||
|
subAccount3, subPubKey3, err := geth.CreateChildAccount(subAccount2, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("cannot create sub-account: %v", err)
|
||||||
|
}
|
||||||
|
if subAccount1 == subAccount3 || subPubKey1 == subPubKey3 || subAccount2 == subAccount3 || subPubKey2 == subPubKey3 {
|
||||||
|
t.Error("sub-account index auto-increament failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecoverAccount(t *testing.T) {
|
||||||
|
err := geth.PrepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accountManager, _ := geth.GetNodeManager().AccountManager()
|
||||||
|
|
||||||
|
// create an account
|
||||||
|
address, pubKey, mnemonic, err := geth.CreateAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||||
|
|
||||||
|
// try recovering using password + mnemonic
|
||||||
|
addressCheck, pubKeyCheck, err := geth.RecoverAccount(newAccountPassword, mnemonic)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("recover account failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if address != addressCheck || pubKey != pubKeyCheck {
|
||||||
|
t.Error("recover account details failed to pull the correct details")
|
||||||
|
}
|
||||||
|
|
||||||
|
// now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device
|
||||||
|
account, err := utils.MakeAddress(accountManager, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("can not get account from address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
account, key, err := accountManager.AccountDecryptedKey(account, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("can not obtain decrypted account key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
extChild2String := key.ExtendedKey.String()
|
||||||
|
|
||||||
|
if err := accountManager.DeleteAccount(account, newAccountPassword); err != nil {
|
||||||
|
t.Errorf("cannot remove account: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addressCheck, pubKeyCheck, err = geth.RecoverAccount(newAccountPassword, mnemonic)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("recover account failed (for non-cached account): %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if address != addressCheck || pubKey != pubKeyCheck {
|
||||||
|
t.Error("recover account details failed to pull the correct details (for non-cached account)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that extended key exists and is imported ok too
|
||||||
|
account, key, err = accountManager.AccountDecryptedKey(account, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("can not obtain decrypted account key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if extChild2String != key.ExtendedKey.String() {
|
||||||
|
t.Errorf("CKD#2 key mismatch, expected: %s, got: %s", extChild2String, key.ExtendedKey.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that calling import several times, just returns from cache (no error is expected)
|
||||||
|
addressCheck, pubKeyCheck, err = geth.RecoverAccount(newAccountPassword, mnemonic)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("recover account failed (for non-cached account): %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if address != addressCheck || pubKey != pubKeyCheck {
|
||||||
|
t.Error("recover account details failed to pull the correct details (for non-cached account)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// time to login with recovered data
|
||||||
|
whisperService, err := geth.GetNodeManager().WhisperService()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("whisper service not running: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that identity is not (yet injected)
|
||||||
|
if whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKeyCheck))) {
|
||||||
|
t.Error("identity already present in whisper")
|
||||||
|
}
|
||||||
|
err = geth.SelectAccount(addressCheck, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKeyCheck))) {
|
||||||
|
t.Errorf("identity not injected into whisper: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountSelect(t *testing.T) {
|
||||||
|
|
||||||
|
err := geth.PrepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// test to see if the account was injected in whisper
|
||||||
|
whisperService, err := geth.GetNodeManager().WhisperService()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("whisper service not running: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an account
|
||||||
|
address1, pubKey1, _, err := geth.CreateAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address1, pubKey1)
|
||||||
|
|
||||||
|
address2, pubKey2, _, err := geth.CreateAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
t.Error("Test failed: could not create account")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address2, pubKey2)
|
||||||
|
|
||||||
|
// make sure that identity is not (yet injected)
|
||||||
|
if whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
||||||
|
t.Error("identity already present in whisper")
|
||||||
|
}
|
||||||
|
|
||||||
|
// try selecting with wrong password
|
||||||
|
err = geth.SelectAccount(address1, "wrongPassword")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("select account is expected to throw error: wrong password used")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = geth.SelectAccount(address1, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
||||||
|
t.Errorf("identity not injected into whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// select another account, make sure that previous account is wiped out from Whisper cache
|
||||||
|
if whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) {
|
||||||
|
t.Error("identity already present in whisper")
|
||||||
|
}
|
||||||
|
err = geth.SelectAccount(address2, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) {
|
||||||
|
t.Errorf("identity not injected into whisper: %v", err)
|
||||||
|
}
|
||||||
|
if whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
||||||
|
t.Error("identity should be removed, but it is still present in whisper")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountLogout(t *testing.T) {
|
||||||
|
|
||||||
|
err := geth.PrepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
whisperService, err := geth.GetNodeManager().WhisperService()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("whisper service not running: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an account
|
||||||
|
address, pubKey, _, err := geth.CreateAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that identity doesn't exist (yet) in Whisper
|
||||||
|
if whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey))) {
|
||||||
|
t.Error("identity already present in whisper")
|
||||||
|
}
|
||||||
|
|
||||||
|
// select/login
|
||||||
|
err = geth.SelectAccount(address, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey))) {
|
||||||
|
t.Error("identity not injected into whisper")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = geth.Logout()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("cannot logout: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now, logout and check if identity is removed indeed
|
||||||
|
if whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey))) {
|
||||||
|
t.Error("identity not cleared from whisper")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,281 @@
|
||||||
|
package geth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/les"
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/release"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/whisper"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
clientIdentifier = "Geth" // Client identifier to advertise over the network
|
||||||
|
versionMajor = 1 // Major version component of the current release
|
||||||
|
versionMinor = 5 // Minor version component of the current release
|
||||||
|
versionPatch = 0 // Patch version component of the current release
|
||||||
|
versionMeta = "unstable" // Version metadata to append to the version string
|
||||||
|
|
||||||
|
versionOracle = "0xfa7b9770ca4cb04296cac84f37736d4041251cdf" // Ethereum address of the Geth release oracle
|
||||||
|
|
||||||
|
RPCPort = 8545 // RPC port (replaced in unit tests)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDataDirPreprocessingFailed = errors.New("failed to pre-process data directory")
|
||||||
|
ErrInvalidGethNode = errors.New("no running geth node detected")
|
||||||
|
ErrInvalidAccountManager = errors.New("could not retrieve account manager")
|
||||||
|
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
|
||||||
|
ErrInvalidLightEthereumService = errors.New("can not retrieve LES service")
|
||||||
|
ErrInvalidClient = errors.New("RPC client is not properly initialized")
|
||||||
|
ErrNodeStartFailure = errors.New("could not create the in-memory node object")
|
||||||
|
)
|
||||||
|
|
||||||
|
type NodeNotificationHandler func(jsonEvent string)
|
||||||
|
|
||||||
|
type NodeManager struct {
|
||||||
|
currentNode *node.Node // currently running geth node
|
||||||
|
ctx *cli.Context // the CLI context used to start the geth node
|
||||||
|
lightEthereum *les.LightEthereum // LES service
|
||||||
|
accountManager *accounts.Manager // the account manager attached to the currentNode
|
||||||
|
SelectedAddress string // address of the account that was processed during the last call to SelectAccount()
|
||||||
|
whisperService *whisper.Whisper // Whisper service
|
||||||
|
client *rpc.ClientRestartWrapper // RPC client
|
||||||
|
notificationHandler NodeNotificationHandler // internal signal handler (used in tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
nodeManagerInstance *NodeManager
|
||||||
|
createOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewNodeManager(datadir string, rpcport int) *NodeManager {
|
||||||
|
createOnce.Do(func() {
|
||||||
|
nodeManagerInstance = &NodeManager{}
|
||||||
|
nodeManagerInstance.MakeNode(datadir, rpcport)
|
||||||
|
nodeManagerInstance.SetNotificationHandler(func(jsonEvent string) {
|
||||||
|
glog.V(logger.Info).Infof("internal notification received: %s\n", jsonEvent)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return nodeManagerInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeManager() *NodeManager {
|
||||||
|
return nodeManagerInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
// createAndStartNode creates a node entity and starts the
|
||||||
|
// node running locally
|
||||||
|
func CreateAndRunNode(datadir string, rpcport int) error {
|
||||||
|
nodeManager := NewNodeManager(datadir, rpcport)
|
||||||
|
|
||||||
|
if nodeManager.HasNode() {
|
||||||
|
nodeManager.RunNode()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrNodeStartFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeNode create a geth node entity
|
||||||
|
func (m *NodeManager) MakeNode(datadir string, rpcport int) *node.Node {
|
||||||
|
// TODO remove admin rpcapi flag
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("lightkdf", true, "Reduce key-derivation RAM & CPU usage at some expense of KDF strength")
|
||||||
|
set.Bool("shh", true, "whisper")
|
||||||
|
set.Bool("light", true, "disable eth")
|
||||||
|
set.Bool("testnet", true, "light test network")
|
||||||
|
set.Bool("rpc", true, "enable rpc")
|
||||||
|
set.String("rpcaddr", "localhost", "host for RPC")
|
||||||
|
set.Int("rpcport", rpcport, "rpc port")
|
||||||
|
set.String("rpccorsdomain", "*", "allow all domains")
|
||||||
|
set.String("verbosity", "3", "verbosity level")
|
||||||
|
set.String("rpcapi", "db,eth,net,web3,shh,personal,admin", "rpc api(s)")
|
||||||
|
set.String("datadir", datadir, "data directory for geth")
|
||||||
|
set.String("logdir", datadir, "log dir for glog")
|
||||||
|
m.ctx = cli.NewContext(nil, set, nil)
|
||||||
|
|
||||||
|
// Construct the textual version string from the individual components
|
||||||
|
vString := fmt.Sprintf("%d.%d.%d", versionMajor, versionMinor, versionPatch)
|
||||||
|
|
||||||
|
// Construct the version release oracle configuration
|
||||||
|
var rConfig release.Config
|
||||||
|
rConfig.Oracle = common.HexToAddress(versionOracle)
|
||||||
|
|
||||||
|
rConfig.Major = uint32(versionMajor)
|
||||||
|
rConfig.Minor = uint32(versionMinor)
|
||||||
|
rConfig.Patch = uint32(versionPatch)
|
||||||
|
|
||||||
|
utils.DebugSetup(m.ctx)
|
||||||
|
|
||||||
|
// create node and start requested protocols
|
||||||
|
m.currentNode = utils.MakeNode(m.ctx, clientIdentifier, vString)
|
||||||
|
utils.RegisterEthService(m.ctx, m.currentNode, rConfig, makeDefaultExtra())
|
||||||
|
|
||||||
|
// Whisper must be explicitly enabled, but is auto-enabled in --dev mode.
|
||||||
|
shhEnabled := m.ctx.GlobalBool(utils.WhisperEnabledFlag.Name)
|
||||||
|
shhAutoEnabled := !m.ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && m.ctx.GlobalIsSet(utils.DevModeFlag.Name)
|
||||||
|
if shhEnabled || shhAutoEnabled {
|
||||||
|
utils.RegisterShhService(m.currentNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.accountManager = m.currentNode.AccountManager()
|
||||||
|
|
||||||
|
return m.currentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartNode starts a geth node entity
|
||||||
|
func (m *NodeManager) RunNode() {
|
||||||
|
utils.StartNode(m.currentNode)
|
||||||
|
|
||||||
|
if m.currentNode.AccountManager() == nil {
|
||||||
|
glog.V(logger.Warn).Infoln("cannot get account manager")
|
||||||
|
}
|
||||||
|
if err := m.currentNode.Service(&m.whisperService); err != nil {
|
||||||
|
glog.V(logger.Warn).Infoln("cannot get whisper service:", err)
|
||||||
|
}
|
||||||
|
if err := m.currentNode.Service(&m.lightEthereum); err != nil {
|
||||||
|
glog.V(logger.Warn).Infoln("cannot get light ethereum service:", err)
|
||||||
|
}
|
||||||
|
m.lightEthereum.StatusBackend.SetTransactionQueueHandler(onSendTransactionRequest)
|
||||||
|
|
||||||
|
m.client = rpc.NewClientRestartWrapper(func() *rpc.Client {
|
||||||
|
client, err := m.currentNode.Attach()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
})
|
||||||
|
m.currentNode.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) AddPeer(url string) (bool, error) {
|
||||||
|
if m == nil || !m.HasNode() {
|
||||||
|
return false, ErrInvalidGethNode
|
||||||
|
}
|
||||||
|
|
||||||
|
server := m.currentNode.Server()
|
||||||
|
if server == nil {
|
||||||
|
return false, errors.New("node not started")
|
||||||
|
}
|
||||||
|
// Try to add the url as a static peer and return
|
||||||
|
parsedNode, err := discover.ParseNode(url)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("invalid enode: %v", err)
|
||||||
|
}
|
||||||
|
server.AddPeer(parsedNode)
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) HasNode() bool {
|
||||||
|
return m != nil && m.currentNode != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) HasAccountManager() bool {
|
||||||
|
return m.accountManager != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) AccountManager() (*accounts.Manager, error) {
|
||||||
|
if m == nil || !m.HasNode() {
|
||||||
|
return nil, ErrInvalidGethNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.HasAccountManager() {
|
||||||
|
return nil, ErrInvalidAccountManager
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.accountManager, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) HasWhisperService() bool {
|
||||||
|
return m.whisperService != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) WhisperService() (*whisper.Whisper, error) {
|
||||||
|
if m == nil || !m.HasNode() {
|
||||||
|
return nil, ErrInvalidGethNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.HasWhisperService() {
|
||||||
|
return nil, ErrInvalidWhisperService
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.whisperService, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) HasLightEthereumService() bool {
|
||||||
|
return m.lightEthereum != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) LightEthereumService() (*les.LightEthereum, error) {
|
||||||
|
if m == nil || !m.HasNode() {
|
||||||
|
return nil, ErrInvalidGethNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.HasLightEthereumService() {
|
||||||
|
return nil, ErrInvalidLightEthereumService
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.lightEthereum, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) HasClientRestartWrapper() bool {
|
||||||
|
return m.client != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) ClientRestartWrapper() (*rpc.ClientRestartWrapper, error) {
|
||||||
|
if m == nil || !m.HasNode() {
|
||||||
|
return nil, ErrInvalidGethNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.HasClientRestartWrapper() {
|
||||||
|
return nil, ErrInvalidClient
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) SetNotificationHandler(fn NodeNotificationHandler) {
|
||||||
|
m.notificationHandler = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NodeManager) NotificationHandler() NodeNotificationHandler {
|
||||||
|
return m.notificationHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeDefaultExtra() []byte {
|
||||||
|
var clientInfo = struct {
|
||||||
|
Version uint
|
||||||
|
Name string
|
||||||
|
GoVersion string
|
||||||
|
Os string
|
||||||
|
}{uint(versionMajor<<16 | versionMinor<<8 | versionPatch), clientIdentifier, runtime.Version(), runtime.GOOS}
|
||||||
|
extra, err := rlp.EncodeToBytes(clientInfo)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Warn).Infoln("error setting canonical miner information:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint64(len(extra)) > params.MaximumExtraDataSize.Uint64() {
|
||||||
|
glog.V(logger.Warn).Infoln("error setting canonical miner information: extra exceeds", params.MaximumExtraDataSize)
|
||||||
|
glog.V(logger.Debug).Infof("extra: %x\n", extra)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return extra
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package geth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/status-im/status-go/geth"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testAddress = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
||||||
|
testAddressPassword = "asdf"
|
||||||
|
newAccountPassword = "badpassword"
|
||||||
|
|
||||||
|
whisperMessage1 = "test message 1 (K1 -> K1)"
|
||||||
|
whisperMessage2 = "test message 2 (K1 -> '')"
|
||||||
|
whisperMessage3 = "test message 3 ('' -> '')"
|
||||||
|
whisperMessage4 = "test message 4 ('' -> K1)"
|
||||||
|
whisperMessage5 = "test message 5 (K2 -> K1)"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNodeSetup(t *testing.T) {
|
||||||
|
err := geth.PrepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
#if defined(IOS_DEPLOYMENT)
|
||||||
|
// ======================================================================================
|
||||||
|
// iOS framework compilation using xgo
|
||||||
|
// ======================================================================================
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
bool StatusServiceSignalEvent(const char *jsonEvent) {
|
||||||
|
// code for sending JSON notification up to iOS app
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(ANDROID_DEPLOYMENT)
|
||||||
|
// ======================================================================================
|
||||||
|
// Android archive compilation using xgo
|
||||||
|
// ======================================================================================
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
bool StatusServiceSignalEvent(const char *jsonEvent);
|
||||||
|
|
||||||
|
static JavaVM *gJavaVM = NULL;
|
||||||
|
static jclass JavaClassPtr_StatusService = NULL;
|
||||||
|
static jmethodID JavaMethodPtr_signalEvent = NULL;
|
||||||
|
|
||||||
|
static bool JniLibraryInit(JNIEnv *env);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Get interface to JNI.
|
||||||
|
*
|
||||||
|
* @return true if thread should be detached from JNI.
|
||||||
|
*/
|
||||||
|
static bool JniAttach(JNIEnv **env) {
|
||||||
|
jint status;
|
||||||
|
|
||||||
|
if (gJavaVM == NULL) {
|
||||||
|
env = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = (*gJavaVM)->GetEnv(gJavaVM, (void **)env, JNI_VERSION_1_6);
|
||||||
|
if (status == JNI_EDETACHED) {
|
||||||
|
// attach thread to JNI
|
||||||
|
//(*gJavaVM)->AttachCurrentThread( gJavaVM, (void **)env, NULL ); // Oracle JNI API
|
||||||
|
(*gJavaVM)->AttachCurrentThread(gJavaVM, env, NULL); // Android JNI API
|
||||||
|
return true;
|
||||||
|
} else if (status != JNI_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief The VM calls JNI_OnLoad when the native library is loaded.
|
||||||
|
*/
|
||||||
|
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||||
|
bool detach;
|
||||||
|
JNIEnv *env;
|
||||||
|
int result = JNI_VERSION_1_6;
|
||||||
|
|
||||||
|
gJavaVM = vm;
|
||||||
|
|
||||||
|
// attach thread to JNI
|
||||||
|
detach = JniAttach(&env);
|
||||||
|
if (env == NULL) {
|
||||||
|
// failed
|
||||||
|
gJavaVM = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!JniLibraryInit(env)) {
|
||||||
|
// fail loading of JNI library
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detach) {
|
||||||
|
// detach thread from JNI
|
||||||
|
(*gJavaVM)->DetachCurrentThread(gJavaVM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != JNI_VERSION_1_6) {
|
||||||
|
gJavaVM = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Initialize library.
|
||||||
|
*/
|
||||||
|
bool JniLibraryInit(JNIEnv *env) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
JavaClassPtr_StatusService = (*env)->FindClass(env, "com/statusim/module/StatusService");
|
||||||
|
if (JavaClassPtr_StatusService == NULL) return false;
|
||||||
|
|
||||||
|
JavaClassPtr_StatusService = (jclass)(*env)->NewGlobalRef(env, JavaClassPtr_StatusService);
|
||||||
|
if (JavaClassPtr_StatusService == NULL) return false;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool bStatic;
|
||||||
|
jclass classPtr;
|
||||||
|
jmethodID *methodPtr;
|
||||||
|
const char *methodId;
|
||||||
|
const char *params;
|
||||||
|
} javaMethodDescriptors[] = {
|
||||||
|
{
|
||||||
|
true,
|
||||||
|
JavaClassPtr_StatusService,
|
||||||
|
&JavaMethodPtr_signalEvent, // &JavaMethodPtr_someNonStaticMethod
|
||||||
|
"signalEvent", // someNonStaticMethod
|
||||||
|
"(Ljava/lang/String;)V"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (i = 0; i < sizeof(javaMethodDescriptors) / sizeof(javaMethodDescriptors[0]); i++) {
|
||||||
|
if (javaMethodDescriptors[i].bStatic) {
|
||||||
|
*(javaMethodDescriptors[i].methodPtr) = (*env)->GetStaticMethodID(
|
||||||
|
env, javaMethodDescriptors[i].classPtr, javaMethodDescriptors[i].methodId, javaMethodDescriptors[i].params);
|
||||||
|
} else {
|
||||||
|
*(javaMethodDescriptors[i].methodPtr) = (*env)->GetMethodID(
|
||||||
|
env, javaMethodDescriptors[i].classPtr, javaMethodDescriptors[i].methodId, javaMethodDescriptors[i].params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*(javaMethodDescriptors[i].methodPtr) == NULL) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Calls static method signalEvent of class com.statusim.module.StatusService.
|
||||||
|
*
|
||||||
|
* @param jsonEvent - UTF8 string
|
||||||
|
*/
|
||||||
|
bool StatusServiceSignalEvent(const char *jsonEvent) {
|
||||||
|
bool detach;
|
||||||
|
JNIEnv *env;
|
||||||
|
|
||||||
|
// attach thread to JNI
|
||||||
|
detach = JniAttach( &env );
|
||||||
|
if (env == NULL) { // failed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring javaJsonEvent = NULL;
|
||||||
|
if (jsonEvent != NULL) {
|
||||||
|
javaJsonEvent = (*env)->NewStringUTF(env, jsonEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
(*env)->CallStaticVoidMethod(env, JavaClassPtr_StatusService, JavaMethodPtr_signalEvent, javaJsonEvent);
|
||||||
|
|
||||||
|
if (javaJsonEvent != NULL) (*env)->DeleteLocalRef(env, javaJsonEvent);
|
||||||
|
|
||||||
|
if (detach) { // detach thread from JNI
|
||||||
|
(*gJavaVM)->DetachCurrentThread(gJavaVM);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// ======================================================================================
|
||||||
|
// cgo compilation (for local tests)
|
||||||
|
// ======================================================================================
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
|
bool StatusServiceSignalEvent(const char *jsonEvent) {
|
||||||
|
NotifyNode((char *)jsonEvent); // re-send notification back to status node
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,43 @@
|
||||||
|
package geth
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
extern bool StatusServiceSignalEvent( const char *jsonEvent );
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/les/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventTransactionQueued = "transaction.queued"
|
||||||
|
)
|
||||||
|
|
||||||
|
func onSendTransactionRequest(queuedTx status.QueuedTx) {
|
||||||
|
event := GethEvent{
|
||||||
|
Type: EventTransactionQueued,
|
||||||
|
Event: SendTransactionEvent{
|
||||||
|
Id: string(queuedTx.Id),
|
||||||
|
Args: queuedTx.Args,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(&event)
|
||||||
|
C.StatusServiceSignalEvent(C.CString(string(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompleteTransaction(id, password string) (common.Hash, error) {
|
||||||
|
lightEthereum, err := GetNodeManager().LightEthereumService()
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
backend := lightEthereum.StatusBackend
|
||||||
|
|
||||||
|
return backend.CompleteQueuedTransaction(status.QueuedTxId(id), password)
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
package geth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/les/status"
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/status-im/status-go/geth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQueuedTransactions(t *testing.T) {
|
||||||
|
err := geth.PrepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accountManager, err := geth.GetNodeManager().AccountManager()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an account
|
||||||
|
address, _, _, err := geth.CreateAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// test transaction queueing
|
||||||
|
lightEthereum, err := geth.GetNodeManager().LightEthereumService()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
backend := lightEthereum.StatusBackend
|
||||||
|
|
||||||
|
// make sure you panic if transaction complete doesn't return
|
||||||
|
completeQueuedTransaction := make(chan bool, 1)
|
||||||
|
geth.PanicAfter(20*time.Second, completeQueuedTransaction)
|
||||||
|
|
||||||
|
// replace transaction notification handler
|
||||||
|
var txHash = common.Hash{}
|
||||||
|
geth.GetNodeManager().SetNotificationHandler(func(jsonEvent string) {
|
||||||
|
var envelope geth.GethEvent
|
||||||
|
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{})
|
||||||
|
glog.V(logger.Info).Infof("Transaction queued (will be completed in 5 secs): {id: %s}\n", event["id"].(string))
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testAddressPassword); err != nil {
|
||||||
|
t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(logger.Info).Infof("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
||||||
|
completeQueuedTransaction <- true // so that timeout is aborted
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// try completing non-existing transaction
|
||||||
|
if _, err := geth.CompleteTransaction("some-bad-transaction-id", testAddressPassword); err == nil {
|
||||||
|
t.Error("error expected and not recieved")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// send normal transaction
|
||||||
|
from, err := utils.MakeAddress(accountManager, testAddress)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not retrieve account from address: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to, err := utils.MakeAddress(accountManager, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not retrieve account from address: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// this call blocks, up until Complete Transaction is called
|
||||||
|
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||||
|
From: from.Address,
|
||||||
|
To: &to.Address,
|
||||||
|
Value: rpc.NewHexNumber(big.NewInt(1000000000000)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: cannot send transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(txHash, txHashCheck) {
|
||||||
|
t.Error("Transaction hash returned from SendTransaction is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||||
|
t.Error("Test failed: transaction was never queued or completed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// now test eviction queue
|
||||||
|
txQueue := backend.TransactionQueue()
|
||||||
|
var i = 0
|
||||||
|
backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) {
|
||||||
|
//glog.V(logger.Info).Infof("%d. Transaction queued (queue size: %d): {id: %v}\n", i, txQueue.Count(), queuedTx.Id)
|
||||||
|
i++
|
||||||
|
})
|
||||||
|
|
||||||
|
if txQueue.Count() != 0 {
|
||||||
|
t.Errorf("transaction count should be zero: %d", txQueue.Count())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go backend.SendTransaction(nil, status.SendTxArgs{})
|
||||||
|
}
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
t.Logf("Number of transactions queued: %d. Queue size (shouldn't be more than %d): %d", i, status.DefaultTxQueueCap, txQueue.Count())
|
||||||
|
|
||||||
|
if txQueue.Count() != 10 {
|
||||||
|
t.Errorf("transaction count should be 10: got %d", txQueue.Count())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < status.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
|
||||||
|
go backend.SendTransaction(nil, status.SendTxArgs{})
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
if txQueue.Count() != status.DefaultTxQueueCap && txQueue.Count() != (status.DefaultTxQueueCap-1) {
|
||||||
|
t.Errorf("transaction count should be %d (or %d): got %d", status.DefaultTxQueueCap, status.DefaultTxQueueCap-1, txQueue.Count())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package common
|
package geth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/les/status"
|
"github.com/ethereum/go-ethereum/les/status"
|
||||||
|
@ -35,7 +35,7 @@ type WhisperMessageEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendTransactionEvent struct {
|
type SendTransactionEvent struct {
|
||||||
Id string `json:"hash"`
|
Id string `json:"id"`
|
||||||
Args status.SendTxArgs `json:"args"`
|
Args status.SendTxArgs `json:"args"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
package geth
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var muPrepareTestNode sync.Mutex
|
||||||
|
|
||||||
|
const (
|
||||||
|
testDataDir = "../.ethereumtest"
|
||||||
|
testNodeSyncSeconds = 300
|
||||||
|
)
|
||||||
|
|
||||||
|
//export NotifyNode
|
||||||
|
func NotifyNode(jsonEvent *C.char) {
|
||||||
|
GetNodeManager().NotificationHandler()(C.GoString(jsonEvent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyFile(dst, src string) error {
|
||||||
|
s, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
d, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(d, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFromFile is usefull for loading test data, from testdata/filename into a variable
|
||||||
|
func LoadFromFile(filename string) string {
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
io.Copy(buf, f)
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
return string(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrepareTestNode() (err error) {
|
||||||
|
muPrepareTestNode.Lock()
|
||||||
|
defer muPrepareTestNode.Unlock()
|
||||||
|
|
||||||
|
manager := GetNodeManager()
|
||||||
|
if manager.HasNode() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
syncRequired := false
|
||||||
|
if _, err := os.Stat(testDataDir); os.IsNotExist(err) {
|
||||||
|
syncRequired = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare node directory
|
||||||
|
dataDir, err := PreprocessDataDir(testDataDir)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Warn).Infoln("make node failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// import test account (with test ether on it)
|
||||||
|
dst := filepath.Join(testDataDir, "testnet", "keystore", "test-account.pk")
|
||||||
|
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
||||||
|
err = CopyFile(dst, filepath.Join("../data", "test-account.pk"))
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Warn).Infof("cannot copy test account PK: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start geth node and wait for it to initialize
|
||||||
|
// internally once.Do() is used, so call below is thread-safe
|
||||||
|
go CreateAndRunNode(dataDir, 8546) // to avoid conflicts with running react-native app, run on different port
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
manager = GetNodeManager()
|
||||||
|
if !manager.HasNode() {
|
||||||
|
panic("could not obtain geth node")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.AddPeer("enode://409772c7dea96fa59a912186ad5bcdb5e51b80556b3fe447d940f99d9eaadb51d4f0ffedb68efad232b52475dd7bd59b51cee99968b3cc79e2d5684b33c4090c@139.162.166.59:30303")
|
||||||
|
|
||||||
|
if syncRequired {
|
||||||
|
glog.V(logger.Warn).Infof("Sync is required, it will take %d seconds", testNodeSyncSeconds)
|
||||||
|
time.Sleep(testNodeSyncSeconds * time.Second) // LES syncs headers, so that we are up do date when it is done
|
||||||
|
} else {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveTestNode() {
|
||||||
|
err := os.RemoveAll(testDataDir)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Warn).Infof("could not clean up temporary datadir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PreprocessDataDir(dataDir string) (string, error) {
|
||||||
|
testDataDir := path.Join(dataDir, "testnet", "keystore")
|
||||||
|
if _, err := os.Stat(testDataDir); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(testDataDir, 0755); err != nil {
|
||||||
|
return dataDir, ErrDataDirPreprocessingFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy over static peer nodes list (LES auto-discovery is not stable yet)
|
||||||
|
dst := filepath.Join(dataDir, "testnet", "static-nodes.json")
|
||||||
|
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
||||||
|
src := filepath.Join("../data", "static-nodes.json")
|
||||||
|
if err := CopyFile(dst, src); err != nil {
|
||||||
|
return dataDir, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicAfter throws panic() after waitSeconds, unless abort channel receives notification
|
||||||
|
func PanicAfter(waitSeconds time.Duration, abort chan bool) {
|
||||||
|
// panic if function takes too long
|
||||||
|
timeout := make(chan bool, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(waitSeconds)
|
||||||
|
timeout <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-abort:
|
||||||
|
return
|
||||||
|
case <-timeout:
|
||||||
|
panic("function takes to long, which generally means we are stuck")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package geth
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
extern bool StatusServiceSignalEvent( const char *jsonEvent );
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/whisper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
whisperFilters []int
|
||||||
|
)
|
||||||
|
|
||||||
|
func onWhisperMessage(message *whisper.Message) {
|
||||||
|
event := GethEvent{
|
||||||
|
Type: "whisper",
|
||||||
|
Event: WhisperMessageEvent{
|
||||||
|
Payload: string(message.Payload),
|
||||||
|
From: common.ToHex(crypto.FromECDSAPub(message.Recover())),
|
||||||
|
To: common.ToHex(crypto.FromECDSAPub(message.To)),
|
||||||
|
Sent: message.Sent.Unix(),
|
||||||
|
TTL: int64(message.TTL / time.Second),
|
||||||
|
Hash: common.ToHex(message.Hash.Bytes()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(&event)
|
||||||
|
C.StatusServiceSignalEvent(C.CString(string(body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddWhisperFilter(args whisper.NewFilterArgs) int {
|
||||||
|
whisperService, err := GetNodeManager().WhisperService()
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := whisper.Filter{
|
||||||
|
To: crypto.ToECDSAPub(common.FromHex(args.To)),
|
||||||
|
From: crypto.ToECDSAPub(common.FromHex(args.From)),
|
||||||
|
Topics: whisper.NewFilterTopics(args.Topics...),
|
||||||
|
Fn: onWhisperMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
id := whisperService.Watch(filter)
|
||||||
|
whisperFilters = append(whisperFilters, id)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveWhisperFilter(idFilter int) {
|
||||||
|
whisperService, err := GetNodeManager().WhisperService()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
whisperService.Unwatch(idFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearWhisperFilters() {
|
||||||
|
for _, idFilter := range whisperFilters {
|
||||||
|
RemoveWhisperFilter(idFilter)
|
||||||
|
}
|
||||||
|
whisperFilters = nil
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
package geth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
"github.com/ethereum/go-ethereum/whisper"
|
||||||
|
"github.com/status-im/status-go/geth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWhisperMessaging(t *testing.T) {
|
||||||
|
err := geth.PrepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
whisperService, err := geth.GetNodeManager().WhisperService()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("whisper service not running: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whisperAPI := whisper.NewPublicWhisperAPI(whisperService)
|
||||||
|
|
||||||
|
// prepare message
|
||||||
|
postArgs := whisper.PostArgs{
|
||||||
|
From: "",
|
||||||
|
To: "",
|
||||||
|
TTL: 10,
|
||||||
|
Topics: [][]byte{[]byte("test topic")},
|
||||||
|
Payload: "test message",
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an accounts
|
||||||
|
address1, pubKey1, _, err := geth.CreateAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
t.Error("Test failed: could not create account")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address1, pubKey1)
|
||||||
|
|
||||||
|
address2, pubKey2, _, err := geth.CreateAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
t.Error("Test failed: could not create account")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address2, pubKey2)
|
||||||
|
|
||||||
|
// start watchers
|
||||||
|
var receivedMessages = map[string]bool{
|
||||||
|
whisperMessage1: false,
|
||||||
|
whisperMessage2: false,
|
||||||
|
whisperMessage3: false,
|
||||||
|
whisperMessage4: false,
|
||||||
|
whisperMessage5: false,
|
||||||
|
}
|
||||||
|
whisperService.Watch(whisper.Filter{
|
||||||
|
//From: crypto.ToECDSAPub(common.FromHex(pubKey1)),
|
||||||
|
//To: crypto.ToECDSAPub(common.FromHex(pubKey2)),
|
||||||
|
Fn: func(msg *whisper.Message) {
|
||||||
|
//glog.V(logger.Info).Infof("Whisper message received: %s", msg.Payload)
|
||||||
|
receivedMessages[string(msg.Payload)] = true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// inject key of newly created account into Whisper, as identity
|
||||||
|
if whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
||||||
|
t.Error("identity already present in whisper")
|
||||||
|
}
|
||||||
|
err = geth.SelectAccount(address1, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
identitySucceess := whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1)))
|
||||||
|
if !identitySucceess || err != nil {
|
||||||
|
t.Errorf("identity not injected into whisper: %v", err)
|
||||||
|
}
|
||||||
|
if whisperService.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) { // ensure that second id is not injected
|
||||||
|
t.Error("identity already present in whisper")
|
||||||
|
}
|
||||||
|
|
||||||
|
// double selecting (shouldn't be a problem)
|
||||||
|
err = geth.SelectAccount(address1, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 0: From != nil && To != nil: encrypted signed message (but we cannot decrypt it - so watchers will not report this)
|
||||||
|
postArgs.From = pubKey1
|
||||||
|
postArgs.To = pubKey2 // owner of that public key will be able to decrypt it
|
||||||
|
postSuccess, err := whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 1: From != nil && To != nil: encrypted signed message (to self)
|
||||||
|
postArgs.From = pubKey1
|
||||||
|
postArgs.To = pubKey1
|
||||||
|
postArgs.Payload = whisperMessage1
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send from account that is not in Whisper identity list
|
||||||
|
postArgs.From = pubKey2
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if err == nil || err.Error() != fmt.Sprintf("unknown identity to send from: %s", pubKey2) {
|
||||||
|
t.Error("expected error not voiced: we are sending from non-injected whisper identity")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 2: From != nil && To == nil: signed broadcast (known sender)
|
||||||
|
postArgs.From = pubKey1
|
||||||
|
postArgs.To = ""
|
||||||
|
postArgs.Payload = whisperMessage2
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 3: From == nil && To == nil: anonymous broadcast
|
||||||
|
postArgs.From = ""
|
||||||
|
postArgs.To = ""
|
||||||
|
postArgs.Payload = whisperMessage3
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 4: From == nil && To != nil: encrypted anonymous message
|
||||||
|
postArgs.From = ""
|
||||||
|
postArgs.To = pubKey1
|
||||||
|
postArgs.Payload = whisperMessage4
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 5: From != nil && To != nil: encrypted and signed response
|
||||||
|
postArgs.From = ""
|
||||||
|
postArgs.To = pubKey1
|
||||||
|
postArgs.Payload = whisperMessage5
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second) // allow whisper to poll
|
||||||
|
for message, status := range receivedMessages {
|
||||||
|
if !status {
|
||||||
|
t.Errorf("Expected message not received: %s", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,259 @@
|
||||||
|
package jail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
"github.com/status-im/status-go/geth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidJail = errors.New("jail environment is not properly initialized")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Jail struct {
|
||||||
|
client *rpc.ClientRestartWrapper // lazy inited on the first call to jail.ClientRestartWrapper()
|
||||||
|
VMs map[string]*otto.Otto
|
||||||
|
statusJS string
|
||||||
|
}
|
||||||
|
|
||||||
|
var jailInstance *Jail
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
func New() *Jail {
|
||||||
|
once.Do(func() {
|
||||||
|
jailInstance = &Jail{
|
||||||
|
VMs: make(map[string]*otto.Otto),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return jailInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(js string) *Jail {
|
||||||
|
jailInstance = New() // singleton, we will always get the same reference
|
||||||
|
jailInstance.statusJS = js
|
||||||
|
|
||||||
|
return jailInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetInstance() *Jail {
|
||||||
|
return New() // singleton, we will always get the same reference
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jail *Jail) Parse(chatId string, js string) string {
|
||||||
|
if jail == nil {
|
||||||
|
return printError(ErrInvalidJail.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
vm := otto.New()
|
||||||
|
initJjs := jail.statusJS + ";"
|
||||||
|
jail.VMs[chatId] = vm
|
||||||
|
_, err := vm.Run(initJjs)
|
||||||
|
vm.Set("jeth", struct{}{})
|
||||||
|
|
||||||
|
jethObj, _ := vm.Get("jeth")
|
||||||
|
jethObj.Object().Set("send", jail.Send)
|
||||||
|
jethObj.Object().Set("sendAsync", jail.Send)
|
||||||
|
|
||||||
|
jjs := Web3_JS + `
|
||||||
|
var Web3 = require('web3');
|
||||||
|
var web3 = new Web3(jeth);
|
||||||
|
var Bignumber = require("bignumber.js");
|
||||||
|
function bn(val){
|
||||||
|
return new Bignumber(val);
|
||||||
|
}
|
||||||
|
` + js + "; var catalog = JSON.stringify(_status_catalog);"
|
||||||
|
vm.Run(jjs)
|
||||||
|
|
||||||
|
res, _ := vm.Get("catalog")
|
||||||
|
|
||||||
|
return printResult(res.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jail *Jail) Call(chatId string, path string, args string) string {
|
||||||
|
_, err := jail.ClientRestartWrapper()
|
||||||
|
if err != nil {
|
||||||
|
return printError(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
vm, ok := jail.VMs[chatId]
|
||||||
|
if !ok {
|
||||||
|
return printError(fmt.Sprintf("VM[%s] doesn't exist.", chatId))
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := vm.Call("call", nil, path, args)
|
||||||
|
|
||||||
|
return printResult(res.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jail *Jail) GetVM(chatId string) (*otto.Otto, error) {
|
||||||
|
if jail == nil {
|
||||||
|
return nil, ErrInvalidJail
|
||||||
|
}
|
||||||
|
|
||||||
|
vm, ok := jail.VMs[chatId]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("VM[%s] doesn't exist.", chatId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonrpcCall struct {
|
||||||
|
Id int64
|
||||||
|
Method string
|
||||||
|
Params []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send will serialize the first argument, send it to the node and returns the response.
|
||||||
|
func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
|
||||||
|
clientFactory, err := jail.ClientRestartWrapper()
|
||||||
|
if err != nil {
|
||||||
|
return newErrorResponse(call, -32603, err.Error(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remarshal the request into a Go value.
|
||||||
|
JSON, _ := call.Otto.Object("JSON")
|
||||||
|
reqVal, err := JSON.Call("stringify", call.Argument(0))
|
||||||
|
if err != nil {
|
||||||
|
throwJSException(err.Error())
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
rawReq = []byte(reqVal.String())
|
||||||
|
reqs []jsonrpcCall
|
||||||
|
batch bool
|
||||||
|
)
|
||||||
|
if rawReq[0] == '[' {
|
||||||
|
batch = true
|
||||||
|
json.Unmarshal(rawReq, &reqs)
|
||||||
|
} else {
|
||||||
|
batch = false
|
||||||
|
reqs = make([]jsonrpcCall, 1)
|
||||||
|
json.Unmarshal(rawReq, &reqs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the requests.
|
||||||
|
resps, _ := call.Otto.Object("new Array()")
|
||||||
|
for _, req := range reqs {
|
||||||
|
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
|
||||||
|
resp.Set("id", req.Id)
|
||||||
|
var result json.RawMessage
|
||||||
|
|
||||||
|
client := clientFactory.Client()
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
errc2 := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errc2 <- <-errc
|
||||||
|
}()
|
||||||
|
errc <- client.Call(&result, req.Method, req.Params...)
|
||||||
|
err = <-errc2
|
||||||
|
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
if result == nil {
|
||||||
|
// Special case null because it is decoded as an empty
|
||||||
|
// raw message for some reason.
|
||||||
|
resp.Set("result", otto.NullValue())
|
||||||
|
} else {
|
||||||
|
resultVal, err := JSON.Call("parse", string(result))
|
||||||
|
if err != nil {
|
||||||
|
resp = newErrorResponse(call, -32603, err.Error(), &req.Id).Object()
|
||||||
|
} else {
|
||||||
|
resp.Set("result", resultVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case rpc.Error:
|
||||||
|
resp.Set("error", map[string]interface{}{
|
||||||
|
"code": err.ErrorCode(),
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
resp = newErrorResponse(call, -32603, err.Error(), &req.Id).Object()
|
||||||
|
}
|
||||||
|
resps.Call("push", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the responses either to the callback (if supplied)
|
||||||
|
// or directly as the return value.
|
||||||
|
if batch {
|
||||||
|
response = resps.Value()
|
||||||
|
} else {
|
||||||
|
response, _ = resps.Get("0")
|
||||||
|
}
|
||||||
|
if fn := call.Argument(1); fn.Class() == "Function" {
|
||||||
|
fn.Call(otto.NullValue(), otto.NullValue(), response)
|
||||||
|
return otto.UndefinedValue()
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jail *Jail) ClientRestartWrapper() (*rpc.ClientRestartWrapper, error) {
|
||||||
|
if jail == nil {
|
||||||
|
return nil, ErrInvalidJail
|
||||||
|
}
|
||||||
|
|
||||||
|
if jail.client != nil {
|
||||||
|
return jail.client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeManager := geth.GetNodeManager()
|
||||||
|
if !nodeManager.HasNode() {
|
||||||
|
return nil, geth.ErrInvalidGethNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// obtain RPC client from running node
|
||||||
|
client, err := nodeManager.ClientRestartWrapper()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jail.client = client
|
||||||
|
|
||||||
|
return jail.client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface{}) otto.Value {
|
||||||
|
// Bundle the error into a JSON RPC call response
|
||||||
|
m := map[string]interface{}{"version": "2.0", "id": id, "error": map[string]interface{}{"code": code, msg: msg}}
|
||||||
|
res, _ := json.Marshal(m)
|
||||||
|
val, _ := call.Otto.Run("(" + string(res) + ")")
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// throwJSException panics on an otto.Value. The Otto VM will recover from the
|
||||||
|
// Go panic and throw msg as a JavaScript error.
|
||||||
|
func throwJSException(msg interface{}) otto.Value {
|
||||||
|
val, err := otto.ToValue(msg)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Error).Infof("Failed to serialize JavaScript exception %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
panic(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printError(error string) string {
|
||||||
|
str := geth.JSONError{
|
||||||
|
Error: error,
|
||||||
|
}
|
||||||
|
outBytes, _ := json.Marshal(&str)
|
||||||
|
return string(outBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printResult(res string, err error) string {
|
||||||
|
var out string
|
||||||
|
if err != nil {
|
||||||
|
out = printError(err.Error())
|
||||||
|
} else {
|
||||||
|
if "undefined" == res {
|
||||||
|
res = "null"
|
||||||
|
}
|
||||||
|
out = fmt.Sprintf(`{"result": %s}`, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
package jail_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/geth"
|
||||||
|
"github.com/status-im/status-go/jail"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TEST_ADDRESS = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
||||||
|
CHAT_ID_INIT = "CHAT_ID_INIT_TEST"
|
||||||
|
CHAT_ID_CALL = "CHAT_ID_CALL_TEST"
|
||||||
|
CHAT_ID_NON_EXISTENT = "CHAT_IDNON_EXISTENT"
|
||||||
|
|
||||||
|
TESTDATA_STATUS_JS = "testdata/status.js"
|
||||||
|
)
|
||||||
|
|
||||||
|
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(CHAT_ID_CALL, ``)
|
||||||
|
if response != expectedError {
|
||||||
|
t.Errorf("error expected, but got: %v", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response = jailInstance.Call(CHAT_ID_CALL, `["commands", "testCommand"]`, `{"val": 12}`)
|
||||||
|
if response != expectedError {
|
||||||
|
t.Errorf("error expected, but got: %v", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := jailInstance.GetVM(CHAT_ID_CALL)
|
||||||
|
if err != jail.ErrInvalidJail {
|
||||||
|
t.Errorf("error expected, but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = jailInstance.ClientRestartWrapper()
|
||||||
|
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(TESTDATA_STATUS_JS) + `;
|
||||||
|
_status_catalog.commands["testCommand"] = function (params) {
|
||||||
|
return params.val * params.val;
|
||||||
|
};`
|
||||||
|
response = jailInstance.Parse(CHAT_ID_CALL, 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(CHAT_ID_CALL, `["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(CHAT_ID_CALL, `["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(CHAT_ID_INIT, 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(TESTDATA_STATUS_JS) + `;
|
||||||
|
_status_catalog.commands["testCommand"] = function (params) {
|
||||||
|
return params.val * params.val;
|
||||||
|
};`
|
||||||
|
jailInstance.Parse(CHAT_ID_CALL, statusJS)
|
||||||
|
|
||||||
|
// call with wrong chat id
|
||||||
|
response := jailInstance.Call(CHAT_ID_NON_EXISTENT, "", "")
|
||||||
|
expectedError := `{"error":"VM[CHAT_IDNON_EXISTENT] 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(CHAT_ID_CALL, `["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(TESTDATA_STATUS_JS)
|
||||||
|
jailInstance.Parse(CHAT_ID_CALL, statusJS)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = vm.Run(`
|
||||||
|
var data = {"jsonrpc":"2.0","method":"eth_getBalance","params":["` + TEST_ADDRESS + `", "latest"],"id":1};
|
||||||
|
var sendResult = web3.currentProvider.send(data)
|
||||||
|
console.log(JSON.stringify(sendResult))
|
||||||
|
var sendResult = web3.fromWei(sendResult.result, "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 < 90 || 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, TEST_ADDRESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := `VM[` + CHAT_ID_NON_EXISTENT + `] doesn't exist.`
|
||||||
|
_, err = jailInstance.GetVM(CHAT_ID_NON_EXISTENT)
|
||||||
|
if err == nil || err.Error() != expectedError {
|
||||||
|
t.Error("expected error, but call succeeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// now let's create VM..
|
||||||
|
jailInstance.Parse(CHAT_ID_CALL, ``)
|
||||||
|
// ..and see if VM becomes available
|
||||||
|
_, err = jailInstance.GetVM(CHAT_ID_CALL)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
status.command({
|
||||||
|
name: "location",
|
||||||
|
description: "Send location",
|
||||||
|
color: "#9a5dcf",
|
||||||
|
preview: function (params) {
|
||||||
|
var text = status.components.text(
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
marginTop: 5,
|
||||||
|
marginHorizontal: 0,
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: "font",
|
||||||
|
color: "black"
|
||||||
|
}
|
||||||
|
}, params.value);
|
||||||
|
var uri = "https://maps.googleapis.com/maps/api/staticmap?center="
|
||||||
|
+ params.value
|
||||||
|
+ "&size=100x100&maptype=roadmap&key=AIzaSyBNsj1qoQEYPb3IllmWMAscuXW0eeuYqAA&language=en"
|
||||||
|
+ "&markers=size:mid%7Ccolor:0xff0000%7Clabel:%7C"
|
||||||
|
+ params.value;
|
||||||
|
|
||||||
|
var image = status.components.image(
|
||||||
|
{
|
||||||
|
source: {uri: uri},
|
||||||
|
style: {
|
||||||
|
width: 100,
|
||||||
|
height: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return status.components.view({}, [text, image]);
|
||||||
|
}
|
||||||
|
}).param({
|
||||||
|
name: "address",
|
||||||
|
type: status.types.TEXT,
|
||||||
|
placeholder: "Address"
|
||||||
|
});
|
||||||
|
|
||||||
|
var phones = [
|
||||||
|
{
|
||||||
|
number: "89171111111",
|
||||||
|
description: "Number format 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "89371111111",
|
||||||
|
description: "Number format 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "+79171111111",
|
||||||
|
description: "Number format 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "9171111111",
|
||||||
|
description: "Number format 3"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function suggestionsContainerStyle(suggestionsCount) {
|
||||||
|
return {
|
||||||
|
marginVertical: 1,
|
||||||
|
marginHorizontal: 0,
|
||||||
|
height: Math.min(150, (56 * suggestionsCount)),
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderRadius: 5
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var suggestionContainerStyle = {
|
||||||
|
paddingLeft: 16,
|
||||||
|
backgroundColor: "white"
|
||||||
|
};
|
||||||
|
|
||||||
|
var suggestionSubContainerStyle = {
|
||||||
|
height: 56,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: "#0000001f"
|
||||||
|
};
|
||||||
|
|
||||||
|
var valueStyle = {
|
||||||
|
marginTop: 9,
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: "font",
|
||||||
|
color: "#000000de"
|
||||||
|
};
|
||||||
|
|
||||||
|
var descriptionStyle = {
|
||||||
|
marginTop: 1.5,
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: "font",
|
||||||
|
color: "#838c93de"
|
||||||
|
};
|
||||||
|
|
||||||
|
function startsWith(str1, str2) {
|
||||||
|
// String.startsWith(...) doesn't work in otto
|
||||||
|
return str1.lastIndexOf(str2, 0) == 0 && str1 != str2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function phoneSuggestions(params) {
|
||||||
|
var ph, suggestions;
|
||||||
|
if (!params.value || params.value == "") {
|
||||||
|
ph = phones;
|
||||||
|
} else {
|
||||||
|
ph = phones.filter(function (phone) {
|
||||||
|
return startsWith(phone.number, params.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ph.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestions = ph.map(function (phone) {
|
||||||
|
return status.components.touchable(
|
||||||
|
{onPress: [status.events.SET_VALUE, phone.number]},
|
||||||
|
status.components.view(suggestionContainerStyle,
|
||||||
|
[status.components.view(suggestionSubContainerStyle,
|
||||||
|
[
|
||||||
|
status.components.text(
|
||||||
|
{style: valueStyle},
|
||||||
|
phone.number
|
||||||
|
),
|
||||||
|
status.components.text(
|
||||||
|
{style: descriptionStyle},
|
||||||
|
phone.description
|
||||||
|
)
|
||||||
|
])])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
var view = status.components.scrollView(
|
||||||
|
suggestionsContainerStyle(ph.length),
|
||||||
|
suggestions
|
||||||
|
);
|
||||||
|
|
||||||
|
return {markup: view};
|
||||||
|
}
|
||||||
|
|
||||||
|
var phoneConfig = {
|
||||||
|
name: "phone",
|
||||||
|
description: "Send phone number",
|
||||||
|
color: "#5fc48d",
|
||||||
|
validator: function (params) {
|
||||||
|
return {
|
||||||
|
validationHandler: "phone",
|
||||||
|
parameters: [params.value]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
params: [{
|
||||||
|
name: "phone",
|
||||||
|
type: status.types.PHONE,
|
||||||
|
suggestions: phoneSuggestions,
|
||||||
|
placeholder: "Phone number"
|
||||||
|
}],
|
||||||
|
handler: function (params) {
|
||||||
|
return {
|
||||||
|
event: "sign-up",
|
||||||
|
params: [params.value]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
status.response(phoneConfig);
|
||||||
|
status.command(phoneConfig);
|
||||||
|
|
||||||
|
status.command({
|
||||||
|
name: "help",
|
||||||
|
description: "Help",
|
||||||
|
color: "#7099e6",
|
||||||
|
/* Validator example
|
||||||
|
validator: function (params) {
|
||||||
|
if (params.value != "3") {
|
||||||
|
var error = status.components.view(
|
||||||
|
{backgroundColor: "red"},
|
||||||
|
[status.components.text({}, "ooops :(")]
|
||||||
|
);
|
||||||
|
return {errors: [error]}
|
||||||
|
}
|
||||||
|
},*/
|
||||||
|
params: [{
|
||||||
|
name: "query",
|
||||||
|
type: status.types.TEXT
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
status.response({
|
||||||
|
name: "confirmation-code",
|
||||||
|
color: "#7099e6",
|
||||||
|
description: "Confirmation code",
|
||||||
|
params: [{
|
||||||
|
name: "code",
|
||||||
|
type: status.types.NUMBER
|
||||||
|
}],
|
||||||
|
handler: function (params) {
|
||||||
|
return {
|
||||||
|
event: "confirm-sign-up",
|
||||||
|
params: [params.value]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
validator: function(params){
|
||||||
|
if(!/^[\d]{4}$/.test(params.value)){
|
||||||
|
var error = status.components.validationMessage(
|
||||||
|
"Confirmation code",
|
||||||
|
"Wrong format"
|
||||||
|
);
|
||||||
|
|
||||||
|
return {errors: [error]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
status.response({
|
||||||
|
name: "keypair",
|
||||||
|
color: "#7099e6",
|
||||||
|
description: "Keypair password",
|
||||||
|
icon: "icon_lock_white",
|
||||||
|
params: [{
|
||||||
|
name: "password",
|
||||||
|
type: status.types.PASSWORD
|
||||||
|
}],
|
||||||
|
handler: function (params) {
|
||||||
|
return {
|
||||||
|
event: "save-password",
|
||||||
|
params: [params.value]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
preview: function (params) {
|
||||||
|
return status.components.text(
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
marginTop: 5,
|
||||||
|
marginHorizontal: 0,
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: "font",
|
||||||
|
color: "black"
|
||||||
|
}
|
||||||
|
}, "*****");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function walletView(params) {
|
||||||
|
if (params.value != "") {
|
||||||
|
var url = params.value;
|
||||||
|
if (!/^[a-zA-Z-_]+:/.test(url)) {
|
||||||
|
url = 'http://' + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {webViewUrl: url};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status.command({
|
||||||
|
name: "browse",
|
||||||
|
description: "browser",
|
||||||
|
color: "#ffa500",
|
||||||
|
fullscreen: true,
|
||||||
|
suggestionsTrigger: 'on-send',
|
||||||
|
params: [{
|
||||||
|
name: "webpage",
|
||||||
|
suggestions: walletView,
|
||||||
|
type: status.types.TEXT
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
function validateBalance(params) {
|
||||||
|
try {
|
||||||
|
var val = web3.toWei(params.value, "ether");
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
errors: [
|
||||||
|
status.components.validationMessage(
|
||||||
|
"Amount",
|
||||||
|
"Amount is not valid number"//err.message
|
||||||
|
)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var balance = web3.eth.getBalance(params.command.address);
|
||||||
|
if (bn(val).greaterThan(bn(balance))) {
|
||||||
|
return {
|
||||||
|
errors: [
|
||||||
|
status.components.validationMessage(
|
||||||
|
"Amount",
|
||||||
|
"Not enough ETH on balance ("
|
||||||
|
+ web3.fromWei(balance, "ether")
|
||||||
|
+ " ETH)"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendTransaction(params) {
|
||||||
|
var data = {
|
||||||
|
from: params.command.from,
|
||||||
|
to: params.command.to,
|
||||||
|
value: web3.toWei(params.value, "ether")
|
||||||
|
};
|
||||||
|
var hash = web3.eth.sendTransaction(data);
|
||||||
|
|
||||||
|
return {"transaction-hash": hash};
|
||||||
|
}
|
||||||
|
|
||||||
|
status.command({
|
||||||
|
name: "send",
|
||||||
|
color: "#5fc48d",
|
||||||
|
description: "Send transaction",
|
||||||
|
params: [{
|
||||||
|
name: "amount",
|
||||||
|
type: status.types.NUMBER
|
||||||
|
}],
|
||||||
|
preview: function (params) {
|
||||||
|
return status.components.text(
|
||||||
|
{},
|
||||||
|
params.value + " ETH"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handler: sendTransaction,
|
||||||
|
validator: validateBalance
|
||||||
|
});
|
|
@ -0,0 +1,155 @@
|
||||||
|
var _status_catalog = {
|
||||||
|
commands: {},
|
||||||
|
responses: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function Command() {
|
||||||
|
}
|
||||||
|
function Response() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Command.prototype.addToCatalog = function () {
|
||||||
|
_status_catalog.commands[this.name] = this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Command.prototype.param = function (parameter) {
|
||||||
|
this.params.push(parameter);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Command.prototype.create = function (com) {
|
||||||
|
this.name = com.name;
|
||||||
|
this.description = com.description;
|
||||||
|
this.handler = com.handler;
|
||||||
|
this["has-handler"] = com.handler != null;
|
||||||
|
this.validator = com.validator;
|
||||||
|
this.color = com.color;
|
||||||
|
this.icon = com.icon;
|
||||||
|
this.params = com.params || [];
|
||||||
|
this.preview = com.preview;
|
||||||
|
this["suggestions-trigger"] = com.suggestionsTrigger || "on-change";
|
||||||
|
this.fullscreen = com.fullscreen;
|
||||||
|
this.addToCatalog();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Response.prototype = Object.create(Command.prototype);
|
||||||
|
Response.prototype.addToCatalog = function () {
|
||||||
|
_status_catalog.responses[this.name] = this;
|
||||||
|
};
|
||||||
|
Response.prototype.onReceiveResponse = function (handler) {
|
||||||
|
this.onReceive = handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
function call(pathStr, paramsStr) {
|
||||||
|
var params = JSON.parse(paramsStr),
|
||||||
|
path = JSON.parse(pathStr),
|
||||||
|
fn, res;
|
||||||
|
|
||||||
|
fn = path.reduce(function (catalog, name) {
|
||||||
|
if (catalog && catalog[name]) {
|
||||||
|
return catalog[name];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_status_catalog
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = fn(params);
|
||||||
|
|
||||||
|
return JSON.stringify(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function text(options, s) {
|
||||||
|
return ['text', options, s];
|
||||||
|
}
|
||||||
|
|
||||||
|
function view(options, elements) {
|
||||||
|
return ['view', options].concat(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function image(options) {
|
||||||
|
return ['image', options];
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchable(options, element) {
|
||||||
|
return ['touchable', options, element];
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollView(options, elements) {
|
||||||
|
return ['scroll-view', options].concat(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function webView(url) {
|
||||||
|
return ['web-view', {
|
||||||
|
source: {
|
||||||
|
uri: url
|
||||||
|
},
|
||||||
|
javaScriptEnabled: true
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
function validationMessage(titleText, descriptionText) {
|
||||||
|
var titleStyle = {
|
||||||
|
color: "white",
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "sans-serif"
|
||||||
|
};
|
||||||
|
var title = status.components.text(titleStyle, titleText);
|
||||||
|
|
||||||
|
var descriptionStyle = {
|
||||||
|
color: "white",
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "sans-serif",
|
||||||
|
opacity: 0.9
|
||||||
|
};
|
||||||
|
var description = status.components.text(descriptionStyle, descriptionText);
|
||||||
|
|
||||||
|
return status.components.view(
|
||||||
|
{
|
||||||
|
backgroundColor: "red",
|
||||||
|
height: 61,
|
||||||
|
paddingLeft: 16,
|
||||||
|
paddingTop: 14,
|
||||||
|
},
|
||||||
|
[title, description]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var status = {
|
||||||
|
command: function (h) {
|
||||||
|
var command = new Command();
|
||||||
|
return command.create(h);
|
||||||
|
},
|
||||||
|
response: function (h) {
|
||||||
|
var response = new Response();
|
||||||
|
return response.create(h);
|
||||||
|
},
|
||||||
|
autorun: function (commandName) {
|
||||||
|
_status_catalog.autorun = commandName;
|
||||||
|
},
|
||||||
|
types: {
|
||||||
|
TEXT: 'text',
|
||||||
|
NUMBER: 'number',
|
||||||
|
PHONE: 'phone',
|
||||||
|
PASSWORD: 'password'
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
SET_VALUE: 'set-value'
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
view: view,
|
||||||
|
text: text,
|
||||||
|
image: image,
|
||||||
|
touchable: touchable,
|
||||||
|
scrollView: scrollView,
|
||||||
|
webView: webView,
|
||||||
|
validationMessage: validationMessage
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package jail
|
||||||
|
|
||||||
const Web3_JS = `
|
const Web3_JS = `
|
||||||
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
@ -1,500 +0,0 @@
|
||||||
{
|
|
||||||
"ImportPath": "github.com/status-im/statusgo/src",
|
|
||||||
"GoVersion": "go1.6",
|
|
||||||
"GodepVersion": "v74",
|
|
||||||
"Packages": [
|
|
||||||
"./..."
|
|
||||||
],
|
|
||||||
"Deps": [
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/Gustav-Simonsson/go-opencl/cl",
|
|
||||||
"Rev": "593e01cfc4f3353585015321e01951d4a907d3ef"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/aristanetworks/goarista/atime",
|
|
||||||
"Rev": "41405b70e69314415c378d9456fd01075f2ad2f2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/ethash",
|
|
||||||
"Comment": "v23.1-247-g2e80de5",
|
|
||||||
"Rev": "2e80de5022370cfe632195b1720db52d07ff8a77"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/accounts",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/accounts/abi",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/accounts/abi/bind",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/cmd/utils",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/common",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/common/compiler",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/common/httpclient",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/common/mclock",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/common/registrar",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/common/registrar/ethreg",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/core",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/core/state",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/core/types",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/core/vm",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto/ecies",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto/randentropy",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto/secp256k1",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/crypto/sha3",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/eth",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/eth/downloader",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/eth/fetcher",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/eth/filters",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/eth/gasprice",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/ethdb",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/event",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/event/filter",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/internal/debug",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/internal/ethapi",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/les",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/les/flowcontrol",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/light",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/logger",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/logger/glog",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/metrics",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/miner",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/node",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/p2p",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/p2p/discover",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/p2p/nat",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/params",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/pow",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/release",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/rlp",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/rpc",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/trie",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/ethereum/go-ethereum/whisper",
|
|
||||||
"Comment": "v1.0.1-1001-g66560ee",
|
|
||||||
"Rev": "66560ee28d836e91ac50bf2d6236b7ee4c9ecadf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/golang/snappy",
|
|
||||||
"Rev": "d9eb7a3d35ec988b8585d4a0068e462c27d28380"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/hashicorp/golang-lru",
|
|
||||||
"Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/hashicorp/golang-lru/simplelru",
|
|
||||||
"Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/huin/goupnp",
|
|
||||||
"Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/huin/goupnp/dcps/internetgateway1",
|
|
||||||
"Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/huin/goupnp/dcps/internetgateway2",
|
|
||||||
"Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/huin/goupnp/httpu",
|
|
||||||
"Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/huin/goupnp/scpd",
|
|
||||||
"Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/huin/goupnp/soap",
|
|
||||||
"Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/huin/goupnp/ssdp",
|
|
||||||
"Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/jackpal/go-nat-pmp",
|
|
||||||
"Comment": "v1.0.1-4-g1fa385a",
|
|
||||||
"Rev": "1fa385a6f45828c83361136b45b1a21a12139493"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/microsoft/go-winio",
|
|
||||||
"Comment": "v0.3.5",
|
|
||||||
"Rev": "4f1a71750d95a5a8a46c40a67ffbed8129c2f138"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/pborman/uuid",
|
|
||||||
"Comment": "v1.0-11-gc55201b",
|
|
||||||
"Rev": "c55201b036063326c5b1b89ccfe45a184973d073"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/pkg/errors",
|
|
||||||
"Comment": "v0.4.0",
|
|
||||||
"Rev": "d814416a46cbb066b728cfff58d30a986bc9ddbe"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/rcrowley/go-metrics",
|
|
||||||
"Rev": "eeba7bd0dd01ace6e690fa833b3f22aaec29af43"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/rjeczalik/notify",
|
|
||||||
"Rev": "5dd6205716539662f8f14ab513552b41eab69d5d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/rs/cors",
|
|
||||||
"Rev": "3ca2b550f6a4333b63c845850f760a7d00412cd6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/rs/xhandler",
|
|
||||||
"Rev": "d9d9599b6aaf6a058cb7b1f48291ded2cbd13390"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/cache",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/comparer",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/errors",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/filter",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/iterator",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/journal",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/memdb",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/opt",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/storage",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/table",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb/util",
|
|
||||||
"Rev": "cfa635847112c5dc4782e128fa7e0d05fdbfb394"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
|
||||||
"Rev": "1777f3ba8c1fed80fcaec3317e3aaa4f627764d2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/ripemd160",
|
|
||||||
"Rev": "1777f3ba8c1fed80fcaec3317e3aaa4f627764d2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/scrypt",
|
|
||||||
"Rev": "1777f3ba8c1fed80fcaec3317e3aaa4f627764d2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/context",
|
|
||||||
"Rev": "fb93926129b8ec0056f2f458b1f519654814edf0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/html",
|
|
||||||
"Rev": "fb93926129b8ec0056f2f458b1f519654814edf0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/html/atom",
|
|
||||||
"Rev": "fb93926129b8ec0056f2f458b1f519654814edf0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/html/charset",
|
|
||||||
"Rev": "fb93926129b8ec0056f2f458b1f519654814edf0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/websocket",
|
|
||||||
"Rev": "fb93926129b8ec0056f2f458b1f519654814edf0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/sys/windows",
|
|
||||||
"Rev": "f64b50fbea64174967a8882830d621a18ee1548e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/charmap",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/htmlindex",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/internal",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/internal/identifier",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/japanese",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/korean",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/simplifiedchinese",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/traditionalchinese",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/encoding/unicode",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/internal/tag",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/internal/utf8internal",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/language",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/runes",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/text/transform",
|
|
||||||
"Rev": "a71fd10341b064c10f4a81ceac72bcf70f26ea34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/tools/go/ast/astutil",
|
|
||||||
"Rev": "5e468032ea9e193c60de97cfcd040ffa7a9b774e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/tools/imports",
|
|
||||||
"Rev": "5e468032ea9e193c60de97cfcd040ffa7a9b774e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "gopkg.in/fatih/set.v0",
|
|
||||||
"Comment": "v0.1.0-3-g27c4092",
|
|
||||||
"Rev": "27c40922c40b43fe04554d8223a402af3ea333f3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "gopkg.in/karalabe/cookiejar.v2/collections/prque",
|
|
||||||
"Rev": "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "gopkg.in/urfave/cli.v1",
|
|
||||||
"Comment": "v1.18.0",
|
|
||||||
"Rev": "1efa31f08b9333f1bd4882d61f9d668a70cd902e"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
This directory tree is generated automatically by godep.
|
|
||||||
|
|
||||||
Please do not edit.
|
|
||||||
|
|
||||||
See https://github.com/tools/godep for more information.
|
|
|
@ -1,631 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/les"
|
|
||||||
"github.com/ethereum/go-ethereum/les/status"
|
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/whisper"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
testDataDir = ".ethereumtest"
|
|
||||||
testAddress = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
|
||||||
testAddressPassword = "asdf"
|
|
||||||
testNodeSyncSeconds = 180
|
|
||||||
newAccountPassword = "badpassword"
|
|
||||||
|
|
||||||
whisperMessage1 = "test message 1 (K1 -> K1)"
|
|
||||||
whisperMessage2 = "test message 2 (K1 -> '')"
|
|
||||||
whisperMessage3 = "test message 3 ('' -> '')"
|
|
||||||
whisperMessage4 = "test message 4 ('' -> K1)"
|
|
||||||
whisperMessage5 = "test message 5 (K2 -> K1)"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreateChildAccount(t *testing.T) {
|
|
||||||
err := prepareTestNode()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create an account
|
|
||||||
address, pubKey, mnemonic, err := createAccount(newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not create account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
|
||||||
|
|
||||||
account, err := utils.MakeAddress(accountManager, address)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("can not get account from address: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// obtain decrypted key, and make sure that extended key (which will be used as root for sub-accounts) is present
|
|
||||||
account, key, err := accountManager.AccountDecryptedKey(account, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("can not obtain decrypted account key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.ExtendedKey == nil {
|
|
||||||
t.Error("CKD#2 has not been generated for new account")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// try creating sub-account, w/o selecting main account i.e. w/o login to main account
|
|
||||||
_, _, err = createChildAccount("", newAccountPassword)
|
|
||||||
if !reflect.DeepEqual(err, ErrNoAccountSelected) {
|
|
||||||
t.Errorf("expected error is not returned (tried to create sub-account w/o login): %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = selectAccount(address, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test failed: could not select account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create sub-account with wrong password
|
|
||||||
_, _, err = createChildAccount("", "wrong password")
|
|
||||||
if !reflect.DeepEqual(err, errors.New("cannot retreive a valid key for a given account: could not decrypt key with given passphrase")) {
|
|
||||||
t.Errorf("expected error is not returned (tried to create sub-account with wrong password): %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create sub-account (from implicit parent)
|
|
||||||
subAccount1, subPubKey1, err := createChildAccount("", newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("cannot create sub-account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that sub-account index automatically progresses
|
|
||||||
subAccount2, subPubKey2, err := createChildAccount("", newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("cannot create sub-account: %v", err)
|
|
||||||
}
|
|
||||||
if subAccount1 == subAccount2 || subPubKey1 == subPubKey2 {
|
|
||||||
t.Error("sub-account index auto-increament failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create sub-account (from explicit parent)
|
|
||||||
subAccount3, subPubKey3, err := createChildAccount(subAccount2, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("cannot create sub-account: %v", err)
|
|
||||||
}
|
|
||||||
if subAccount1 == subAccount3 || subPubKey1 == subPubKey3 || subAccount2 == subAccount3 || subPubKey2 == subPubKey3 {
|
|
||||||
t.Error("sub-account index auto-increament failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecoverAccount(t *testing.T) {
|
|
||||||
err := prepareTestNode()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create an account
|
|
||||||
address, pubKey, mnemonic, err := createAccount(newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not create account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
|
||||||
|
|
||||||
// try recovering using password + mnemonic
|
|
||||||
addressCheck, pubKeyCheck, err := recoverAccount(newAccountPassword, mnemonic)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("recover account failed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if address != addressCheck || pubKey != pubKeyCheck {
|
|
||||||
t.Error("recover account details failed to pull the correct details")
|
|
||||||
}
|
|
||||||
|
|
||||||
// now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device
|
|
||||||
account, err := utils.MakeAddress(accountManager, address)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("can not get account from address: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
account, key, err := accountManager.AccountDecryptedKey(account, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("can not obtain decrypted account key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
extChild2String := key.ExtendedKey.String()
|
|
||||||
|
|
||||||
if err := accountManager.DeleteAccount(account, newAccountPassword); err != nil {
|
|
||||||
t.Errorf("cannot remove account: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addressCheck, pubKeyCheck, err = recoverAccount(newAccountPassword, mnemonic)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("recover account failed (for non-cached account): %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if address != addressCheck || pubKey != pubKeyCheck {
|
|
||||||
t.Error("recover account details failed to pull the correct details (for non-cached account)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that extended key exists and is imported ok too
|
|
||||||
account, key, err = accountManager.AccountDecryptedKey(account, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("can not obtain decrypted account key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if extChild2String != key.ExtendedKey.String() {
|
|
||||||
t.Errorf("CKD#2 key mismatch, expected: %s, got: %s", extChild2String, key.ExtendedKey.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that calling import several times, just returns from cache (no error is expected)
|
|
||||||
addressCheck, pubKeyCheck, err = recoverAccount(newAccountPassword, mnemonic)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("recover account failed (for non-cached account): %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if address != addressCheck || pubKey != pubKeyCheck {
|
|
||||||
t.Error("recover account details failed to pull the correct details (for non-cached account)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// time to login with recovered data
|
|
||||||
var whisperInstance *whisper.Whisper
|
|
||||||
if err := currentNode.Service(&whisperInstance); err != nil {
|
|
||||||
t.Errorf("whisper service not running: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that identity is not (yet injected)
|
|
||||||
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKeyCheck))) {
|
|
||||||
t.Errorf("identity already present in whisper")
|
|
||||||
}
|
|
||||||
err = selectAccount(addressCheck, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test failed: could not select account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKeyCheck))) {
|
|
||||||
t.Errorf("identity not injected into whisper: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccountSelect(t *testing.T) {
|
|
||||||
|
|
||||||
err := prepareTestNode()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// test to see if the account was injected in whisper
|
|
||||||
var whisperInstance *whisper.Whisper
|
|
||||||
if err := currentNode.Service(&whisperInstance); err != nil {
|
|
||||||
t.Errorf("whisper service not running: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create an account
|
|
||||||
address1, pubKey1, _, err := createAccount(newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not create account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address1, pubKey1)
|
|
||||||
|
|
||||||
address2, pubKey2, _, err := createAccount(newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
t.Error("Test failed: could not create account")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address2, pubKey2)
|
|
||||||
|
|
||||||
// make sure that identity is not (yet injected)
|
|
||||||
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
|
||||||
t.Errorf("identity already present in whisper")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try selecting with wrong password
|
|
||||||
err = selectAccount(address1, "wrongPassword")
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("select account is expected to throw error: wrong password used")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = selectAccount(address1, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test failed: could not select account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
|
||||||
t.Errorf("identity not injected into whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// select another account, make sure that previous account is wiped out from Whisper cache
|
|
||||||
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) {
|
|
||||||
t.Errorf("identity already present in whisper")
|
|
||||||
}
|
|
||||||
err = selectAccount(address2, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test failed: could not select account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) {
|
|
||||||
t.Errorf("identity not injected into whisper: %v", err)
|
|
||||||
}
|
|
||||||
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
|
||||||
t.Errorf("identity should be removed, but it is still present in whisper")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccountLogout(t *testing.T) {
|
|
||||||
|
|
||||||
err := prepareTestNode()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var whisperInstance *whisper.Whisper
|
|
||||||
if err := currentNode.Service(&whisperInstance); err != nil {
|
|
||||||
t.Errorf("whisper service not running: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create an account
|
|
||||||
address, pubKey, _, err := createAccount(newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not create account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that identity doesn't exist (yet) in Whisper
|
|
||||||
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey))) {
|
|
||||||
t.Error("identity already present in whisper")
|
|
||||||
}
|
|
||||||
|
|
||||||
// select/login
|
|
||||||
err = selectAccount(address, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test failed: could not select account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey))) {
|
|
||||||
t.Error("identity not injected into whisper")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = logout()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("cannot logout: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now, logout and check if identity is removed indeed
|
|
||||||
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey))) {
|
|
||||||
t.Error("identity not cleared from whisper")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWhisperMessaging(t *testing.T) {
|
|
||||||
err := prepareTestNode()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// test to see if the account was injected in whisper
|
|
||||||
var whisperInstance *whisper.Whisper
|
|
||||||
if err := currentNode.Service(&whisperInstance); err != nil {
|
|
||||||
t.Errorf("whisper service not running: %v", err)
|
|
||||||
}
|
|
||||||
whisperAPI := whisper.NewPublicWhisperAPI(whisperInstance)
|
|
||||||
|
|
||||||
// prepare message
|
|
||||||
postArgs := whisper.PostArgs{
|
|
||||||
From: "",
|
|
||||||
To: "",
|
|
||||||
TTL: 10,
|
|
||||||
Topics: [][]byte{[]byte("test topic")},
|
|
||||||
Payload: "test message",
|
|
||||||
}
|
|
||||||
|
|
||||||
// create an accounts
|
|
||||||
address1, pubKey1, _, err := createAccount(newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
t.Error("Test failed: could not create account")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address1, pubKey1)
|
|
||||||
|
|
||||||
address2, pubKey2, _, err := createAccount(newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
t.Error("Test failed: could not create account")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address2, pubKey2)
|
|
||||||
|
|
||||||
// start watchers
|
|
||||||
var receivedMessages = map[string]bool{
|
|
||||||
whisperMessage1: false,
|
|
||||||
whisperMessage2: false,
|
|
||||||
whisperMessage3: false,
|
|
||||||
whisperMessage4: false,
|
|
||||||
whisperMessage5: false,
|
|
||||||
}
|
|
||||||
whisperService.Watch(whisper.Filter{
|
|
||||||
//From: crypto.ToECDSAPub(common.FromHex(pubKey1)),
|
|
||||||
//To: crypto.ToECDSAPub(common.FromHex(pubKey2)),
|
|
||||||
Fn: func(msg *whisper.Message) {
|
|
||||||
glog.V(logger.Info).Infof("Whisper message received: %s", msg.Payload)
|
|
||||||
receivedMessages[string(msg.Payload)] = true
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// inject key of newly created account into Whisper, as identity
|
|
||||||
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
|
||||||
t.Errorf("identity already present in whisper")
|
|
||||||
}
|
|
||||||
err = selectAccount(address1, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test failed: could not select account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
identitySucceess := whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1)))
|
|
||||||
if !identitySucceess || err != nil {
|
|
||||||
t.Errorf("identity not injected into whisper: %v", err)
|
|
||||||
}
|
|
||||||
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) { // ensure that second id is not injected
|
|
||||||
t.Errorf("identity already present in whisper")
|
|
||||||
}
|
|
||||||
|
|
||||||
// double selecting (shouldn't be a problem)
|
|
||||||
err = selectAccount(address1, newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test failed: could not select account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TEST 0: From != nil && To != nil: encrypted signed message (but we cannot decrypt it - so watchers will not report this)
|
|
||||||
postArgs.From = pubKey1
|
|
||||||
postArgs.To = pubKey2 // owner of that public key will be able to decrypt it
|
|
||||||
postSuccess, err := whisperAPI.Post(postArgs)
|
|
||||||
if !postSuccess || err != nil {
|
|
||||||
t.Errorf("could not post to whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TEST 1: From != nil && To != nil: encrypted signed message (to self)
|
|
||||||
postArgs.From = pubKey1
|
|
||||||
postArgs.To = pubKey1
|
|
||||||
postArgs.Payload = whisperMessage1
|
|
||||||
postSuccess, err = whisperAPI.Post(postArgs)
|
|
||||||
if !postSuccess || err != nil {
|
|
||||||
t.Errorf("could not post to whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// send from account that is not in Whisper identity list
|
|
||||||
postArgs.From = pubKey2
|
|
||||||
postSuccess, err = whisperAPI.Post(postArgs)
|
|
||||||
if err == nil || err.Error() != fmt.Sprintf("unknown identity to send from: %s", pubKey2) {
|
|
||||||
t.Errorf("expected error not voiced: we are sending from non-injected whisper identity")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TEST 2: From != nil && To == nil: signed broadcast (known sender)
|
|
||||||
postArgs.From = pubKey1
|
|
||||||
postArgs.To = ""
|
|
||||||
postArgs.Payload = whisperMessage2
|
|
||||||
postSuccess, err = whisperAPI.Post(postArgs)
|
|
||||||
if !postSuccess || err != nil {
|
|
||||||
t.Errorf("could not post to whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TEST 3: From == nil && To == nil: anonymous broadcast
|
|
||||||
postArgs.From = ""
|
|
||||||
postArgs.To = ""
|
|
||||||
postArgs.Payload = whisperMessage3
|
|
||||||
postSuccess, err = whisperAPI.Post(postArgs)
|
|
||||||
if !postSuccess || err != nil {
|
|
||||||
t.Errorf("could not post to whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TEST 4: From == nil && To != nil: encrypted anonymous message
|
|
||||||
postArgs.From = ""
|
|
||||||
postArgs.To = pubKey1
|
|
||||||
postArgs.Payload = whisperMessage4
|
|
||||||
postSuccess, err = whisperAPI.Post(postArgs)
|
|
||||||
if !postSuccess || err != nil {
|
|
||||||
t.Errorf("could not post to whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TEST 5: From != nil && To != nil: encrypted and signed response
|
|
||||||
postArgs.From = ""
|
|
||||||
postArgs.To = pubKey1
|
|
||||||
postArgs.Payload = whisperMessage5
|
|
||||||
postSuccess, err = whisperAPI.Post(postArgs)
|
|
||||||
if !postSuccess || err != nil {
|
|
||||||
t.Errorf("could not post to whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(2 * time.Second) // allow whisper to poll
|
|
||||||
for message, status := range receivedMessages {
|
|
||||||
if !status {
|
|
||||||
t.Errorf("Expected message not received: %s", message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueuedTransactions(t *testing.T) {
|
|
||||||
err := prepareTestNode()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create an account
|
|
||||||
address, _, _, err := createAccount(newAccountPassword)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not create account: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// test transaction queueing
|
|
||||||
var lightEthereum *les.LightEthereum
|
|
||||||
if err := currentNode.Service(&lightEthereum); err != nil {
|
|
||||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
|
||||||
}
|
|
||||||
backend := lightEthereum.StatusBackend
|
|
||||||
|
|
||||||
// replace transaction notification handler
|
|
||||||
var txHash = common.Hash{}
|
|
||||||
backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) {
|
|
||||||
glog.V(logger.Info).Infof("Transaction queued (will be completed in 5 secs): {id: %v, hash: %v}\n", queuedTx.Id, queuedTx.Hash.Hex())
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
if txHash, err = completeTransaction(string(queuedTx.Id), testAddressPassword); err != nil {
|
|
||||||
t.Errorf("cannot complete queued transation[%v]: %v", queuedTx.Id, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(logger.Info).Infof("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
|
||||||
})
|
|
||||||
|
|
||||||
// try completing non-existing transaction
|
|
||||||
if _, err := completeTransaction("some-bad-transaction-id", testAddressPassword); err == nil {
|
|
||||||
t.Errorf("Test failed: error expected and not recieved")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// send normal transaction
|
|
||||||
from, err := utils.MakeAddress(accountManager, testAddress)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not retrieve account from address: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
to, err := utils.MakeAddress(accountManager, address)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not retrieve account from address: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// this call blocks, up until Complete Transaction is called
|
|
||||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
|
||||||
From: from.Address,
|
|
||||||
To: &to.Address,
|
|
||||||
Value: rpc.NewHexNumber(big.NewInt(1000000000000)),
|
|
||||||
})
|
|
||||||
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")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
|
|
||||||
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
|
||||||
t.Error("Test failed: transaction was never queued or completed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// now test eviction queue
|
|
||||||
txQueue := backend.TransactionQueue()
|
|
||||||
var i = 0
|
|
||||||
backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) {
|
|
||||||
//glog.V(logger.Info).Infof("%d. Transaction queued (queue size: %d): {id: %v}\n", i, txQueue.Count(), queuedTx.Id)
|
|
||||||
i++
|
|
||||||
})
|
|
||||||
|
|
||||||
if txQueue.Count() != 0 {
|
|
||||||
t.Errorf("transaction count should be zero: %d", txQueue.Count())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
go backend.SendTransaction(nil, status.SendTxArgs{})
|
|
||||||
}
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
|
|
||||||
t.Logf("Number of transactions queued: %d. Queue size (shouldn't be more than %d): %d", i, status.DefaultTxQueueCap, txQueue.Count())
|
|
||||||
|
|
||||||
if txQueue.Count() != 10 {
|
|
||||||
t.Errorf("transaction count should be 10: got %d", txQueue.Count())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < status.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
|
|
||||||
go backend.SendTransaction(nil, status.SendTxArgs{})
|
|
||||||
}
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
|
|
||||||
if txQueue.Count() != status.DefaultTxQueueCap && txQueue.Count() != (status.DefaultTxQueueCap-1) {
|
|
||||||
t.Errorf("transaction count should be %d (or %d): got %d", status.DefaultTxQueueCap, status.DefaultTxQueueCap-1, txQueue.Count())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareTestNode() error {
|
|
||||||
if currentNode != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rpcport = 8546 // in order to avoid conflicts with running react-native app
|
|
||||||
|
|
||||||
syncRequired := false
|
|
||||||
if _, err := os.Stat(testDataDir); os.IsNotExist(err) {
|
|
||||||
syncRequired = true
|
|
||||||
}
|
|
||||||
|
|
||||||
dataDir, err := preprocessDataDir(testDataDir)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(logger.Warn).Infoln("make node failed:", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// import test account (with test ether on it)
|
|
||||||
err = copyFile(filepath.Join(testDataDir, "testnet", "keystore", "test-account.pk"), filepath.Join("data", "test-account.pk"))
|
|
||||||
if err != nil {
|
|
||||||
glog.V(logger.Warn).Infof("Test failed: cannot copy test account PK: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// start geth node and wait for it to initialize
|
|
||||||
go createAndStartNode(dataDir)
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
if currentNode == nil {
|
|
||||||
return errors.New("Test failed: could not start geth node")
|
|
||||||
}
|
|
||||||
|
|
||||||
if syncRequired {
|
|
||||||
glog.V(logger.Warn).Infof("Sync is required, it will take %d seconds", testNodeSyncSeconds)
|
|
||||||
time.Sleep(testNodeSyncSeconds * time.Second) // LES syncs headers, so that we are up do date when it is done
|
|
||||||
} else {
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanup() {
|
|
||||||
err := os.RemoveAll(testDataDir)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(logger.Warn).Infof("Test failed: could not clean up temporary datadir")
|
|
||||||
}
|
|
||||||
}
|
|
207
src/jail.go
207
src/jail.go
|
@ -1,207 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
"fmt"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
var statusJs string
|
|
||||||
var vms = make(map[string]*otto.Otto)
|
|
||||||
|
|
||||||
func Init(js string) {
|
|
||||||
statusJs = js
|
|
||||||
}
|
|
||||||
|
|
||||||
func printError(error string) string {
|
|
||||||
str := JSONError{
|
|
||||||
Error: error,
|
|
||||||
}
|
|
||||||
outBytes, _ := json.Marshal(&str)
|
|
||||||
return string(outBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printResult(res string, err error) string {
|
|
||||||
var out string
|
|
||||||
if err != nil {
|
|
||||||
out = printError(err.Error())
|
|
||||||
} else {
|
|
||||||
if "undefined" == res {
|
|
||||||
res = "null";
|
|
||||||
}
|
|
||||||
out = fmt.Sprintf(`{"result": %s}`, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(chatId string, js string) string {
|
|
||||||
vm := otto.New()
|
|
||||||
initJjs := statusJs + ";"
|
|
||||||
vms[chatId] = vm
|
|
||||||
_, err := vm.Run(initJjs)
|
|
||||||
vm.Set("jeth", struct{}{})
|
|
||||||
|
|
||||||
jethObj, _ := vm.Get("jeth")
|
|
||||||
jethObj.Object().Set("send", Send)
|
|
||||||
jethObj.Object().Set("sendAsync", Send)
|
|
||||||
|
|
||||||
jjs := Web3_JS + `
|
|
||||||
var Web3 = require('web3');
|
|
||||||
var web3 = new Web3(jeth);
|
|
||||||
var Bignumber = require("bignumber.js");
|
|
||||||
function bn(val){
|
|
||||||
return new Bignumber(val);
|
|
||||||
}
|
|
||||||
` + js + "; var catalog = JSON.stringify(_status_catalog);"
|
|
||||||
vm.Run(jjs)
|
|
||||||
|
|
||||||
res, _ := vm.Get("catalog")
|
|
||||||
|
|
||||||
return printResult(res.String(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Call(chatId string, path string, args string) string {
|
|
||||||
vm, ok := vms[chatId]
|
|
||||||
if !ok {
|
|
||||||
return printError(fmt.Sprintf("Vm[%s] doesn't exist.", chatId))
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := vm.Call("call", nil, path, args)
|
|
||||||
|
|
||||||
return printResult(res.String(), err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send will serialize the first argument, send it to the node and returns the response.
|
|
||||||
func Send(call otto.FunctionCall) (response otto.Value) {
|
|
||||||
// Ensure that we've got a batch request (array) or a single request (object)
|
|
||||||
arg := call.Argument(0).Object()
|
|
||||||
if arg == nil || (arg.Class() != "Array" && arg.Class() != "Object") {
|
|
||||||
throwJSException("request must be an object or array")
|
|
||||||
}
|
|
||||||
// Convert the otto VM arguments to Go values
|
|
||||||
data, err := call.Otto.Call("JSON.stringify", nil, arg)
|
|
||||||
if err != nil {
|
|
||||||
throwJSException(err.Error())
|
|
||||||
}
|
|
||||||
reqjson, err := data.ToString()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
throwJSException(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
reqs []rpc.JSONRequest
|
|
||||||
batch = true
|
|
||||||
)
|
|
||||||
if err = json.Unmarshal([]byte(reqjson), &reqs); err != nil {
|
|
||||||
// single request?
|
|
||||||
reqs = make([]rpc.JSONRequest, 1)
|
|
||||||
if err = json.Unmarshal([]byte(reqjson), &reqs[0]); err != nil {
|
|
||||||
throwJSException("invalid request")
|
|
||||||
}
|
|
||||||
batch = false
|
|
||||||
}
|
|
||||||
// Iteratively execute the requests
|
|
||||||
call.Otto.Set("response_len", len(reqs))
|
|
||||||
call.Otto.Run("var ret_response = new Array(response_len);")
|
|
||||||
|
|
||||||
for i, req := range reqs {
|
|
||||||
// Execute the RPC request and parse the reply
|
|
||||||
if err = client.Send(&req); err != nil {
|
|
||||||
return newErrorResponse(call, -32603, err.Error(), req.Id)
|
|
||||||
}
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
if err = client.Recv(&result); err != nil {
|
|
||||||
return newErrorResponse(call, -32603, err.Error(), req.Id)
|
|
||||||
}
|
|
||||||
// Feed the reply back into the JavaScript runtime environment
|
|
||||||
id, _ := result["id"]
|
|
||||||
jsonver, _ := result["jsonrpc"]
|
|
||||||
|
|
||||||
call.Otto.Set("ret_id", id)
|
|
||||||
call.Otto.Set("ret_jsonrpc", jsonver)
|
|
||||||
call.Otto.Set("response_idx", i)
|
|
||||||
|
|
||||||
if res, ok := result["result"]; ok {
|
|
||||||
payload, _ := json.Marshal(res)
|
|
||||||
call.Otto.Set("ret_result", string(payload))
|
|
||||||
response, err = call.Otto.Run(`
|
|
||||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
|
|
||||||
`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if res, ok := result["error"]; ok {
|
|
||||||
payload, _ := json.Marshal(res)
|
|
||||||
call.Otto.Set("ret_result", string(payload))
|
|
||||||
response, err = call.Otto.Run(`
|
|
||||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, error: JSON.parse(ret_result) };
|
|
||||||
`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return newErrorResponse(call, -32603, fmt.Sprintf("Invalid response"), new(int64))
|
|
||||||
}
|
|
||||||
// Convert single requests back from batch ones
|
|
||||||
if !batch {
|
|
||||||
call.Otto.Run("ret_response = ret_response[0];")
|
|
||||||
}
|
|
||||||
// Execute any registered callbacks
|
|
||||||
if call.Argument(1).IsObject() {
|
|
||||||
call.Otto.Set("callback", call.Argument(1))
|
|
||||||
call.Otto.Run(`
|
|
||||||
if (Object.prototype.toString.call(callback) == '[object Function]') {
|
|
||||||
callback(null, ret_response);
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// newErrorResponse creates a JSON RPC error response for a specific request id,
|
|
||||||
// containing the specified error code and error message. Beside returning the
|
|
||||||
// error to the caller, it also sets the ret_error and ret_response JavaScript
|
|
||||||
// variables.
|
|
||||||
func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {
|
|
||||||
// Bundle the error into a JSON RPC call response
|
|
||||||
res := rpc.JSONErrResponse{
|
|
||||||
Version: "2.0",
|
|
||||||
Id: id,
|
|
||||||
Error: rpc.JSONError{
|
|
||||||
Code: code,
|
|
||||||
Message: msg,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// Serialize the error response into JavaScript variables
|
|
||||||
errObj, err := json.Marshal(res.Error)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(logger.Error).Infof("Failed to serialize JSON RPC error: %v", err)
|
|
||||||
}
|
|
||||||
resObj, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(logger.Error).Infof("Failed to serialize JSON RPC error response: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = call.Otto.Run("ret_error = " + string(errObj)); err != nil {
|
|
||||||
glog.V(logger.Error).Infof("Failed to set `ret_error` to the occurred error: %v", err)
|
|
||||||
}
|
|
||||||
resVal, err := call.Otto.Run("ret_response = " + string(resObj))
|
|
||||||
if err != nil {
|
|
||||||
glog.V(logger.Error).Infof("Failed to set `ret_response` to the JSON RPC response: %v", err)
|
|
||||||
}
|
|
||||||
return resVal
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// throwJSException panics on an otto.Value. The Otto VM will recover from the
|
|
||||||
// Go panic and throw msg as a JavaScript error.
|
|
||||||
func throwJSException(msg interface{}) otto.Value {
|
|
||||||
val, err := otto.ToValue(msg)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(logger.Error).Infof("Failed to serialize JavaScript exception %v: %v", msg, err)
|
|
||||||
}
|
|
||||||
panic(val)
|
|
||||||
}
|
|
170
src/library.c
170
src/library.c
|
@ -1,170 +0,0 @@
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#ifdef IOS_DEPLOYMENT
|
|
||||||
#else
|
|
||||||
#include <jni.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool GethServiceSignalEvent( const char *jsonEvent );
|
|
||||||
|
|
||||||
#ifdef IOS_DEPLOYMENT
|
|
||||||
|
|
||||||
bool GethServiceSignalEvent( const char *jsonEvent )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
static JavaVM *gJavaVM = NULL;
|
|
||||||
static jclass JavaClassPtr_GethService = NULL;
|
|
||||||
static jmethodID JavaMethodPtr_signalEvent = NULL;
|
|
||||||
|
|
||||||
static bool JniLibraryInit( JNIEnv *env );
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* @brief Get interface to JNI.
|
|
||||||
*
|
|
||||||
* @return true if thread should be detached from JNI.
|
|
||||||
*/
|
|
||||||
static bool JniAttach( JNIEnv **env )
|
|
||||||
{
|
|
||||||
jint status;
|
|
||||||
|
|
||||||
if (gJavaVM == NULL)
|
|
||||||
{
|
|
||||||
env = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = (*gJavaVM)->GetEnv( gJavaVM, (void **)env, JNI_VERSION_1_6);
|
|
||||||
if (status == JNI_EDETACHED)
|
|
||||||
{
|
|
||||||
// attach thread to JNI
|
|
||||||
//(*gJavaVM)->AttachCurrentThread( gJavaVM, (void **)env, NULL ); // Oracle JNI API
|
|
||||||
(*gJavaVM)->AttachCurrentThread( gJavaVM, env, NULL ); // Android JNI API
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (status != JNI_OK)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* @brief The VM calls JNI_OnLoad when the native library is loaded.
|
|
||||||
*/
|
|
||||||
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|
||||||
{
|
|
||||||
bool detach;
|
|
||||||
JNIEnv *env;
|
|
||||||
int result = JNI_VERSION_1_6;
|
|
||||||
|
|
||||||
gJavaVM = vm;
|
|
||||||
|
|
||||||
// attach thread to JNI
|
|
||||||
detach = JniAttach( &env );
|
|
||||||
if (env == NULL)
|
|
||||||
{
|
|
||||||
// failed
|
|
||||||
gJavaVM = NULL;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!JniLibraryInit( env ))
|
|
||||||
{
|
|
||||||
// fail loading of JNI library
|
|
||||||
result = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (detach)
|
|
||||||
{
|
|
||||||
// detach thread from JNI
|
|
||||||
(*gJavaVM)->DetachCurrentThread( gJavaVM );
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != JNI_VERSION_1_6)
|
|
||||||
{
|
|
||||||
gJavaVM = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* @brief Initialize library.
|
|
||||||
*/
|
|
||||||
bool JniLibraryInit( JNIEnv *env )
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
JavaClassPtr_GethService = (*env)->FindClass( env, "com/statusim/geth/service/GethService" );
|
|
||||||
if (JavaClassPtr_GethService == NULL) return false;
|
|
||||||
JavaClassPtr_GethService = (jclass)(*env)->NewGlobalRef( env, JavaClassPtr_GethService );
|
|
||||||
if (JavaClassPtr_GethService == NULL) return false;
|
|
||||||
|
|
||||||
struct { bool bStatic; jclass classPtr; jmethodID *methodPtr; const char *methodId; const char *params; } javaMethodDescriptors[] =
|
|
||||||
{
|
|
||||||
{ true, JavaClassPtr_GethService, &JavaMethodPtr_signalEvent, "signalEvent", "(Ljava/lang/String;)V" },
|
|
||||||
|
|
||||||
// { false, JavaClassPtr_GethService, &JavaMethodPtr_someNonStaticMethod, "someNonStaticMethod", "(Ljava/lang/String;)V" },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i = 0; i < sizeof(javaMethodDescriptors) / sizeof(javaMethodDescriptors[0]); i++)
|
|
||||||
{
|
|
||||||
if (javaMethodDescriptors[i].bStatic)
|
|
||||||
{
|
|
||||||
*(javaMethodDescriptors[i].methodPtr) = (*env)->GetStaticMethodID( env, javaMethodDescriptors[i].classPtr, javaMethodDescriptors[i].methodId, javaMethodDescriptors[i].params );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*(javaMethodDescriptors[i].methodPtr) = (*env)->GetMethodID( env, javaMethodDescriptors[i].classPtr, javaMethodDescriptors[i].methodId, javaMethodDescriptors[i].params );
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*(javaMethodDescriptors[i].methodPtr) == NULL) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* @brief Calls static method signalEvent of class com.statusim.GethService.
|
|
||||||
*
|
|
||||||
* @param jsonEvent - UTF8 string
|
|
||||||
*/
|
|
||||||
bool GethServiceSignalEvent( const char *jsonEvent )
|
|
||||||
{
|
|
||||||
bool detach;
|
|
||||||
JNIEnv *env;
|
|
||||||
|
|
||||||
// attach thread to JNI
|
|
||||||
detach = JniAttach( &env );
|
|
||||||
if (env == NULL)
|
|
||||||
{
|
|
||||||
// failed
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
jstring javaJsonEvent = NULL;
|
|
||||||
if (jsonEvent != NULL)
|
|
||||||
{
|
|
||||||
javaJsonEvent = (*env)->NewStringUTF( env, jsonEvent );
|
|
||||||
}
|
|
||||||
|
|
||||||
(*env)->CallStaticVoidMethod( env, JavaClassPtr_GethService, JavaMethodPtr_signalEvent, javaJsonEvent );
|
|
||||||
|
|
||||||
if (javaJsonEvent != NULL) (*env)->DeleteLocalRef( env, javaJsonEvent );
|
|
||||||
|
|
||||||
if (detach)
|
|
||||||
{
|
|
||||||
// detach thread from JNI
|
|
||||||
(*gJavaVM)->DetachCurrentThread( gJavaVM );
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
162
src/node.go
162
src/node.go
|
@ -1,162 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/les"
|
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/release"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/whisper"
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
clientIdentifier = "Geth" // Client identifier to advertise over the network
|
|
||||||
versionMajor = 1 // Major version component of the current release
|
|
||||||
versionMinor = 5 // Minor version component of the current release
|
|
||||||
versionPatch = 0 // Patch version component of the current release
|
|
||||||
versionMeta = "unstable" // Version metadata to append to the version string
|
|
||||||
|
|
||||||
versionOracle = "0xfa7b9770ca4cb04296cac84f37736d4041251cdf" // Ethereum address of the Geth release oracle
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
vString string // Combined textual representation of the version
|
|
||||||
rConfig release.Config // Structured version information and release oracle config
|
|
||||||
currentNode *node.Node // currently running geth node
|
|
||||||
c *cli.Context // the CLI context used to start the geth node
|
|
||||||
lightEthereum *les.LightEthereum // LES service
|
|
||||||
accountManager *accounts.Manager // the account manager attached to the currentNode
|
|
||||||
selectedAddress string // address of the account that was processed during the last call to SelectAccount()
|
|
||||||
whisperService *whisper.Whisper // whisper service
|
|
||||||
datadir string // data directory for geth
|
|
||||||
rpcport int = 8545 // RPC port (replaced in unit tests)
|
|
||||||
client *rpc.Client
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrDataDirPreprocessingFailed = errors.New("Failed to pre-process data directory")
|
|
||||||
)
|
|
||||||
|
|
||||||
// MakeNode create a geth node entity
|
|
||||||
func MakeNode(inputDir string) *node.Node {
|
|
||||||
|
|
||||||
datadir := inputDir
|
|
||||||
|
|
||||||
// TODO remove admin rpcapi flag
|
|
||||||
set := flag.NewFlagSet("test", 0)
|
|
||||||
set.Bool("lightkdf", true, "Reduce key-derivation RAM & CPU usage at some expense of KDF strength")
|
|
||||||
set.Bool("shh", true, "whisper")
|
|
||||||
set.Bool("light", true, "disable eth")
|
|
||||||
set.Bool("testnet", true, "light test network")
|
|
||||||
set.Bool("rpc", true, "enable rpc")
|
|
||||||
set.String("rpcaddr", "localhost", "host for RPC")
|
|
||||||
set.Int("rpcport", rpcport, "rpc port")
|
|
||||||
set.String("rpccorsdomain", "*", "allow all domains")
|
|
||||||
set.String("verbosity", "3", "verbosity level")
|
|
||||||
set.String("rpcapi", "db,eth,net,web3,shh,personal,admin", "rpc api(s)")
|
|
||||||
set.String("datadir", datadir, "data directory for geth")
|
|
||||||
set.String("logdir", datadir, "log dir for glog")
|
|
||||||
c = cli.NewContext(nil, set, nil)
|
|
||||||
|
|
||||||
// Construct the textual version string from the individual components
|
|
||||||
vString = fmt.Sprintf("%d.%d.%d", versionMajor, versionMinor, versionPatch)
|
|
||||||
|
|
||||||
// Construct the version release oracle configuration
|
|
||||||
rConfig.Oracle = common.HexToAddress(versionOracle)
|
|
||||||
|
|
||||||
rConfig.Major = uint32(versionMajor)
|
|
||||||
rConfig.Minor = uint32(versionMinor)
|
|
||||||
rConfig.Patch = uint32(versionPatch)
|
|
||||||
|
|
||||||
utils.DebugSetup(c)
|
|
||||||
currentNode = makeNode(c, clientIdentifier, vString)
|
|
||||||
|
|
||||||
return currentNode
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeNode(ctx *cli.Context, name, version string) *node.Node {
|
|
||||||
nodeIn := utils.MakeNode(ctx, name, version)
|
|
||||||
utils.RegisterEthService(ctx, nodeIn, rConfig, makeDefaultExtra())
|
|
||||||
// Whisper must be explicitly enabled, but is auto-enabled in --dev mode.
|
|
||||||
shhEnabled := ctx.GlobalBool(utils.WhisperEnabledFlag.Name)
|
|
||||||
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
|
|
||||||
if shhEnabled || shhAutoEnabled {
|
|
||||||
utils.RegisterShhService(nodeIn)
|
|
||||||
}
|
|
||||||
return nodeIn
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartNode starts a geth node entity
|
|
||||||
func RunNode(nodeIn *node.Node) {
|
|
||||||
utils.StartNode(nodeIn)
|
|
||||||
|
|
||||||
if err := nodeIn.Service(&accountManager); err != nil {
|
|
||||||
glog.V(logger.Warn).Infoln("cannot get account manager:", err)
|
|
||||||
}
|
|
||||||
if err := nodeIn.Service(&whisperService); err != nil {
|
|
||||||
glog.V(logger.Warn).Infoln("cannot get whisper service:", err)
|
|
||||||
}
|
|
||||||
if err := nodeIn.Service(&lightEthereum); err != nil {
|
|
||||||
glog.V(logger.Warn).Infoln("cannot get light ethereum service:", err)
|
|
||||||
}
|
|
||||||
lightEthereum.StatusBackend.SetTransactionQueueHandler(onSendTransactionRequest)
|
|
||||||
|
|
||||||
client, _ = nodeIn.Attach()
|
|
||||||
nodeIn.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeDefaultExtra() []byte {
|
|
||||||
var clientInfo = struct {
|
|
||||||
Version uint
|
|
||||||
Name string
|
|
||||||
GoVersion string
|
|
||||||
Os string
|
|
||||||
}{uint(versionMajor<<16 | versionMinor<<8 | versionPatch), clientIdentifier, runtime.Version(), runtime.GOOS}
|
|
||||||
extra, err := rlp.EncodeToBytes(clientInfo)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(logger.Warn).Infoln("error setting canonical miner information:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if uint64(len(extra)) > params.MaximumExtraDataSize.Uint64() {
|
|
||||||
glog.V(logger.Warn).Infoln("error setting canonical miner information: extra exceeds", params.MaximumExtraDataSize)
|
|
||||||
glog.V(logger.Debug).Infof("extra: %x\n", extra)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return extra
|
|
||||||
}
|
|
||||||
|
|
||||||
func preprocessDataDir(dataDir string) (string, error) {
|
|
||||||
testDataDir := path.Join(dataDir, "testnet", "keystore")
|
|
||||||
if _, err := os.Stat(testDataDir); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(testDataDir, 0755); err != nil {
|
|
||||||
return dataDir, ErrDataDirPreprocessingFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy over static peer nodes list (LES auto-discovery is not stable yet)
|
|
||||||
dst := filepath.Join(dataDir, "testnet", "static-nodes.json")
|
|
||||||
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
|
||||||
src := filepath.Join("data", "static-nodes.json")
|
|
||||||
if err := copyFile(dst, src); err != nil {
|
|
||||||
return dataDir, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataDir, nil
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
extern bool GethServiceSignalEvent( const char *jsonEvent );
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/whisper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var(
|
|
||||||
whisperFilters []int
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func onWhisperMessage(message *whisper.Message) {
|
|
||||||
event := GethEvent{
|
|
||||||
Type: "whisper",
|
|
||||||
Event: WhisperMessageEvent{
|
|
||||||
Payload: string(message.Payload),
|
|
||||||
From: common.ToHex(crypto.FromECDSAPub(message.Recover())),
|
|
||||||
To: common.ToHex(crypto.FromECDSAPub(message.To)),
|
|
||||||
Sent: message.Sent.Unix(),
|
|
||||||
TTL: int64(message.TTL / time.Second),
|
|
||||||
Hash: common.ToHex(message.Hash.Bytes()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
body, _ := json.Marshal(&event)
|
|
||||||
C.GethServiceSignalEvent(C.CString(string(body)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func doAddWhisperFilter(args whisper.NewFilterArgs) int {
|
|
||||||
var id int
|
|
||||||
filter := whisper.Filter{
|
|
||||||
To: crypto.ToECDSAPub(common.FromHex(args.To)),
|
|
||||||
From: crypto.ToECDSAPub(common.FromHex(args.From)),
|
|
||||||
Topics: whisper.NewFilterTopics(args.Topics...),
|
|
||||||
Fn: onWhisperMessage,
|
|
||||||
}
|
|
||||||
|
|
||||||
id = whisperService.Watch(filter)
|
|
||||||
whisperFilters = append(whisperFilters, id)
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func doRemoveWhisperFilter(idFilter int) {
|
|
||||||
whisperService.Unwatch(idFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doClearWhisperFilters() {
|
|
||||||
for _, idFilter := range whisperFilters {
|
|
||||||
doRemoveWhisperFilter(idFilter)
|
|
||||||
}
|
|
||||||
whisperFilters = nil
|
|
||||||
}
|
|
|
@ -34,7 +34,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/status-im/status-go/src/extkeys"
|
"github.com/status-im/status-go/extkeys"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -33,7 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
"github.com/status-im/status-go/src/extkeys"
|
"github.com/status-im/status-go/extkeys"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -39,7 +39,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
"github.com/status-im/status-go/src/extkeys"
|
"github.com/status-im/status-go/extkeys"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
FROM karalabe/xgo-latest
|
FROM karalabe/xgo-latest
|
||||||
|
|
||||||
# Inject the container entry point, the build script (patched for Status bindings conditional builds of library.c)
|
# Inject the container entry point, the build script (patched for Status bindings conditional builds of C code)
|
||||||
ADD build.sh /build.sh
|
ADD build.sh /build.sh
|
||||||
ENV BUILD /build.sh
|
ENV BUILD /build.sh
|
||||||
RUN chmod +x $BUILD
|
RUN chmod +x $BUILD
|
||||||
|
|
|
@ -159,6 +159,7 @@ for TARGET in $TARGETS; do
|
||||||
if [ "$PLATFORM" == "" ] || [ "$PLATFORM" == "." ] || [ "$PLATFORM" == "android" ]; then
|
if [ "$PLATFORM" == "" ] || [ "$PLATFORM" == "." ] || [ "$PLATFORM" == "android" ]; then
|
||||||
PLATFORM=16 # Jelly Bean 4.0.0
|
PLATFORM=16 # Jelly Bean 4.0.0
|
||||||
fi
|
fi
|
||||||
|
CGO_STATUS_IM="-D ANDROID_DEPLOYMENT"
|
||||||
if [ "$PLATFORM" -ge 16 ]; then
|
if [ "$PLATFORM" -ge 16 ]; then
|
||||||
CGO_CCPIE="-fPIE"
|
CGO_CCPIE="-fPIE"
|
||||||
CGO_LDPIE="-fPIE"
|
CGO_LDPIE="-fPIE"
|
||||||
|
@ -190,11 +191,11 @@ for TARGET in $TARGETS; do
|
||||||
|
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "arm" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "arm" ]; then
|
||||||
CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
||||||
CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go build $V $X "${T[@]}" --ldflags="$V $EXT_LDPIE $EXT_LDAMD $LD" $BM -o "/build/$NAME-android-$PLATFORM-arm`extension android`" ./$PACK
|
CGO_CFLAGS="-D ANDROID_DEPLOYMENT" CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE $CGO_STATUS_IM" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go build $V $X "${T[@]}" --ldflags="$V $EXT_LDPIE $EXT_LDAMD $LD" $BM -o "/build/$NAME-android-$PLATFORM-arm`extension android`" ./$PACK
|
||||||
fi
|
fi
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "aar" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "aar" ]; then
|
||||||
CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
||||||
CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 go build $V $X "${T[@]}" --ldflags="$V $EXT_LDAMD $LD" --buildmode=c-shared -o "/build-android-aar/$NAME-android-$PLATFORM-arm.so" ./$PACK
|
CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ GOOS=android GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS="$CGO_STATUS_IM" go build $V $X "${T[@]}" --ldflags="$V $EXT_LDAMD $LD" --buildmode=c-shared -o "/build-android-aar/$NAME-android-$PLATFORM-arm.so" ./$PACK
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
@ -214,11 +215,11 @@ for TARGET in $TARGETS; do
|
||||||
|
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "386" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "386" ]; then
|
||||||
CC=i686-linux-android-gcc CXX=i686-linux-android-g++ GOOS=android GOARCH=386 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
CC=i686-linux-android-gcc CXX=i686-linux-android-g++ GOOS=android GOARCH=386 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
||||||
CC=i686-linux-android-gcc CXX=i686-linux-android-g++ GOOS=android GOARCH=386 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go build $V $X "${T[@]}" --ldflags="$V $EXT_LDPIE $LD" $BM -o "/build/$NAME-android-$PLATFORM-386`extension android`" ./$PACK
|
CC=i686-linux-android-gcc CXX=i686-linux-android-g++ GOOS=android GOARCH=386 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE $CGO_STATUS_IM" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go build $V $X "${T[@]}" --ldflags="$V $EXT_LDPIE $LD" $BM -o "/build/$NAME-android-$PLATFORM-386`extension android`" ./$PACK
|
||||||
fi
|
fi
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "aar" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "aar" ]; then
|
||||||
CC=i686-linux-android-gcc CXX=i686-linux-android-g++ GOOS=android GOARCH=386 CGO_ENABLED=1 go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
CC=i686-linux-android-gcc CXX=i686-linux-android-g++ GOOS=android GOARCH=386 CGO_ENABLED=1 go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
||||||
CC=i686-linux-android-gcc CXX=i686-linux-android-g++ GOOS=android GOARCH=386 CGO_ENABLED=1 go build $V $X "${T[@]}" --ldflags="$V $LD" --buildmode=c-shared -o "/build-android-aar/$NAME-android-$PLATFORM-386.so" ./$PACK
|
CC=i686-linux-android-gcc CXX=i686-linux-android-g++ GOOS=android GOARCH=386 CGO_ENABLED=1 CGO_CFLAGS="$CGO_STATUS_IM" go build $V $X "${T[@]}" --ldflags="$V $LD" --buildmode=c-shared -o "/build-android-aar/$NAME-android-$PLATFORM-386.so" ./$PACK
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ "$PLATFORM" -ge 21 ] && ([ $XGOARCH == "." ] || [ $XGOARCH == "arm64" ] || [ $XGOARCH == "aar" ]); then
|
if [ "$PLATFORM" -ge 21 ] && ([ $XGOARCH == "." ] || [ $XGOARCH == "arm64" ] || [ $XGOARCH == "aar" ]); then
|
||||||
|
@ -234,14 +235,15 @@ for TARGET in $TARGETS; do
|
||||||
|
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "arm64" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "arm64" ]; then
|
||||||
CC=aarch64-linux-android-gcc CXX=aarch64-linux-android-g++ GOOS=android GOARCH=arm64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
CC=aarch64-linux-android-gcc CXX=aarch64-linux-android-g++ GOOS=android GOARCH=arm64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
||||||
CC=aarch64-linux-android-gcc CXX=aarch64-linux-android-g++ GOOS=android GOARCH=arm64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go build $V $X "${T[@]}" --ldflags="$V $EXT_LDPIE $LD" $BM -o "/build/$NAME-android-$PLATFORM-arm64`extension android`" ./$PACK
|
CC=aarch64-linux-android-gcc CXX=aarch64-linux-android-g++ GOOS=android GOARCH=arm64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_CCPIE $CGO_STATUS_IM" CGO_CXXFLAGS="$CGO_CCPIE" CGO_LDFLAGS="$CGO_LDPIE" go build $V $X "${T[@]}" --ldflags="$V $EXT_LDPIE $LD" $BM -o "/build/$NAME-android-$PLATFORM-arm64`extension android`" ./$PACK
|
||||||
fi
|
fi
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "aar" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "aar" ]; then
|
||||||
CC=aarch64-linux-android-gcc CXX=aarch64-linux-android-g++ GOOS=android GOARCH=arm64 CGO_ENABLED=1 go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
CC=aarch64-linux-android-gcc CXX=aarch64-linux-android-g++ GOOS=android GOARCH=arm64 CGO_ENABLED=1 go get $V $X "${T[@]}" --ldflags="$V $LD" -d ./$PACK
|
||||||
CC=aarch64-linux-android-gcc CXX=aarch64-linux-android-g++ GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build $V $X "${T[@]}" --ldflags="$V $LD" --buildmode=c-shared -o "/build-android-aar/$NAME-android-$PLATFORM-arm64.so" ./$PACK
|
CC=aarch64-linux-android-gcc CXX=aarch64-linux-android-g++ GOOS=android GOARCH=arm64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_STATUS_IM" go build $V $X "${T[@]}" --ldflags="$V $LD" --buildmode=c-shared -o "/build-android-aar/$NAME-android-$PLATFORM-arm64.so" ./$PACK
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
unset CGO_STATUS_IM # good to let that extra var go away
|
||||||
# Assemble the Android Archive from the built shared libraries
|
# Assemble the Android Archive from the built shared libraries
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "aar" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "aar" ]; then
|
||||||
title=${NAME^}
|
title=${NAME^}
|
||||||
|
@ -491,7 +493,7 @@ for TARGET in $TARGETS; do
|
||||||
if [ "$GO_VERSION" -lt 160 ]; then
|
if [ "$GO_VERSION" -lt 160 ]; then
|
||||||
LDSTRIP="-s"
|
LDSTRIP="-s"
|
||||||
fi
|
fi
|
||||||
STATUS_IM_CFLAGS="-D IOS_DEPLOYMENT"
|
CGO_STATUS_IM="-D IOS_DEPLOYMENT"
|
||||||
# Cross compile to all available iOS and simulator platforms
|
# Cross compile to all available iOS and simulator platforms
|
||||||
if [ -d "$IOS_NDK_ARM_7" ] && ([ $XGOARCH == "." ] || [ $XGOARCH == "arm-7" ] || [ $XGOARCH == "framework" ]); then
|
if [ -d "$IOS_NDK_ARM_7" ] && ([ $XGOARCH == "." ] || [ $XGOARCH == "arm-7" ] || [ $XGOARCH == "framework" ]); then
|
||||||
echo "Bootstrapping ios-$PLATFORM/arm-7..."
|
echo "Bootstrapping ios-$PLATFORM/arm-7..."
|
||||||
|
@ -505,7 +507,7 @@ for TARGET in $TARGETS; do
|
||||||
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm GOARM=7 CGO_ENABLED=1 go build $V $X "${IOSTAGS[@]}" --ldflags="$LDSTRIP $V $LD" $BM -o "/build/$NAME-ios-$PLATFORM-armv7`extension darwin`" ./$PACK
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm GOARM=7 CGO_ENABLED=1 go build $V $X "${IOSTAGS[@]}" --ldflags="$LDSTRIP $V $LD" $BM -o "/build/$NAME-ios-$PLATFORM-armv7`extension darwin`" ./$PACK
|
||||||
fi
|
fi
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "framework" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "framework" ]; then
|
||||||
CGO_CFLAGS="-D IOS_DEPLOYMENT" CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm GOARM=7 CGO_ENABLED=1 go build $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" --buildmode=c-archive -o "/build-ios-fw/$NAME-ios-$PLATFORM-armv7.a" ./$PACK
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm GOARM=7 CGO_ENABLED=1 CGO_CFLAGS="$CGO_STATUS_IM" go build $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" --buildmode=c-archive -o "/build-ios-fw/$NAME-ios-$PLATFORM-armv7.a" ./$PACK
|
||||||
fi
|
fi
|
||||||
echo "Cleaning up Go runtime for ios-$PLATFORM/arm-7..."
|
echo "Cleaning up Go runtime for ios-$PLATFORM/arm-7..."
|
||||||
rm -rf /usr/local/go/pkg/darwin_arm
|
rm -rf /usr/local/go/pkg/darwin_arm
|
||||||
|
@ -519,10 +521,10 @@ for TARGET in $TARGETS; do
|
||||||
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ HOST=arm-apple-darwin11 PREFIX=/usr/local $BUILD_DEPS /deps ${DEPS_ARGS[@]}
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ HOST=arm-apple-darwin11 PREFIX=/usr/local $BUILD_DEPS /deps ${DEPS_ARGS[@]}
|
||||||
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go get $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" -d ./$PACK
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go get $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" -d ./$PACK
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "arm64" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "arm64" ]; then
|
||||||
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build $V $X "${IOSTAGS[@]}" --ldflags="$LDSTRIP $V $LD" $BM -o "/build/$NAME-ios-$PLATFORM-arm64`extension darwin`" ./$PACK
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_STATUS_IM" go build $V $X "${IOSTAGS[@]}" --ldflags="$LDSTRIP $V $LD" $BM -o "/build/$NAME-ios-$PLATFORM-arm64`extension darwin`" ./$PACK
|
||||||
fi
|
fi
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "framework" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "framework" ]; then
|
||||||
CGO_CFLAGS="-D IOS_DEPLOYMENT" CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" --buildmode=c-archive -o "/build-ios-fw/$NAME-ios-$PLATFORM-arm64.a" ./$PACK
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_STATUS_IM" go build $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" --buildmode=c-archive -o "/build-ios-fw/$NAME-ios-$PLATFORM-arm64.a" ./$PACK
|
||||||
fi
|
fi
|
||||||
echo "Cleaning up Go runtime for ios-$PLATFORM/arm64..."
|
echo "Cleaning up Go runtime for ios-$PLATFORM/arm64..."
|
||||||
rm -rf /usr/local/go/pkg/darwin_arm64
|
rm -rf /usr/local/go/pkg/darwin_arm64
|
||||||
|
@ -537,15 +539,16 @@ for TARGET in $TARGETS; do
|
||||||
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ HOST=arm-apple-darwin11 PREFIX=/usr/local $BUILD_DEPS /deps ${DEPS_ARGS[@]}
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ HOST=arm-apple-darwin11 PREFIX=/usr/local $BUILD_DEPS /deps ${DEPS_ARGS[@]}
|
||||||
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go get $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" -d ./$PACK
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go get $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" -d ./$PACK
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "amd64" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "amd64" ]; then
|
||||||
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build $V $X "${IOSTAGS[@]}" --ldflags="$LDSTRIP $V $LD" $BM -o "/build/$NAME-ios-$PLATFORM-x86_64`extension darwin`" ./$PACK
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_STATUS_IM" go build $V $X "${IOSTAGS[@]}" --ldflags="$LDSTRIP $V $LD" $BM -o "/build/$NAME-ios-$PLATFORM-x86_64`extension darwin`" ./$PACK
|
||||||
fi
|
fi
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "framework" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "framework" ]; then
|
||||||
CGO_CFLAGS="-D IOS_DEPLOYMENT" CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" --buildmode=c-archive -o "/build-ios-fw/$NAME-ios-$PLATFORM-x86_64.a" ./$PACK
|
CC=arm-apple-darwin11-clang CXX=arm-apple-darwin11-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 CGO_CFLAGS="$CGO_STATUS_IM" go build $V $X "${IOSTAGS[@]}" --ldflags="$V $LD" --buildmode=c-archive -o "/build-ios-fw/$NAME-ios-$PLATFORM-x86_64.a" ./$PACK
|
||||||
fi
|
fi
|
||||||
echo "Cleaning up Go runtime for ios-$PLATFORM/amd64..."
|
echo "Cleaning up Go runtime for ios-$PLATFORM/amd64..."
|
||||||
rm -rf /usr/local/go/pkg/darwin_amd64
|
rm -rf /usr/local/go/pkg/darwin_amd64
|
||||||
mv /usr/local/go/pkg/darwin_amd64_bak /usr/local/go/pkg/darwin_amd64
|
mv /usr/local/go/pkg/darwin_amd64_bak /usr/local/go/pkg/darwin_amd64
|
||||||
fi
|
fi
|
||||||
|
unset CGO_STATUS_IM
|
||||||
# Assemble the iOS framework from the built binaries
|
# Assemble the iOS framework from the built binaries
|
||||||
if [ $XGOARCH == "." ] || [ $XGOARCH == "framework" ]; then
|
if [ $XGOARCH == "." ] || [ $XGOARCH == "framework" ]; then
|
||||||
title=${NAME^}
|
title=${NAME^}
|
||||||
|
|
Loading…
Reference in New Issue