Merge pull request #35 from farazdagi/feature/rebase
Rebased against `zsfelfoldi/light-stable` + Major refactoring
This commit is contained in:
commit
40a3c76273
|
@ -13,8 +13,6 @@
|
|||
.ethtest
|
||||
*/**/*tx_database*
|
||||
*/**/*dapps*
|
||||
Godeps/_workspace/pkg
|
||||
Godeps/_workspace/bin
|
||||
|
||||
#*
|
||||
.#*
|
||||
|
@ -35,8 +33,9 @@ profile.cov
|
|||
.vagrant
|
||||
|
||||
# tests
|
||||
src/.ethereumtest/
|
||||
.ethereumtest/
|
||||
#
|
||||
# golang
|
||||
cover.out
|
||||
cover.html
|
||||
coverage.out
|
||||
coverage-all.out
|
||||
coverage.html
|
||||
|
|
39
Makefile
39
Makefile
|
@ -5,7 +5,7 @@ GOBIN = build/bin
|
|||
GO ?= latest
|
||||
|
||||
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 "Run \"build/bin/statusgo\" to view available commands"
|
||||
|
||||
|
@ -14,25 +14,48 @@ statusgo-cross: statusgo-android statusgo-ios
|
|||
@ls -ld $(GOBIN)/statusgo-*
|
||||
|
||||
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:"
|
||||
|
||||
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:"
|
||||
|
||||
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:"
|
||||
|
||||
xgo:
|
||||
build/env.sh go get github.com/karalabe/xgo
|
||||
|
||||
test:
|
||||
build/env.sh go test -v -coverprofile=cover.out ./src
|
||||
test-all:
|
||||
@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
|
||||
build/env.sh go tool cover -html=cover.out -o cover.html
|
||||
test: test-all
|
||||
|
||||
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:
|
||||
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
|
||||
mkdir -p "$WS1"
|
||||
cd "$WS1"
|
||||
ln -s "$ROOT/src/vendor" src
|
||||
ln -s "$ROOT/vendor" src
|
||||
cd "$ROOT"
|
||||
fi
|
||||
|
||||
|
|
|
@ -4,26 +4,26 @@ import "C"
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/whisper"
|
||||
"os"
|
||||
)
|
||||
|
||||
var emptyError = ""
|
||||
"github.com/ethereum/go-ethereum/whisper"
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/jail"
|
||||
)
|
||||
|
||||
//export CreateAccount
|
||||
func CreateAccount(password *C.char) *C.char {
|
||||
|
||||
// This is equivalent to creating an account from the command line,
|
||||
// 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 {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := AccountInfo{
|
||||
out := geth.AccountInfo{
|
||||
Address: address,
|
||||
PubKey: pubKey,
|
||||
Mnemonic: mnemonic,
|
||||
|
@ -37,15 +37,15 @@ func CreateAccount(password *C.char) *C.char {
|
|||
//export CreateChildAccount
|
||||
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 {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := AccountInfo{
|
||||
out := geth.AccountInfo{
|
||||
Address: address,
|
||||
PubKey: pubKey,
|
||||
Error: errString,
|
||||
|
@ -58,15 +58,15 @@ func CreateChildAccount(parentAddress, password *C.char) *C.char {
|
|||
//export RecoverAccount
|
||||
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 {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := AccountInfo{
|
||||
out := geth.AccountInfo{
|
||||
Address: address,
|
||||
PubKey: pubKey,
|
||||
Mnemonic: C.GoString(mnemonic),
|
||||
|
@ -81,15 +81,15 @@ func RecoverAccount(password, mnemonic *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
|
||||
// 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 {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := JSONError{
|
||||
out := geth.JSONError{
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
@ -101,15 +101,15 @@ func Login(address, password *C.char) *C.char {
|
|||
func Logout() *C.char {
|
||||
|
||||
// This is equivalent to clearing whisper identities
|
||||
err := logout()
|
||||
err := geth.Logout()
|
||||
|
||||
errString := emptyError
|
||||
errString := ""
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := JSONError{
|
||||
out := geth.JSONError{
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
@ -123,15 +123,15 @@ func UnlockAccount(address, password *C.char, seconds int) *C.char {
|
|||
// This is equivalent to unlocking an account from the command line,
|
||||
// just modified to unlock the account for the currently running geth node
|
||||
// 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 {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := JSONError{
|
||||
out := geth.JSONError{
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
@ -141,15 +141,15 @@ func UnlockAccount(address, password *C.char, seconds int) *C.char {
|
|||
|
||||
//export CompleteTransaction
|
||||
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 {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := CompleteTransactionResult{
|
||||
out := geth.CompleteTransactionResult{
|
||||
Hash: txHash.Hex(),
|
||||
Error: errString,
|
||||
}
|
||||
|
@ -160,17 +160,16 @@ func CompleteTransaction(id, password *C.char) *C.char {
|
|||
|
||||
//export StartNode
|
||||
func StartNode(datadir *C.char) *C.char {
|
||||
|
||||
// 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 {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := JSONError{
|
||||
out := geth.JSONError{
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
@ -178,33 +177,33 @@ func StartNode(datadir *C.char) *C.char {
|
|||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export parse
|
||||
func parse(chatId *C.char, js *C.char) *C.char {
|
||||
res := Parse(C.GoString(chatId), C.GoString(js))
|
||||
//export InitJail
|
||||
func InitJail(js *C.char) {
|
||||
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)
|
||||
}
|
||||
|
||||
//export call
|
||||
func call(chatId *C.char, path *C.char, params *C.char) *C.char {
|
||||
res := Call(C.GoString(chatId), C.GoString(path), C.GoString(params))
|
||||
//export Call
|
||||
func Call(chatId *C.char, path *C.char, params *C.char) *C.char {
|
||||
res := jail.GetInstance().Call(C.GoString(chatId), C.GoString(path), C.GoString(params))
|
||||
return C.CString(res)
|
||||
}
|
||||
|
||||
//export initJail
|
||||
func initJail(js *C.char) {
|
||||
Init(C.GoString(js))
|
||||
}
|
||||
|
||||
//export addPeer
|
||||
func addPeer(url *C.char) *C.char {
|
||||
success, err := doAddPeer(C.GoString(url))
|
||||
errString := emptyError
|
||||
//export AddPeer
|
||||
func AddPeer(url *C.char) *C.char {
|
||||
success, err := geth.GetNodeManager().AddPeer(C.GoString(url))
|
||||
errString := ""
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := AddPeerResult{
|
||||
out := geth.AddPeerResult{
|
||||
Success: success,
|
||||
Error: errString,
|
||||
}
|
||||
|
@ -213,24 +212,24 @@ func addPeer(url *C.char) *C.char {
|
|||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export addWhisperFilter
|
||||
func addWhisperFilter(filterJson *C.char) *C.char {
|
||||
//export AddWhisperFilter
|
||||
func AddWhisperFilter(filterJson *C.char) *C.char {
|
||||
|
||||
var id int
|
||||
var filter whisper.NewFilterArgs
|
||||
|
||||
err := json.Unmarshal([]byte(C.GoString(filterJson)), &filter)
|
||||
if err == nil {
|
||||
id = doAddWhisperFilter(filter)
|
||||
id = geth.AddWhisperFilter(filter)
|
||||
}
|
||||
|
||||
errString := emptyError
|
||||
errString := ""
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := AddWhisperFilterResult{
|
||||
out := geth.AddWhisperFilterResult{
|
||||
Id: id,
|
||||
Error: errString,
|
||||
}
|
||||
|
@ -240,14 +239,12 @@ func addWhisperFilter(filterJson *C.char) *C.char {
|
|||
|
||||
}
|
||||
|
||||
//export removeWhisperFilter
|
||||
func removeWhisperFilter(idFilter int) {
|
||||
|
||||
doRemoveWhisperFilter(idFilter)
|
||||
//export RemoveWhisperFilter
|
||||
func RemoveWhisperFilter(idFilter int) {
|
||||
geth.RemoveWhisperFilter(idFilter)
|
||||
}
|
||||
|
||||
//export clearWhisperFilters
|
||||
func clearWhisperFilters() {
|
||||
|
||||
doClearWhisperFilters()
|
||||
//export ClearWhisperFilters
|
||||
func ClearWhisperFilters() {
|
||||
geth.ClearWhisperFilters()
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
gitCommit = "rely on linker: -ldflags -X main.GitCommit"
|
||||
buildStamp = "rely on linker: -ldflags -X main.buildStamp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Status\nGit Commit: %s\nBuild Time: %s\n", gitCommit, buildStamp)
|
||||
}
|
|
@ -8,7 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/status-im/status-go/src/extkeys"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
)
|
||||
|
||||
func TestBIP32Vectors(t *testing.T) {
|
||||
|
@ -122,7 +122,7 @@ tests:
|
|||
}
|
||||
|
||||
if !extKey.IsPrivate {
|
||||
t.Errorf("Master node must feature private key")
|
||||
t.Error("Master node must feature private key")
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -416,7 +416,7 @@ func TestErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
// Generate a new key and neuter it to a public extended key.
|
||||
mnemonic := extkeys.NewMnemonic()
|
||||
mnemonic := extkeys.NewMnemonic(extkeys.Salt)
|
||||
|
||||
phrase, err := mnemonic.MnemonicPhrase(128, extkeys.EnglishLanguage)
|
||||
if err != nil {
|
||||
|
@ -505,12 +505,12 @@ func TestBIP44ChildDerivation(t *testing.T) {
|
|||
|
||||
extKey, err := extkeys.NewKeyFromString(keyString)
|
||||
if err != nil {
|
||||
t.Errorf("NewKeyFromString: cannot create extended key")
|
||||
t.Error("NewKeyFromString: cannot create extended key")
|
||||
}
|
||||
|
||||
accounKey1, err := extKey.BIP44Child(extkeys.CoinTypeETH, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error dering BIP44-compliant key")
|
||||
t.Error("Error dering BIP44-compliant key")
|
||||
}
|
||||
if accounKey1.String() != derivedKey1String {
|
||||
t.Errorf("BIP44Child: key mismatch -- got: %v, want: %v", accounKey1.String(), derivedKey1String)
|
||||
|
@ -519,7 +519,7 @@ func TestBIP44ChildDerivation(t *testing.T) {
|
|||
|
||||
accounKey2, err := extKey.BIP44Child(extkeys.CoinTypeETH, 1)
|
||||
if err != nil {
|
||||
t.Errorf("Error dering BIP44-compliant key")
|
||||
t.Error("Error dering BIP44-compliant key")
|
||||
}
|
||||
if accounKey2.String() != derivedKey2String {
|
||||
t.Errorf("BIP44Child: key mismatch -- got: %v, want: %v", accounKey2.String(), derivedKey2String)
|
File diff suppressed because one or more lines are too long
|
@ -1,10 +1,12 @@
|
|||
package extkeys
|
||||
package extkeys_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
)
|
||||
|
||||
type VectorsFile struct {
|
||||
|
@ -19,13 +21,13 @@ type Vector struct {
|
|||
// TestMnemonicPhrase
|
||||
func TestMnemonicPhrase(t *testing.T) {
|
||||
|
||||
mnemonic := NewMnemonic()
|
||||
mnemonic := extkeys.NewMnemonic(extkeys.Salt)
|
||||
|
||||
// test mnemonic generation
|
||||
t.Logf("Test mnemonic generation:")
|
||||
t.Log("Test mnemonic generation:")
|
||||
for _, language := range mnemonic.AvailableLanguages() {
|
||||
phrase, err := mnemonic.MnemonicPhrase(128, language)
|
||||
t.Logf("Mnemonic (%s): %s", Languages[language], phrase)
|
||||
t.Logf("Mnemonic (%s): %s", extkeys.Languages[language], phrase)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not create seed: %s", err)
|
||||
|
@ -42,11 +44,11 @@ func TestMnemonicPhrase(t *testing.T) {
|
|||
t.Error(err)
|
||||
}
|
||||
|
||||
t.Logf("Test against pre-computed seed vectors:")
|
||||
t.Log("Test against pre-computed seed vectors:")
|
||||
stats := map[string]int{}
|
||||
for _, vector := range vectorsFile.vectors {
|
||||
stats[vector.language] += 1
|
||||
mnemonic.salt = vector.salt
|
||||
mnemonic := extkeys.NewMnemonic(vector.salt)
|
||||
seed := mnemonic.MnemonicSeed(vector.mnemonic, vector.password)
|
||||
if fmt.Sprintf("%x", seed) != vector.seed {
|
||||
t.Errorf("Test failed (%s): incorrect seed (%x) generated (expected: %s)", vector.language, seed, vector.seed)
|
|
@ -1,30 +1,16 @@
|
|||
package main
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
extern bool GethServiceSignalEvent( const char *jsonEvent );
|
||||
*/
|
||||
import "C"
|
||||
package geth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"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/status"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
errextra "github.com/pkg/errors"
|
||||
"github.com/status-im/status-go/src/extkeys"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
)
|
||||
|
||||
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")
|
||||
ErrAccountToKeyMappingFailure = errors.New("cannot retreive a valid key for a given account")
|
||||
ErrUnlockCalled = errors.New("no need to unlock accounts, login instead")
|
||||
|
@ -32,32 +18,25 @@ var (
|
|||
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
|
||||
ErrWhisperNoIdentityFound = errors.New("failed to locate identity previously injected into Whisper")
|
||||
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
|
||||
// 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
|
||||
// sub-account derivations)
|
||||
func createAccount(password string) (address, pubKey, mnemonic string, err error) {
|
||||
if currentNode == nil {
|
||||
return "", "", "", ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if accountManager == nil {
|
||||
return "", "", "", ErrInvalidAccountManager
|
||||
}
|
||||
|
||||
func CreateAccount(password string) (address, pubKey, mnemonic string, err error) {
|
||||
// generate mnemonic phrase
|
||||
m := extkeys.NewMnemonic()
|
||||
m := extkeys.NewMnemonic(extkeys.Salt)
|
||||
mnemonic, err = m.MnemonicPhrase(128, extkeys.EnglishLanguage)
|
||||
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)
|
||||
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
||||
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
|
||||
|
@ -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.
|
||||
// CKD#2 is used as root for master accounts (when parentAddress is "").
|
||||
// Otherwise (when parentAddress != ""), child is derived directly from parent.
|
||||
func createChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
||||
if currentNode == nil {
|
||||
return "", "", ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if accountManager == nil {
|
||||
return "", "", ErrInvalidAccountManager
|
||||
func CreateChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
||||
nodeManager := GetNodeManager()
|
||||
accountManager, err := nodeManager.AccountManager()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if parentAddress == "" { // by default derive from currently selected account
|
||||
parentAddress = selectedAddress
|
||||
parentAddress = nodeManager.SelectedAddress
|
||||
}
|
||||
|
||||
if parentAddress == "" {
|
||||
|
@ -123,20 +100,12 @@ func createChildAccount(parentAddress, password string) (address, pubKey string,
|
|||
|
||||
// recoverAccount re-creates master key using given details.
|
||||
// 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) {
|
||||
if currentNode == nil {
|
||||
return "", "", ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if accountManager == nil {
|
||||
return "", "", ErrInvalidAccountManager
|
||||
}
|
||||
|
||||
func RecoverAccount(password, mnemonic string) (address, pubKey string, err error) {
|
||||
// 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))
|
||||
if err != nil {
|
||||
return "", "", errextra.Wrap(err, "Can not create master extended key")
|
||||
return "", "", ErrInvalidMasterKeyCreated
|
||||
}
|
||||
|
||||
// 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
|
||||
// using provided password. Once verification is done, decrypted key is injected into Whisper (as a single identity,
|
||||
// all previous identities are removed).
|
||||
func selectAccount(address, password string) error {
|
||||
if currentNode == nil {
|
||||
return ErrInvalidGethNode
|
||||
func SelectAccount(address, password string) error {
|
||||
nodeManager := GetNodeManager()
|
||||
accountManager, err := nodeManager.AccountManager()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if accountManager == nil {
|
||||
return ErrInvalidAccountManager
|
||||
}
|
||||
account, err := utils.MakeAddress(accountManager, address)
|
||||
if err != nil {
|
||||
return ErrAddressToAccountMappingFailure
|
||||
|
@ -169,34 +137,35 @@ func selectAccount(address, password string) error {
|
|||
return fmt.Errorf("%s: %v", ErrAccountToKeyMappingFailure.Error(), err)
|
||||
}
|
||||
|
||||
if whisperService == nil {
|
||||
return ErrInvalidWhisperService
|
||||
whisperService, err := nodeManager.WhisperService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := whisperService.InjectIdentity(accountKey.PrivateKey); err != nil {
|
||||
return ErrWhisperIdentityInjectionFailure
|
||||
}
|
||||
|
||||
// persist address for easier recovery of currently selected key (from Whisper)
|
||||
selectedAddress = address
|
||||
nodeManager.SelectedAddress = address
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// logout clears whisper identities
|
||||
func logout() error {
|
||||
if currentNode == nil {
|
||||
return ErrInvalidGethNode
|
||||
}
|
||||
if whisperService == nil {
|
||||
return ErrInvalidWhisperService
|
||||
func Logout() error {
|
||||
nodeManager := GetNodeManager()
|
||||
whisperService, err := nodeManager.WhisperService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := whisperService.ClearIdentities()
|
||||
err = whisperService.ClearIdentities()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %v", ErrWhisperClearIdentitiesFailure, err)
|
||||
}
|
||||
|
||||
selectedAddress = ""
|
||||
nodeManager.SelectedAddress = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -204,85 +173,31 @@ func logout() error {
|
|||
// unlockAccount unlocks an existing account for a certain duration and
|
||||
// inject the account as a whisper identity if the account was created as
|
||||
// a whisper enabled account
|
||||
func unlockAccount(address, password string, seconds int) error {
|
||||
if currentNode == nil {
|
||||
return ErrInvalidGethNode
|
||||
}
|
||||
|
||||
func UnlockAccount(address, password string, seconds int) error {
|
||||
return ErrUnlockCalled
|
||||
}
|
||||
|
||||
// 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.
|
||||
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)
|
||||
account, err := accountManager.ImportExtendedKey(extKey, password)
|
||||
if err != nil {
|
||||
return "", "", errextra.Wrap(err, "Account manager could not create the account")
|
||||
return "", "", err
|
||||
}
|
||||
address = fmt.Sprintf("%x", account.Address)
|
||||
|
||||
// obtain public key to return
|
||||
account, key, err := accountManager.AccountDecryptedKey(account, password)
|
||||
if err != nil {
|
||||
return address, "", errextra.Wrap(err, "Could not recover the key")
|
||||
return address, "", err
|
||||
}
|
||||
pubKey = common.ToHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
|
||||
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,299 @@
|
|||
package geth
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
extern bool StatusServiceSignalEvent( const char *jsonEvent );
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"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)
|
||||
|
||||
EventNodeStarted = "node.started"
|
||||
)
|
||||
|
||||
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 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
|
||||
nodeStarted chan struct{} // channel to wait for node to start
|
||||
}
|
||||
|
||||
var (
|
||||
nodeManagerInstance *NodeManager
|
||||
createOnce sync.Once
|
||||
)
|
||||
|
||||
func NewNodeManager(datadir string, rpcport int) *NodeManager {
|
||||
createOnce.Do(func() {
|
||||
nodeManagerInstance = &NodeManager{}
|
||||
nodeManagerInstance.MakeNode(datadir, rpcport)
|
||||
})
|
||||
|
||||
return nodeManagerInstance
|
||||
}
|
||||
|
||||
func GetNodeManager() *NodeManager {
|
||||
return nodeManagerInstance
|
||||
}
|
||||
|
||||
// createAndStartNode creates a node entity and starts the
|
||||
// node running locally exposing given RPC port
|
||||
func CreateAndRunNode(datadir string, rpcport int) error {
|
||||
nodeManager := NewNodeManager(datadir, rpcport)
|
||||
|
||||
if nodeManager.HasNode() {
|
||||
nodeManager.RunNode()
|
||||
|
||||
<-nodeManager.nodeStarted // block until node is ready
|
||||
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()
|
||||
m.nodeStarted = make(chan struct{})
|
||||
|
||||
return m.currentNode
|
||||
}
|
||||
|
||||
// StartNode starts a geth node entity
|
||||
func (m *NodeManager) RunNode() {
|
||||
go func() {
|
||||
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.onNodeStarted() // node started, notify listeners
|
||||
m.currentNode.Wait()
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *NodeManager) onNodeStarted() {
|
||||
// notify local listener
|
||||
m.nodeStarted <- struct{}{}
|
||||
close(m.nodeStarted)
|
||||
|
||||
// send signal up to native app
|
||||
event := GethEvent{
|
||||
Type: EventNodeStarted,
|
||||
Event: struct{}{},
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(&event)
|
||||
C.StatusServiceSignalEvent(C.CString(string(body)))
|
||||
}
|
||||
|
||||
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 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,45 @@
|
|||
package geth_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/geth"
|
||||
)
|
||||
|
||||
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 TestMain(m *testing.M) {
|
||||
// make sure you panic if node start signal is not received
|
||||
signalRecieved := make(chan struct{}, 1)
|
||||
abortPanic := make(chan bool, 1)
|
||||
geth.PanicAfter(10*time.Second, abortPanic, "TestNodeSetup")
|
||||
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
if jsonEvent == `{"type":"node.started","event":{}}` {
|
||||
signalRecieved <- struct{}{}
|
||||
}
|
||||
})
|
||||
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
|
||||
<-signalRecieved // block and wait for either panic or successful signal
|
||||
abortPanic <- true
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
|
@ -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, "TestQueuedTransactions")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = common.Hash{}
|
||||
geth.SetDefaultNodeNotificationHandler(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 main
|
||||
package geth
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
|
@ -35,7 +35,7 @@ type WhisperMessageEvent struct {
|
|||
}
|
||||
|
||||
type SendTransactionEvent struct {
|
||||
Id string `json:"hash"`
|
||||
Id string `json:"id"`
|
||||
Args status.SendTxArgs `json:"args"`
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package geth
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
extern bool StatusServiceSignalEvent(const char *jsonEvent);
|
||||
*/
|
||||
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
|
||||
)
|
||||
|
||||
type NodeNotificationHandler func(jsonEvent string)
|
||||
|
||||
var notificationHandler NodeNotificationHandler = func(jsonEvent string) { // internal signal handler (used in tests)
|
||||
glog.V(logger.Info).Infof("notification received (default notification handler): %s\n", jsonEvent)
|
||||
}
|
||||
|
||||
func SetDefaultNodeNotificationHandler(fn NodeNotificationHandler) {
|
||||
notificationHandler = fn
|
||||
}
|
||||
|
||||
//export NotifyNode
|
||||
func NotifyNode(jsonEvent *C.char) {
|
||||
notificationHandler(C.GoString(jsonEvent))
|
||||
}
|
||||
|
||||
// export TriggerTestSignal
|
||||
func TriggerTestSignal() {
|
||||
C.StatusServiceSignalEvent(C.CString(`{"answer": 42}`))
|
||||
}
|
||||
|
||||
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
|
||||
CreateAndRunNode(dataDir, 8546) // to avoid conflicts with running react-native app, run on different port
|
||||
|
||||
manager = GetNodeManager()
|
||||
if !manager.HasNode() {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
if !manager.HasClientRestartWrapper() {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
if !manager.HasWhisperService() {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
if !manager.HasLightEthereumService() {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
|
||||
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, desc string) {
|
||||
// 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("whatever you were doing takes toooo long: " + desc)
|
||||
}
|
||||
}()
|
||||
}
|
|
@ -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 = `
|
||||
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
|
177
src/main.go
177
src/main.go
|
@ -1,177 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"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"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
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
|
||||
accountSync *[]node.Service // the object used to sync accounts between geth services
|
||||
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
|
||||
gitCommit = "rely on linker: -ldflags -X main.GitCommit"
|
||||
buildStamp = "rely on linker: -ldflags -X main.buildStamp"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDataDirPreprocessingFailed = errors.New("Failed to pre-process data directory")
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Status\nGit Commit: %s\nBuild Time: %s\n", gitCommit, buildStamp)
|
||||
}
|
||||
|
||||
// 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, accountSync = utils.MakeSystemNode(clientIdentifier, vString, rConfig, makeDefaultExtra(), c)
|
||||
|
||||
return currentNode
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
260
src/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/backends/remote.go
generated
vendored
260
src/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/backends/remote.go
generated
vendored
|
@ -1,260 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// This nil assignment ensures compile time that rpcBackend implements bind.ContractBackend.
|
||||
var _ bind.ContractBackend = (*rpcBackend)(nil)
|
||||
|
||||
// rpcBackend implements bind.ContractBackend, and acts as the data provider to
|
||||
// Ethereum contracts bound to Go structs. It uses an RPC connection to delegate
|
||||
// all its functionality.
|
||||
//
|
||||
// Note: The current implementation is a blocking one. This should be replaced
|
||||
// by a proper async version when a real RPC client is created.
|
||||
type rpcBackend struct {
|
||||
client rpc.Client // RPC client connection to interact with an API server
|
||||
autoid uint32 // ID number to use for the next API request
|
||||
lock sync.Mutex // Singleton access until we get to request multiplexing
|
||||
}
|
||||
|
||||
// NewRPCBackend creates a new binding backend to an RPC provider that can be
|
||||
// used to interact with remote contracts.
|
||||
func NewRPCBackend(client rpc.Client) bind.ContractBackend {
|
||||
return &rpcBackend{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// request is a JSON RPC request package assembled internally from the client
|
||||
// method calls.
|
||||
type request struct {
|
||||
JSONRPC string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
||||
ID int `json:"id"` // Auto incrementing ID number for this request
|
||||
Method string `json:"method"` // Remote procedure name to invoke on the server
|
||||
Params []interface{} `json:"params"` // List of parameters to pass through (keep types simple)
|
||||
}
|
||||
|
||||
// response is a JSON RPC response package sent back from the API server.
|
||||
type response struct {
|
||||
JSONRPC string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
||||
ID int `json:"id"` // Auto incrementing ID number for this request
|
||||
Error *failure `json:"error"` // Any error returned by the remote side
|
||||
Result json.RawMessage `json:"result"` // Whatever the remote side sends us in reply
|
||||
}
|
||||
|
||||
// failure is a JSON RPC response error field sent back from the API server.
|
||||
type failure struct {
|
||||
Code int `json:"code"` // JSON RPC error code associated with the failure
|
||||
Message string `json:"message"` // Specific error message of the failure
|
||||
}
|
||||
|
||||
// request forwards an API request to the RPC server, and parses the response.
|
||||
//
|
||||
// This is currently painfully non-concurrent, but it will have to do until we
|
||||
// find the time for niceties like this :P
|
||||
func (b *rpcBackend) request(ctx context.Context, method string, params []interface{}) (json.RawMessage, error) {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
// Ugly hack to serialize an empty list properly
|
||||
if params == nil {
|
||||
params = []interface{}{}
|
||||
}
|
||||
// Assemble the request object
|
||||
reqID := int(atomic.AddUint32(&b.autoid, 1))
|
||||
req := &request{
|
||||
JSONRPC: "2.0",
|
||||
ID: reqID,
|
||||
Method: method,
|
||||
Params: params,
|
||||
}
|
||||
if err := b.client.Send(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := new(response)
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
errc <- b.client.Recv(res)
|
||||
}()
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
if res.Error != nil {
|
||||
if res.Error.Message == bind.ErrNoCode.Error() {
|
||||
return nil, bind.ErrNoCode
|
||||
}
|
||||
return nil, fmt.Errorf("remote error: %s", res.Error.Message)
|
||||
}
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
// HasCode implements ContractVerifier.HasCode by retrieving any code associated
|
||||
// with the contract from the remote node, and checking its size.
|
||||
func (b *rpcBackend) HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error) {
|
||||
// Execute the RPC code retrieval
|
||||
block := "latest"
|
||||
if pending {
|
||||
block = "pending"
|
||||
}
|
||||
res, err := b.request(ctx, "eth_getCode", []interface{}{contract.Hex(), block})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var hex string
|
||||
if err := json.Unmarshal(res, &hex); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Convert the response back to a Go byte slice and return
|
||||
return len(common.FromHex(hex)) > 0, nil
|
||||
}
|
||||
|
||||
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
|
||||
// a contract call to the remote node, returning the reply to for local processing.
|
||||
func (b *rpcBackend) ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) {
|
||||
// Pack up the request into an RPC argument
|
||||
args := struct {
|
||||
To common.Address `json:"to"`
|
||||
Data string `json:"data"`
|
||||
}{
|
||||
To: contract,
|
||||
Data: common.ToHex(data),
|
||||
}
|
||||
// Execute the RPC call and retrieve the response
|
||||
block := "latest"
|
||||
if pending {
|
||||
block = "pending"
|
||||
}
|
||||
res, err := b.request(ctx, "eth_call", []interface{}{args, block})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var hex string
|
||||
if err := json.Unmarshal(res, &hex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert the response back to a Go byte slice and return
|
||||
return common.FromHex(hex), nil
|
||||
}
|
||||
|
||||
// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, delegating
|
||||
// the current account nonce retrieval to the remote node.
|
||||
func (b *rpcBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) {
|
||||
res, err := b.request(ctx, "eth_getTransactionCount", []interface{}{account.Hex(), "pending"})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var hex string
|
||||
if err := json.Unmarshal(res, &hex); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nonce, ok := new(big.Int).SetString(hex, 0)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("invalid nonce hex: %s", hex)
|
||||
}
|
||||
return nonce.Uint64(), nil
|
||||
}
|
||||
|
||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice, delegating the
|
||||
// gas price oracle request to the remote node.
|
||||
func (b *rpcBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||
res, err := b.request(ctx, "eth_gasPrice", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var hex string
|
||||
if err := json.Unmarshal(res, &hex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
price, ok := new(big.Int).SetString(hex, 0)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid price hex: %s", hex)
|
||||
}
|
||||
return price, nil
|
||||
}
|
||||
|
||||
// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, delegating
|
||||
// the gas estimation to the remote node.
|
||||
func (b *rpcBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
|
||||
// Pack up the request into an RPC argument
|
||||
args := struct {
|
||||
From common.Address `json:"from"`
|
||||
To *common.Address `json:"to"`
|
||||
Value *rpc.HexNumber `json:"value"`
|
||||
Data string `json:"data"`
|
||||
}{
|
||||
From: sender,
|
||||
To: contract,
|
||||
Data: common.ToHex(data),
|
||||
Value: rpc.NewHexNumber(value),
|
||||
}
|
||||
// Execute the RPC call and retrieve the response
|
||||
res, err := b.request(ctx, "eth_estimateGas", []interface{}{args})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var hex string
|
||||
if err := json.Unmarshal(res, &hex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
estimate, ok := new(big.Int).SetString(hex, 0)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid estimate hex: %s", hex)
|
||||
}
|
||||
return estimate, nil
|
||||
}
|
||||
|
||||
// SendTransaction implements ContractTransactor.SendTransaction, delegating the
|
||||
// raw transaction injection to the remote node.
|
||||
func (b *rpcBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
||||
data, err := rlp.EncodeToBytes(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := b.request(ctx, "eth_sendRawTransaction", []interface{}{common.ToHex(data)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var hex string
|
||||
if err := json.Unmarshal(res, &hex); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// NewRemoteRPCClient returns a RPC client which connects to a running geth instance.
|
||||
// Depending on the given context this can either be a IPC or a HTTP client.
|
||||
func NewRemoteRPCClient(ctx *cli.Context) (rpc.Client, error) {
|
||||
if ctx.Args().Present() {
|
||||
endpoint := ctx.Args().First()
|
||||
return NewRemoteRPCClientFromString(endpoint)
|
||||
}
|
||||
// use IPC by default
|
||||
return rpc.NewIPCClient(node.DefaultIPCEndpoint())
|
||||
}
|
||||
|
||||
// NewRemoteRPCClientFromString returns a RPC client which connects to the given
|
||||
// endpoint. It must start with either `ipc:` or `rpc:` (HTTP).
|
||||
func NewRemoteRPCClientFromString(endpoint string) (rpc.Client, error) {
|
||||
if strings.HasPrefix(endpoint, "ipc:") {
|
||||
return rpc.NewIPCClient(endpoint[4:])
|
||||
}
|
||||
if strings.HasPrefix(endpoint, "rpc:") {
|
||||
return rpc.NewHTTPClient(endpoint[4:])
|
||||
}
|
||||
if strings.HasPrefix(endpoint, "http://") {
|
||||
return rpc.NewHTTPClient(endpoint)
|
||||
}
|
||||
if strings.HasPrefix(endpoint, "ws:") {
|
||||
return rpc.NewWSClient(endpoint)
|
||||
}
|
||||
return nil, fmt.Errorf("invalid endpoint")
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
versionRegexp = regexp.MustCompile("[0-9]+\\.[0-9]+\\.[0-9]+")
|
||||
legacyRegexp = regexp.MustCompile("0\\.(9\\..*|1\\.[01])")
|
||||
paramsLegacy = []string{
|
||||
"--binary", // Request to output the contract in binary (hexadecimal).
|
||||
"file", //
|
||||
"--json-abi", // Request to output the contract's JSON ABI interface.
|
||||
"file", //
|
||||
"--natspec-user", // Request to output the contract's Natspec user documentation.
|
||||
"file", //
|
||||
"--natspec-dev", // Request to output the contract's Natspec developer documentation.
|
||||
"file",
|
||||
"--add-std",
|
||||
"1",
|
||||
}
|
||||
paramsNew = []string{
|
||||
"--bin", // Request to output the contract in binary (hexadecimal).
|
||||
"--abi", // Request to output the contract's JSON ABI interface.
|
||||
"--userdoc", // Request to output the contract's Natspec user documentation.
|
||||
"--devdoc", // Request to output the contract's Natspec developer documentation.
|
||||
"--add-std", // include standard lib contracts
|
||||
"--optimize", // code optimizer switched on
|
||||
"-o", // output directory
|
||||
}
|
||||
)
|
||||
|
||||
type Contract struct {
|
||||
Code string `json:"code"`
|
||||
Info ContractInfo `json:"info"`
|
||||
}
|
||||
|
||||
type ContractInfo struct {
|
||||
Source string `json:"source"`
|
||||
Language string `json:"language"`
|
||||
LanguageVersion string `json:"languageVersion"`
|
||||
CompilerVersion string `json:"compilerVersion"`
|
||||
CompilerOptions string `json:"compilerOptions"`
|
||||
AbiDefinition interface{} `json:"abiDefinition"`
|
||||
UserDoc interface{} `json:"userDoc"`
|
||||
DeveloperDoc interface{} `json:"developerDoc"`
|
||||
}
|
||||
|
||||
type Solidity struct {
|
||||
solcPath string
|
||||
version string
|
||||
fullVersion string
|
||||
legacy bool
|
||||
}
|
||||
|
||||
func New(solcPath string) (sol *Solidity, err error) {
|
||||
// set default solc
|
||||
if len(solcPath) == 0 {
|
||||
solcPath = "solc"
|
||||
}
|
||||
solcPath, err = exec.LookPath(solcPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command(solcPath, "--version")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fullVersion := out.String()
|
||||
version := versionRegexp.FindString(fullVersion)
|
||||
legacy := legacyRegexp.MatchString(version)
|
||||
|
||||
sol = &Solidity{
|
||||
solcPath: solcPath,
|
||||
version: version,
|
||||
fullVersion: fullVersion,
|
||||
legacy: legacy,
|
||||
}
|
||||
glog.V(logger.Info).Infoln(sol.Info())
|
||||
return
|
||||
}
|
||||
|
||||
func (sol *Solidity) Info() string {
|
||||
return fmt.Sprintf("%s\npath: %s", sol.fullVersion, sol.solcPath)
|
||||
}
|
||||
|
||||
func (sol *Solidity) Version() string {
|
||||
return sol.version
|
||||
}
|
||||
|
||||
// Compile builds and returns all the contracts contained within a source string.
|
||||
func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
|
||||
// Short circuit if no source code was specified
|
||||
if len(source) == 0 {
|
||||
return nil, errors.New("solc: empty source string")
|
||||
}
|
||||
// Create a safe place to dump compilation output
|
||||
wd, err := ioutil.TempDir("", "solc")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("solc: failed to create temporary build folder: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(wd)
|
||||
|
||||
// Assemble the compiler command, change to the temp folder and capture any errors
|
||||
stderr := new(bytes.Buffer)
|
||||
|
||||
var params []string
|
||||
if sol.legacy {
|
||||
params = paramsLegacy
|
||||
} else {
|
||||
params = paramsNew
|
||||
params = append(params, wd)
|
||||
}
|
||||
compilerOptions := strings.Join(params, " ")
|
||||
|
||||
cmd := exec.Command(sol.solcPath, params...)
|
||||
cmd.Stdin = strings.NewReader(source)
|
||||
cmd.Stderr = stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("solc: %v\n%s", err, string(stderr.Bytes()))
|
||||
}
|
||||
// Sanity check that something was actually built
|
||||
matches, _ := filepath.Glob(filepath.Join(wd, "*.bin*"))
|
||||
if len(matches) < 1 {
|
||||
return nil, fmt.Errorf("solc: no build results found")
|
||||
}
|
||||
// Compilation succeeded, assemble and return the contracts
|
||||
contracts := make(map[string]*Contract)
|
||||
for _, path := range matches {
|
||||
_, file := filepath.Split(path)
|
||||
base := strings.Split(file, ".")[0]
|
||||
|
||||
// Parse the individual compilation results (code binary, ABI definitions, user and dev docs)
|
||||
var binary []byte
|
||||
binext := ".bin"
|
||||
if sol.legacy {
|
||||
binext = ".binary"
|
||||
}
|
||||
if binary, err = ioutil.ReadFile(filepath.Join(wd, base+binext)); err != nil {
|
||||
return nil, fmt.Errorf("solc: error reading compiler output for code: %v", err)
|
||||
}
|
||||
|
||||
var abi interface{}
|
||||
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".abi")); err != nil {
|
||||
return nil, fmt.Errorf("solc: error reading abi definition: %v", err)
|
||||
} else if err = json.Unmarshal(blob, &abi); err != nil {
|
||||
return nil, fmt.Errorf("solc: error parsing abi definition: %v", err)
|
||||
}
|
||||
|
||||
var userdoc interface{}
|
||||
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docuser")); err != nil {
|
||||
return nil, fmt.Errorf("solc: error reading user doc: %v", err)
|
||||
} else if err = json.Unmarshal(blob, &userdoc); err != nil {
|
||||
return nil, fmt.Errorf("solc: error parsing user doc: %v", err)
|
||||
}
|
||||
|
||||
var devdoc interface{}
|
||||
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docdev")); err != nil {
|
||||
return nil, fmt.Errorf("solc: error reading dev doc: %v", err)
|
||||
} else if err = json.Unmarshal(blob, &devdoc); err != nil {
|
||||
return nil, fmt.Errorf("solc: error parsing dev doc: %v", err)
|
||||
}
|
||||
// Assemble the final contract
|
||||
contracts[base] = &Contract{
|
||||
Code: "0x" + string(binary),
|
||||
Info: ContractInfo{
|
||||
Source: source,
|
||||
Language: "Solidity",
|
||||
LanguageVersion: sol.version,
|
||||
CompilerVersion: sol.version,
|
||||
CompilerOptions: compilerOptions,
|
||||
AbiDefinition: abi,
|
||||
UserDoc: userdoc,
|
||||
DeveloperDoc: devdoc,
|
||||
},
|
||||
}
|
||||
}
|
||||
return contracts, nil
|
||||
}
|
||||
|
||||
func SaveInfo(info *ContractInfo, filename string) (contenthash common.Hash, err error) {
|
||||
infojson, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
contenthash = common.BytesToHash(crypto.Keccak256(infojson))
|
||||
err = ioutil.WriteFile(filename, infojson, 0600)
|
||||
return
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// PublicDownloaderAPI provides an API which gives information about the current synchronisation status.
|
||||
// It offers only methods that operates on data that can be available to anyone without security risks.
|
||||
type PublicDownloaderAPI struct {
|
||||
d *Downloader
|
||||
mux *event.TypeMux
|
||||
muSyncSubscriptions sync.Mutex
|
||||
syncSubscriptions map[string]rpc.Subscription
|
||||
}
|
||||
|
||||
// NewPublicDownloaderAPI create a new PublicDownloaderAPI.
|
||||
func NewPublicDownloaderAPI(d *Downloader, m *event.TypeMux) *PublicDownloaderAPI {
|
||||
api := &PublicDownloaderAPI{d: d, mux: m, syncSubscriptions: make(map[string]rpc.Subscription)}
|
||||
|
||||
go api.run()
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
func (api *PublicDownloaderAPI) run() {
|
||||
sub := api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{})
|
||||
|
||||
for event := range sub.Chan() {
|
||||
var notification interface{}
|
||||
|
||||
switch event.Data.(type) {
|
||||
case StartEvent:
|
||||
result := &SyncingResult{Syncing: true}
|
||||
result.Status.Origin, result.Status.Current, result.Status.Height, result.Status.Pulled, result.Status.Known = api.d.Progress()
|
||||
notification = result
|
||||
case DoneEvent, FailedEvent:
|
||||
notification = false
|
||||
}
|
||||
|
||||
api.muSyncSubscriptions.Lock()
|
||||
for id, sub := range api.syncSubscriptions {
|
||||
if sub.Notify(notification) == rpc.ErrNotificationNotFound {
|
||||
delete(api.syncSubscriptions, id)
|
||||
}
|
||||
}
|
||||
api.muSyncSubscriptions.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Progress gives progress indications when the node is synchronising with the Ethereum network.
|
||||
type Progress struct {
|
||||
Origin uint64 `json:"startingBlock"`
|
||||
Current uint64 `json:"currentBlock"`
|
||||
Height uint64 `json:"highestBlock"`
|
||||
Pulled uint64 `json:"pulledStates"`
|
||||
Known uint64 `json:"knownStates"`
|
||||
}
|
||||
|
||||
// SyncingResult provides information about the current synchronisation status for this node.
|
||||
type SyncingResult struct {
|
||||
Syncing bool `json:"syncing"`
|
||||
Status Progress `json:"status"`
|
||||
}
|
||||
|
||||
// Syncing provides information when this nodes starts synchronising with the Ethereum network and when it's finished.
|
||||
func (api *PublicDownloaderAPI) Syncing(ctx context.Context) (rpc.Subscription, error) {
|
||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||
if !supported {
|
||||
return nil, rpc.ErrNotificationsUnsupported
|
||||
}
|
||||
|
||||
subscription, err := notifier.NewSubscription(func(id string) {
|
||||
api.muSyncSubscriptions.Lock()
|
||||
delete(api.syncSubscriptions, id)
|
||||
api.muSyncSubscriptions.Unlock()
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api.muSyncSubscriptions.Lock()
|
||||
api.syncSubscriptions[subscription.ID()] = subscription
|
||||
api.muSyncSubscriptions.Unlock()
|
||||
|
||||
return subscription, nil
|
||||
}
|
|
@ -1,655 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
filterTickerTime = 5 * time.Minute
|
||||
)
|
||||
|
||||
// byte will be inferred
|
||||
const (
|
||||
unknownFilterTy = iota
|
||||
blockFilterTy
|
||||
transactionFilterTy
|
||||
logFilterTy
|
||||
)
|
||||
|
||||
// PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
|
||||
// information related to the Ethereum protocol such als blocks, transactions and logs.
|
||||
type PublicFilterAPI struct {
|
||||
apiBackend ethapi.Backend
|
||||
|
||||
quit chan struct{}
|
||||
chainDb ethdb.Database
|
||||
mux *event.TypeMux
|
||||
|
||||
filterManager *FilterSystem
|
||||
|
||||
filterMapMu sync.RWMutex
|
||||
filterMapping map[string]int // maps between filter internal filter identifiers and external filter identifiers
|
||||
|
||||
logMu sync.RWMutex
|
||||
logQueue map[int]*logQueue
|
||||
|
||||
blockMu sync.RWMutex
|
||||
blockQueue map[int]*hashQueue
|
||||
|
||||
transactionMu sync.RWMutex
|
||||
transactionQueue map[int]*hashQueue
|
||||
|
||||
transactMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewPublicFilterAPI returns a new PublicFilterAPI instance.
|
||||
func NewPublicFilterAPI(apiBackend ethapi.Backend) *PublicFilterAPI {
|
||||
svc := &PublicFilterAPI{
|
||||
apiBackend: apiBackend,
|
||||
mux: apiBackend.EventMux(),
|
||||
chainDb: apiBackend.ChainDb(),
|
||||
filterManager: NewFilterSystem(apiBackend.EventMux()),
|
||||
filterMapping: make(map[string]int),
|
||||
logQueue: make(map[int]*logQueue),
|
||||
blockQueue: make(map[int]*hashQueue),
|
||||
transactionQueue: make(map[int]*hashQueue),
|
||||
}
|
||||
go svc.start()
|
||||
return svc
|
||||
}
|
||||
|
||||
// Stop quits the work loop.
|
||||
func (s *PublicFilterAPI) Stop() {
|
||||
close(s.quit)
|
||||
}
|
||||
|
||||
// start the work loop, wait and process events.
|
||||
func (s *PublicFilterAPI) start() {
|
||||
timer := time.NewTicker(2 * time.Second)
|
||||
defer timer.Stop()
|
||||
done:
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
s.logMu.Lock()
|
||||
for id, filter := range s.logQueue {
|
||||
if time.Since(filter.timeout) > filterTickerTime {
|
||||
s.filterManager.Remove(id)
|
||||
delete(s.logQueue, id)
|
||||
}
|
||||
}
|
||||
s.logMu.Unlock()
|
||||
|
||||
s.blockMu.Lock()
|
||||
for id, filter := range s.blockQueue {
|
||||
if time.Since(filter.timeout) > filterTickerTime {
|
||||
s.filterManager.Remove(id)
|
||||
delete(s.blockQueue, id)
|
||||
}
|
||||
}
|
||||
s.blockMu.Unlock()
|
||||
|
||||
s.transactionMu.Lock()
|
||||
for id, filter := range s.transactionQueue {
|
||||
if time.Since(filter.timeout) > filterTickerTime {
|
||||
s.filterManager.Remove(id)
|
||||
delete(s.transactionQueue, id)
|
||||
}
|
||||
}
|
||||
s.transactionMu.Unlock()
|
||||
case <-s.quit:
|
||||
break done
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewBlockFilter create a new filter that returns blocks that are included into the canonical chain.
|
||||
func (s *PublicFilterAPI) NewBlockFilter() (string, error) {
|
||||
externalId, err := newFilterId()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.blockMu.Lock()
|
||||
filter := New(s.apiBackend)
|
||||
id, err := s.filterManager.Add(filter, ChainFilter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.blockQueue[id] = &hashQueue{timeout: time.Now()}
|
||||
|
||||
filter.BlockCallback = func(block *types.Block, logs vm.Logs) {
|
||||
s.blockMu.Lock()
|
||||
defer s.blockMu.Unlock()
|
||||
|
||||
if queue := s.blockQueue[id]; queue != nil {
|
||||
queue.add(block.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
defer s.blockMu.Unlock()
|
||||
|
||||
s.filterMapMu.Lock()
|
||||
s.filterMapping[externalId] = id
|
||||
s.filterMapMu.Unlock()
|
||||
|
||||
return externalId, nil
|
||||
}
|
||||
|
||||
// NewPendingTransactionFilter creates a filter that returns new pending transactions.
|
||||
func (s *PublicFilterAPI) NewPendingTransactionFilter() (string, error) {
|
||||
externalId, err := newFilterId()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.transactionMu.Lock()
|
||||
defer s.transactionMu.Unlock()
|
||||
|
||||
filter := New(s.apiBackend)
|
||||
id, err := s.filterManager.Add(filter, PendingTxFilter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.transactionQueue[id] = &hashQueue{timeout: time.Now()}
|
||||
|
||||
filter.TransactionCallback = func(tx *types.Transaction) {
|
||||
s.transactionMu.Lock()
|
||||
defer s.transactionMu.Unlock()
|
||||
|
||||
if queue := s.transactionQueue[id]; queue != nil {
|
||||
queue.add(tx.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
s.filterMapMu.Lock()
|
||||
s.filterMapping[externalId] = id
|
||||
s.filterMapMu.Unlock()
|
||||
|
||||
return externalId, nil
|
||||
}
|
||||
|
||||
// newLogFilter creates a new log filter.
|
||||
func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []common.Address, topics [][]common.Hash, callback func(log *vm.Log, removed bool)) (int, error) {
|
||||
s.logMu.Lock()
|
||||
defer s.logMu.Unlock()
|
||||
|
||||
filter := New(s.apiBackend)
|
||||
id, err := s.filterManager.Add(filter, LogFilter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
s.logQueue[id] = &logQueue{timeout: time.Now()}
|
||||
|
||||
filter.SetBeginBlock(earliest)
|
||||
filter.SetEndBlock(latest)
|
||||
filter.SetAddresses(addresses)
|
||||
filter.SetTopics(topics)
|
||||
filter.LogCallback = func(log *vm.Log, removed bool) {
|
||||
if callback != nil {
|
||||
callback(log, removed)
|
||||
} else {
|
||||
s.logMu.Lock()
|
||||
defer s.logMu.Unlock()
|
||||
if queue := s.logQueue[id]; queue != nil {
|
||||
queue.add(vmlog{log, removed})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Logs creates a subscription that fires for all new log that match the given filter criteria.
|
||||
func (s *PublicFilterAPI) Logs(ctx context.Context, args NewFilterArgs) (rpc.Subscription, error) {
|
||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||
if !supported {
|
||||
return nil, rpc.ErrNotificationsUnsupported
|
||||
}
|
||||
|
||||
var (
|
||||
externalId string
|
||||
subscription rpc.Subscription
|
||||
err error
|
||||
)
|
||||
|
||||
if externalId, err = newFilterId(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// uninstall filter when subscription is unsubscribed/cancelled
|
||||
if subscription, err = notifier.NewSubscription(func(string) {
|
||||
s.UninstallFilter(externalId)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notifySubscriber := func(log *vm.Log, removed bool) {
|
||||
rpcLog := toRPCLogs(vm.Logs{log}, removed)
|
||||
if err := subscription.Notify(rpcLog); err != nil {
|
||||
subscription.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// from and to block number are not used since subscriptions don't allow you to travel to "time"
|
||||
var id int
|
||||
if len(args.Addresses) > 0 {
|
||||
id, err = s.newLogFilter(-1, -1, args.Addresses, args.Topics, notifySubscriber)
|
||||
} else {
|
||||
id, err = s.newLogFilter(-1, -1, nil, args.Topics, notifySubscriber)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
subscription.Cancel()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.filterMapMu.Lock()
|
||||
s.filterMapping[externalId] = id
|
||||
s.filterMapMu.Unlock()
|
||||
|
||||
return subscription, err
|
||||
}
|
||||
|
||||
// NewFilterArgs represents a request to create a new filter.
|
||||
type NewFilterArgs struct {
|
||||
FromBlock rpc.BlockNumber
|
||||
ToBlock rpc.BlockNumber
|
||||
Addresses []common.Address
|
||||
Topics [][]common.Hash
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets *args fields with given data.
|
||||
func (args *NewFilterArgs) UnmarshalJSON(data []byte) error {
|
||||
type input struct {
|
||||
From *rpc.BlockNumber `json:"fromBlock"`
|
||||
ToBlock *rpc.BlockNumber `json:"toBlock"`
|
||||
Addresses interface{} `json:"address"`
|
||||
Topics []interface{} `json:"topics"`
|
||||
}
|
||||
|
||||
var raw input
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if raw.From == nil || raw.From.Int64() < 0 {
|
||||
args.FromBlock = rpc.LatestBlockNumber
|
||||
} else {
|
||||
args.FromBlock = *raw.From
|
||||
}
|
||||
|
||||
if raw.ToBlock == nil || raw.ToBlock.Int64() < 0 {
|
||||
args.ToBlock = rpc.LatestBlockNumber
|
||||
} else {
|
||||
args.ToBlock = *raw.ToBlock
|
||||
}
|
||||
|
||||
args.Addresses = []common.Address{}
|
||||
|
||||
if raw.Addresses != nil {
|
||||
// raw.Address can contain a single address or an array of addresses
|
||||
var addresses []common.Address
|
||||
if strAddrs, ok := raw.Addresses.([]interface{}); ok {
|
||||
for i, addr := range strAddrs {
|
||||
if strAddr, ok := addr.(string); ok {
|
||||
if len(strAddr) >= 2 && strAddr[0] == '0' && (strAddr[1] == 'x' || strAddr[1] == 'X') {
|
||||
strAddr = strAddr[2:]
|
||||
}
|
||||
if decAddr, err := hex.DecodeString(strAddr); err == nil {
|
||||
addresses = append(addresses, common.BytesToAddress(decAddr))
|
||||
} else {
|
||||
return fmt.Errorf("invalid address given")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid address on index %d", i)
|
||||
}
|
||||
}
|
||||
} else if singleAddr, ok := raw.Addresses.(string); ok {
|
||||
if len(singleAddr) >= 2 && singleAddr[0] == '0' && (singleAddr[1] == 'x' || singleAddr[1] == 'X') {
|
||||
singleAddr = singleAddr[2:]
|
||||
}
|
||||
if decAddr, err := hex.DecodeString(singleAddr); err == nil {
|
||||
addresses = append(addresses, common.BytesToAddress(decAddr))
|
||||
} else {
|
||||
return fmt.Errorf("invalid address given")
|
||||
}
|
||||
} else {
|
||||
return errors.New("invalid address(es) given")
|
||||
}
|
||||
args.Addresses = addresses
|
||||
}
|
||||
|
||||
// helper function which parses a string to a topic hash
|
||||
topicConverter := func(raw string) (common.Hash, error) {
|
||||
if len(raw) == 0 {
|
||||
return common.Hash{}, nil
|
||||
}
|
||||
if len(raw) >= 2 && raw[0] == '0' && (raw[1] == 'x' || raw[1] == 'X') {
|
||||
raw = raw[2:]
|
||||
}
|
||||
if len(raw) != 2*common.HashLength {
|
||||
return common.Hash{}, errors.New("invalid topic(s)")
|
||||
}
|
||||
if decAddr, err := hex.DecodeString(raw); err == nil {
|
||||
return common.BytesToHash(decAddr), nil
|
||||
}
|
||||
return common.Hash{}, errors.New("invalid topic(s)")
|
||||
}
|
||||
|
||||
// topics is an array consisting of strings and/or arrays of strings.
|
||||
// JSON null values are converted to common.Hash{} and ignored by the filter manager.
|
||||
if len(raw.Topics) > 0 {
|
||||
args.Topics = make([][]common.Hash, len(raw.Topics))
|
||||
for i, t := range raw.Topics {
|
||||
if t == nil { // ignore topic when matching logs
|
||||
args.Topics[i] = []common.Hash{common.Hash{}}
|
||||
} else if topic, ok := t.(string); ok { // match specific topic
|
||||
top, err := topicConverter(topic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args.Topics[i] = []common.Hash{top}
|
||||
} else if topics, ok := t.([]interface{}); ok { // or case e.g. [null, "topic0", "topic1"]
|
||||
for _, rawTopic := range topics {
|
||||
if rawTopic == nil {
|
||||
args.Topics[i] = append(args.Topics[i], common.Hash{})
|
||||
} else if topic, ok := rawTopic.(string); ok {
|
||||
parsed, err := topicConverter(topic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args.Topics[i] = append(args.Topics[i], parsed)
|
||||
} else {
|
||||
return fmt.Errorf("invalid topic(s)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid topic(s)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFilter creates a new filter and returns the filter id. It can be uses to retrieve logs.
|
||||
func (s *PublicFilterAPI) NewFilter(args NewFilterArgs) (string, error) {
|
||||
externalId, err := newFilterId()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var id int
|
||||
if len(args.Addresses) > 0 {
|
||||
id, err = s.newLogFilter(args.FromBlock.Int64(), args.ToBlock.Int64(), args.Addresses, args.Topics, nil)
|
||||
} else {
|
||||
id, err = s.newLogFilter(args.FromBlock.Int64(), args.ToBlock.Int64(), nil, args.Topics, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.filterMapMu.Lock()
|
||||
s.filterMapping[externalId] = id
|
||||
s.filterMapMu.Unlock()
|
||||
|
||||
return externalId, nil
|
||||
}
|
||||
|
||||
// GetLogs returns the logs matching the given argument.
|
||||
func (s *PublicFilterAPI) GetLogs(ctx context.Context, args NewFilterArgs) ([]vmlog, error) {
|
||||
filter := New(s.apiBackend)
|
||||
filter.SetBeginBlock(args.FromBlock.Int64())
|
||||
filter.SetEndBlock(args.ToBlock.Int64())
|
||||
filter.SetAddresses(args.Addresses)
|
||||
filter.SetTopics(args.Topics)
|
||||
|
||||
logs, err := filter.Find(ctx)
|
||||
return toRPCLogs(logs, false), err
|
||||
}
|
||||
|
||||
// UninstallFilter removes the filter with the given filter id.
|
||||
func (s *PublicFilterAPI) UninstallFilter(filterId string) bool {
|
||||
s.filterMapMu.Lock()
|
||||
defer s.filterMapMu.Unlock()
|
||||
|
||||
id, ok := s.filterMapping[filterId]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
defer s.filterManager.Remove(id)
|
||||
delete(s.filterMapping, filterId)
|
||||
|
||||
if _, ok := s.logQueue[id]; ok {
|
||||
s.logMu.Lock()
|
||||
defer s.logMu.Unlock()
|
||||
delete(s.logQueue, id)
|
||||
return true
|
||||
}
|
||||
if _, ok := s.blockQueue[id]; ok {
|
||||
s.blockMu.Lock()
|
||||
defer s.blockMu.Unlock()
|
||||
delete(s.blockQueue, id)
|
||||
return true
|
||||
}
|
||||
if _, ok := s.transactionQueue[id]; ok {
|
||||
s.transactionMu.Lock()
|
||||
defer s.transactionMu.Unlock()
|
||||
delete(s.transactionQueue, id)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getFilterType is a helper utility that determine the type of filter for the given filter id.
|
||||
func (s *PublicFilterAPI) getFilterType(id int) byte {
|
||||
if _, ok := s.blockQueue[id]; ok {
|
||||
return blockFilterTy
|
||||
} else if _, ok := s.transactionQueue[id]; ok {
|
||||
return transactionFilterTy
|
||||
} else if _, ok := s.logQueue[id]; ok {
|
||||
return logFilterTy
|
||||
}
|
||||
|
||||
return unknownFilterTy
|
||||
}
|
||||
|
||||
// blockFilterChanged returns a collection of block hashes for the block filter with the given id.
|
||||
func (s *PublicFilterAPI) blockFilterChanged(id int) []common.Hash {
|
||||
s.blockMu.Lock()
|
||||
defer s.blockMu.Unlock()
|
||||
|
||||
if s.blockQueue[id] != nil {
|
||||
res := s.blockQueue[id].get()
|
||||
return res
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// transactionFilterChanged returns a collection of transaction hashes for the pending
|
||||
// transaction filter with the given id.
|
||||
func (s *PublicFilterAPI) transactionFilterChanged(id int) []common.Hash {
|
||||
s.blockMu.Lock()
|
||||
defer s.blockMu.Unlock()
|
||||
|
||||
if s.transactionQueue[id] != nil {
|
||||
return s.transactionQueue[id].get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// logFilterChanged returns a collection of logs for the log filter with the given id.
|
||||
func (s *PublicFilterAPI) logFilterChanged(id int) []vmlog {
|
||||
s.logMu.Lock()
|
||||
defer s.logMu.Unlock()
|
||||
|
||||
if s.logQueue[id] != nil {
|
||||
return s.logQueue[id].get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFilterLogs returns the logs for the filter with the given id.
|
||||
func (s *PublicFilterAPI) GetFilterLogs(ctx context.Context, filterId string) ([]vmlog, error) {
|
||||
id, ok := s.filterMapping[filterId]
|
||||
if !ok {
|
||||
return toRPCLogs(nil, false), nil
|
||||
}
|
||||
|
||||
if filter := s.filterManager.Get(id); filter != nil {
|
||||
logs, err := filter.Find(ctx)
|
||||
return toRPCLogs(logs, false), err
|
||||
}
|
||||
|
||||
return toRPCLogs(nil, false), nil
|
||||
}
|
||||
|
||||
// GetFilterChanges returns the logs for the filter with the given id since last time is was called.
|
||||
// This can be used for polling.
|
||||
func (s *PublicFilterAPI) GetFilterChanges(filterId string) interface{} {
|
||||
s.filterMapMu.Lock()
|
||||
id, ok := s.filterMapping[filterId]
|
||||
s.filterMapMu.Unlock()
|
||||
|
||||
if !ok { // filter not found
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
switch s.getFilterType(id) {
|
||||
case blockFilterTy:
|
||||
return returnHashes(s.blockFilterChanged(id))
|
||||
case transactionFilterTy:
|
||||
return returnHashes(s.transactionFilterChanged(id))
|
||||
case logFilterTy:
|
||||
return s.logFilterChanged(id)
|
||||
}
|
||||
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
type vmlog struct {
|
||||
*vm.Log
|
||||
Removed bool `json:"removed"`
|
||||
}
|
||||
|
||||
type logQueue struct {
|
||||
mu sync.Mutex
|
||||
|
||||
logs []vmlog
|
||||
timeout time.Time
|
||||
id int
|
||||
}
|
||||
|
||||
func (l *logQueue) add(logs ...vmlog) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.logs = append(l.logs, logs...)
|
||||
}
|
||||
|
||||
func (l *logQueue) get() []vmlog {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.timeout = time.Now()
|
||||
tmp := l.logs
|
||||
l.logs = nil
|
||||
return tmp
|
||||
}
|
||||
|
||||
type hashQueue struct {
|
||||
mu sync.Mutex
|
||||
|
||||
hashes []common.Hash
|
||||
timeout time.Time
|
||||
id int
|
||||
}
|
||||
|
||||
func (l *hashQueue) add(hashes ...common.Hash) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.hashes = append(l.hashes, hashes...)
|
||||
}
|
||||
|
||||
func (l *hashQueue) get() []common.Hash {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.timeout = time.Now()
|
||||
tmp := l.hashes
|
||||
l.hashes = nil
|
||||
return tmp
|
||||
}
|
||||
|
||||
// newFilterId generates a new random filter identifier that can be exposed to the outer world. By publishing random
|
||||
// identifiers it is not feasible for DApp's to guess filter id's for other DApp's and uninstall or poll for them
|
||||
// causing the affected DApp to miss data.
|
||||
func newFilterId() (string, error) {
|
||||
var subid [16]byte
|
||||
n, _ := rand.Read(subid[:])
|
||||
if n != 16 {
|
||||
return "", errors.New("Unable to generate filter id")
|
||||
}
|
||||
return "0x" + hex.EncodeToString(subid[:]), nil
|
||||
}
|
||||
|
||||
// toRPCLogs is a helper that will convert a vm.Logs array to an structure which
|
||||
// can hold additional information about the logs such as whether it was deleted.
|
||||
// Additionally when nil is given it will by default instead create an empty slice
|
||||
// instead. This is required by the RPC specification.
|
||||
func toRPCLogs(logs vm.Logs, removed bool) []vmlog {
|
||||
convertedLogs := make([]vmlog, len(logs))
|
||||
for i, log := range logs {
|
||||
convertedLogs[i] = vmlog{Log: log, Removed: removed}
|
||||
}
|
||||
return convertedLogs
|
||||
}
|
||||
|
||||
// returnHashes is a helper that will return an empty hash array case the given hash array is nil, otherwise is will
|
||||
// return the given hashes. The RPC interfaces defines that always an array is returned.
|
||||
func returnHashes(hashes []common.Hash) []common.Hash {
|
||||
if hashes == nil {
|
||||
return []common.Hash{}
|
||||
}
|
||||
return hashes
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// package filters implements an ethereum filtering system for block,
|
||||
// transactions and log events.
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
// FilterType determines the type of filter and is used to put the filter in to
|
||||
// the correct bucket when added.
|
||||
type FilterType byte
|
||||
|
||||
const (
|
||||
ChainFilter FilterType = iota // new block events filter
|
||||
PendingTxFilter // pending transaction filter
|
||||
LogFilter // new or removed log filter
|
||||
PendingLogFilter // pending log filter
|
||||
)
|
||||
|
||||
// FilterSystem manages filters that filter specific events such as
|
||||
// block, transaction and log events. The Filtering system can be used to listen
|
||||
// for specific LOG events fired by the EVM (Ethereum Virtual Machine).
|
||||
type FilterSystem struct {
|
||||
filterMu sync.RWMutex
|
||||
filterId int
|
||||
|
||||
chainFilters map[int]*Filter
|
||||
pendingTxFilters map[int]*Filter
|
||||
logFilters map[int]*Filter
|
||||
pendingLogFilters map[int]*Filter
|
||||
|
||||
// generic is an ugly hack for Get
|
||||
generic map[int]*Filter
|
||||
|
||||
sub event.Subscription
|
||||
}
|
||||
|
||||
// NewFilterSystem returns a newly allocated filter manager
|
||||
func NewFilterSystem(mux *event.TypeMux) *FilterSystem {
|
||||
fs := &FilterSystem{
|
||||
chainFilters: make(map[int]*Filter),
|
||||
pendingTxFilters: make(map[int]*Filter),
|
||||
logFilters: make(map[int]*Filter),
|
||||
pendingLogFilters: make(map[int]*Filter),
|
||||
generic: make(map[int]*Filter),
|
||||
}
|
||||
fs.sub = mux.Subscribe(
|
||||
core.PendingLogsEvent{},
|
||||
core.RemovedLogsEvent{},
|
||||
core.ChainEvent{},
|
||||
core.TxPreEvent{},
|
||||
vm.Logs(nil),
|
||||
)
|
||||
go fs.filterLoop()
|
||||
return fs
|
||||
}
|
||||
|
||||
// Stop quits the filter loop required for polling events
|
||||
func (fs *FilterSystem) Stop() {
|
||||
fs.sub.Unsubscribe()
|
||||
}
|
||||
|
||||
// Add adds a filter to the filter manager
|
||||
func (fs *FilterSystem) Add(filter *Filter, filterType FilterType) (int, error) {
|
||||
fs.filterMu.Lock()
|
||||
defer fs.filterMu.Unlock()
|
||||
|
||||
id := fs.filterId
|
||||
filter.created = time.Now()
|
||||
|
||||
switch filterType {
|
||||
case ChainFilter:
|
||||
fs.chainFilters[id] = filter
|
||||
case PendingTxFilter:
|
||||
fs.pendingTxFilters[id] = filter
|
||||
case LogFilter:
|
||||
fs.logFilters[id] = filter
|
||||
case PendingLogFilter:
|
||||
fs.pendingLogFilters[id] = filter
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown filter type %v", filterType)
|
||||
}
|
||||
fs.generic[id] = filter
|
||||
|
||||
fs.filterId++
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Remove removes a filter by filter id
|
||||
func (fs *FilterSystem) Remove(id int) {
|
||||
fs.filterMu.Lock()
|
||||
defer fs.filterMu.Unlock()
|
||||
|
||||
delete(fs.chainFilters, id)
|
||||
delete(fs.pendingTxFilters, id)
|
||||
delete(fs.logFilters, id)
|
||||
delete(fs.pendingLogFilters, id)
|
||||
delete(fs.generic, id)
|
||||
}
|
||||
|
||||
func (fs *FilterSystem) Get(id int) *Filter {
|
||||
fs.filterMu.RLock()
|
||||
defer fs.filterMu.RUnlock()
|
||||
|
||||
return fs.generic[id]
|
||||
}
|
||||
|
||||
// filterLoop waits for specific events from ethereum and fires their handlers
|
||||
// when the filter matches the requirements.
|
||||
func (fs *FilterSystem) filterLoop() {
|
||||
for event := range fs.sub.Chan() {
|
||||
switch ev := event.Data.(type) {
|
||||
case core.ChainEvent:
|
||||
fs.filterMu.RLock()
|
||||
for _, filter := range fs.chainFilters {
|
||||
if filter.BlockCallback != nil && !filter.created.After(event.Time) {
|
||||
filter.BlockCallback(ev.Block, ev.Logs)
|
||||
}
|
||||
}
|
||||
fs.filterMu.RUnlock()
|
||||
case core.TxPreEvent:
|
||||
fs.filterMu.RLock()
|
||||
for _, filter := range fs.pendingTxFilters {
|
||||
if filter.TransactionCallback != nil && !filter.created.After(event.Time) {
|
||||
filter.TransactionCallback(ev.Tx)
|
||||
}
|
||||
}
|
||||
fs.filterMu.RUnlock()
|
||||
|
||||
case vm.Logs:
|
||||
fs.filterMu.RLock()
|
||||
for _, filter := range fs.logFilters {
|
||||
if filter.LogCallback != nil && !filter.created.After(event.Time) {
|
||||
for _, log := range filter.FilterLogs(ev) {
|
||||
filter.LogCallback(log, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.filterMu.RUnlock()
|
||||
case core.RemovedLogsEvent:
|
||||
fs.filterMu.RLock()
|
||||
for _, filter := range fs.logFilters {
|
||||
if filter.LogCallback != nil && !filter.created.After(event.Time) {
|
||||
for _, removedLog := range filter.FilterLogs(ev.Logs) {
|
||||
filter.LogCallback(removedLog, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.filterMu.RUnlock()
|
||||
case core.PendingLogsEvent:
|
||||
fs.filterMu.RLock()
|
||||
for _, filter := range fs.pendingLogFilters {
|
||||
if filter.LogCallback != nil && !filter.created.After(event.Time) {
|
||||
for _, pendingLog := range ev.Logs {
|
||||
filter.LogCallback(pendingLog, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.filterMu.RUnlock()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
const (
|
||||
maxHTTPRequestContentLength = 1024 * 128
|
||||
)
|
||||
|
||||
// httpClient connects to a geth RPC server over HTTP.
|
||||
type httpClient struct {
|
||||
endpoint *url.URL // HTTP-RPC server endpoint
|
||||
httpClient http.Client // reuse connection
|
||||
lastRes []byte // HTTP requests are synchronous, store last response
|
||||
}
|
||||
|
||||
// NewHTTPClient create a new RPC clients that connection to a geth RPC server
|
||||
// over HTTP.
|
||||
func NewHTTPClient(endpoint string) (Client, error) {
|
||||
url, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &httpClient{endpoint: url}, nil
|
||||
}
|
||||
|
||||
// Send will serialize the given msg to JSON and sends it to the RPC server.
|
||||
// Since HTTP is synchronous the response is stored until Recv is called.
|
||||
func (client *httpClient) Send(msg interface{}) error {
|
||||
var body []byte
|
||||
var err error
|
||||
|
||||
client.lastRes = nil
|
||||
if body, err = json.Marshal(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.httpClient.Post(client.endpoint.String(), "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
client.lastRes, err = ioutil.ReadAll(resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("request failed: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Recv will try to deserialize the last received response into the given msg.
|
||||
func (client *httpClient) Recv(msg interface{}) error {
|
||||
return json.Unmarshal(client.lastRes, &msg)
|
||||
}
|
||||
|
||||
// Close is not necessary for httpClient
|
||||
func (client *httpClient) Close() {
|
||||
}
|
||||
|
||||
// SupportedModules will return the collection of offered RPC modules.
|
||||
func (client *httpClient) SupportedModules() (map[string]string, error) {
|
||||
return SupportedModules(client)
|
||||
}
|
||||
|
||||
// httpReadWriteNopCloser wraps a io.Reader and io.Writer with a NOP Close method.
|
||||
type httpReadWriteNopCloser struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// Close does nothing and returns always nil
|
||||
func (t *httpReadWriteNopCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newJSONHTTPHandler creates a HTTP handler that will parse incoming JSON requests,
|
||||
// send the request to the given API provider and sends the response back to the caller.
|
||||
func newJSONHTTPHandler(srv *Server) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ContentLength > maxHTTPRequestContentLength {
|
||||
http.Error(w,
|
||||
fmt.Sprintf("content length too large (%d>%d)", r.ContentLength, maxHTTPRequestContentLength),
|
||||
http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("content-type", "application/json")
|
||||
|
||||
// create a codec that reads direct from the request body until
|
||||
// EOF and writes the response to w and order the server to process
|
||||
// a single request.
|
||||
codec := NewJSONCodec(&httpReadWriteNopCloser{r.Body, w})
|
||||
defer codec.Close()
|
||||
srv.ServeSingleRequest(codec, OptionMethodInvocation)
|
||||
}
|
||||
}
|
||||
|
||||
// NewHTTPServer creates a new HTTP RPC server around an API provider.
|
||||
func NewHTTPServer(corsString string, srv *Server) *http.Server {
|
||||
var allowedOrigins []string
|
||||
for _, domain := range strings.Split(corsString, ",") {
|
||||
allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))
|
||||
}
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: allowedOrigins,
|
||||
AllowedMethods: []string{"POST", "GET"},
|
||||
})
|
||||
|
||||
handler := c.Handler(newJSONHTTPHandler(srv))
|
||||
|
||||
return &http.Server{
|
||||
Handler: handler,
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// inProcClient is an in-process buffer stream attached to an RPC server.
|
||||
type inProcClient struct {
|
||||
server *Server
|
||||
cl io.Closer
|
||||
enc *json.Encoder
|
||||
dec *json.Decoder
|
||||
}
|
||||
|
||||
// Close tears down the request channel of the in-proc client.
|
||||
func (c *inProcClient) Close() {
|
||||
c.cl.Close()
|
||||
}
|
||||
|
||||
// NewInProcRPCClient creates an in-process buffer stream attachment to a given
|
||||
// RPC server.
|
||||
func NewInProcRPCClient(handler *Server) Client {
|
||||
p1, p2 := net.Pipe()
|
||||
go handler.ServeCodec(NewJSONCodec(p1), OptionMethodInvocation|OptionSubscriptions)
|
||||
return &inProcClient{handler, p2, json.NewEncoder(p2), json.NewDecoder(p2)}
|
||||
}
|
||||
|
||||
// Send marshals a message into a json format and injects in into the client
|
||||
// request channel.
|
||||
func (c *inProcClient) Send(msg interface{}) error {
|
||||
return c.enc.Encode(msg)
|
||||
}
|
||||
|
||||
// Recv reads a message from the response channel and tries to parse it into the
|
||||
// given msg interface.
|
||||
func (c *inProcClient) Recv(msg interface{}) error {
|
||||
return c.dec.Decode(msg)
|
||||
}
|
||||
|
||||
// Returns the collection of modules the RPC server offers.
|
||||
func (c *inProcClient) SupportedModules() (map[string]string, error) {
|
||||
return SupportedModules(c)
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
)
|
||||
|
||||
// CreateIPCListener creates an listener, on Unix platforms this is a unix socket, on Windows this is a named pipe
|
||||
func CreateIPCListener(endpoint string) (net.Listener, error) {
|
||||
return ipcListen(endpoint)
|
||||
}
|
||||
|
||||
// ipcClient represent an IPC RPC client. It will connect to a given endpoint and tries to communicate with a node using
|
||||
// JSON serialization.
|
||||
type ipcClient struct {
|
||||
endpoint string
|
||||
conn net.Conn
|
||||
out *json.Encoder
|
||||
in *json.Decoder
|
||||
}
|
||||
|
||||
// NewIPCClient create a new IPC client that will connect on the given endpoint. Messages are JSON encoded and encoded.
|
||||
// On Unix it assumes the endpoint is the full path to a unix socket, and Windows the endpoint is an identifier for a
|
||||
// named pipe.
|
||||
func NewIPCClient(endpoint string) (Client, error) {
|
||||
conn, err := newIPCConnection(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ipcClient{endpoint: endpoint, conn: conn, in: json.NewDecoder(conn), out: json.NewEncoder(conn)}, nil
|
||||
}
|
||||
|
||||
// Send will serialize the given message and send it to the server.
|
||||
// When sending the message fails it will try to reconnect once and send the message again.
|
||||
func (client *ipcClient) Send(msg interface{}) error {
|
||||
if err := client.out.Encode(msg); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// retry once
|
||||
client.conn.Close()
|
||||
|
||||
conn, err := newIPCConnection(client.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client.conn = conn
|
||||
client.in = json.NewDecoder(conn)
|
||||
client.out = json.NewEncoder(conn)
|
||||
|
||||
return client.out.Encode(msg)
|
||||
}
|
||||
|
||||
// Recv will read a message from the connection and tries to parse it. It assumes the received message is JSON encoded.
|
||||
func (client *ipcClient) Recv(msg interface{}) error {
|
||||
return client.in.Decode(&msg)
|
||||
}
|
||||
|
||||
// Close will close the underlying IPC connection
|
||||
func (client *ipcClient) Close() {
|
||||
client.conn.Close()
|
||||
}
|
||||
|
||||
// SupportedModules will return the collection of offered RPC modules.
|
||||
func (client *ipcClient) SupportedModules() (map[string]string, error) {
|
||||
return SupportedModules(client)
|
||||
}
|
|
@ -1,297 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotificationsUnsupported is returned when the connection doesn't support notifications
|
||||
ErrNotificationsUnsupported = errors.New("notifications not supported")
|
||||
|
||||
// ErrNotificationNotFound is returned when the notification for the given id is not found
|
||||
ErrNotificationNotFound = errors.New("notification not found")
|
||||
|
||||
// errNotifierStopped is returned when the notifier is stopped (e.g. codec is closed)
|
||||
errNotifierStopped = errors.New("unable to send notification")
|
||||
|
||||
// errNotificationQueueFull is returns when there are too many notifications in the queue
|
||||
errNotificationQueueFull = errors.New("too many pending notifications")
|
||||
)
|
||||
|
||||
// unsubSignal is a signal that the subscription is unsubscribed. It is used to flush buffered
|
||||
// notifications that might be pending in the internal queue.
|
||||
var unsubSignal = new(struct{})
|
||||
|
||||
// UnsubscribeCallback defines a callback that is called when a subcription ends.
|
||||
// It receives the subscription id as argument.
|
||||
type UnsubscribeCallback func(id string)
|
||||
|
||||
// notification is a helper object that holds event data for a subscription
|
||||
type notification struct {
|
||||
sub *bufferedSubscription // subscription id
|
||||
data interface{} // event data
|
||||
}
|
||||
|
||||
// A Notifier type describes the interface for objects that can send create subscriptions
|
||||
type Notifier interface {
|
||||
// Create a new subscription. The given callback is called when this subscription
|
||||
// is cancelled (e.g. client send an unsubscribe, connection closed).
|
||||
NewSubscription(UnsubscribeCallback) (Subscription, error)
|
||||
// Cancel subscription
|
||||
Unsubscribe(id string) error
|
||||
}
|
||||
|
||||
type notifierKey struct{}
|
||||
|
||||
// NotifierFromContext returns the Notifier value stored in ctx, if any.
|
||||
func NotifierFromContext(ctx context.Context) (Notifier, bool) {
|
||||
n, ok := ctx.Value(notifierKey{}).(Notifier)
|
||||
return n, ok
|
||||
}
|
||||
|
||||
// Subscription defines the interface for objects that can notify subscribers
|
||||
type Subscription interface {
|
||||
// Inform client of an event
|
||||
Notify(data interface{}) error
|
||||
// Unique identifier
|
||||
ID() string
|
||||
// Cancel subscription
|
||||
Cancel() error
|
||||
}
|
||||
|
||||
// bufferedSubscription is a subscription that uses a bufferedNotifier to send
|
||||
// notifications to subscribers.
|
||||
type bufferedSubscription struct {
|
||||
id string
|
||||
unsubOnce sync.Once // call unsub method once
|
||||
unsub UnsubscribeCallback // called on Unsubscribed
|
||||
notifier *bufferedNotifier // forward notifications to
|
||||
pending chan interface{} // closed when active
|
||||
flushed chan interface{} // closed when all buffered notifications are send
|
||||
lastNotification time.Time // last time a notification was send
|
||||
}
|
||||
|
||||
// ID returns the subscription identifier that the client uses to refer to this instance.
|
||||
func (s *bufferedSubscription) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
// Cancel informs the notifier that this subscription is cancelled by the API
|
||||
func (s *bufferedSubscription) Cancel() error {
|
||||
return s.notifier.Unsubscribe(s.id)
|
||||
}
|
||||
|
||||
// Notify the subscriber of a particular event.
|
||||
func (s *bufferedSubscription) Notify(data interface{}) error {
|
||||
return s.notifier.send(s.id, data)
|
||||
}
|
||||
|
||||
// bufferedNotifier is a notifier that queues notifications in an internal queue and
|
||||
// send them as fast as possible to the client from this queue. It will stop if the
|
||||
// queue grows past a given size.
|
||||
type bufferedNotifier struct {
|
||||
codec ServerCodec // underlying connection
|
||||
mu sync.Mutex // guard internal state
|
||||
subscriptions map[string]*bufferedSubscription // keep track of subscriptions associated with codec
|
||||
queueSize int // max number of items in queue
|
||||
queue chan *notification // notification queue
|
||||
stopped bool // indication if this notifier is ordered to stop
|
||||
}
|
||||
|
||||
// newBufferedNotifier returns a notifier that queues notifications in an internal queue
|
||||
// from which notifications are send as fast as possible to the client. If the queue size
|
||||
// limit is reached (client is unable to keep up) it will stop and closes the codec.
|
||||
func newBufferedNotifier(codec ServerCodec, size int) *bufferedNotifier {
|
||||
notifier := &bufferedNotifier{
|
||||
codec: codec,
|
||||
subscriptions: make(map[string]*bufferedSubscription),
|
||||
queue: make(chan *notification, size),
|
||||
queueSize: size,
|
||||
}
|
||||
|
||||
go notifier.run()
|
||||
|
||||
return notifier
|
||||
}
|
||||
|
||||
// NewSubscription creates a new subscription that forwards events to this instance internal
|
||||
// queue. The given callback is called when the subscription is unsubscribed/cancelled.
|
||||
func (n *bufferedNotifier) NewSubscription(callback UnsubscribeCallback) (Subscription, error) {
|
||||
id, err := newSubscriptionID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
if n.stopped {
|
||||
return nil, errNotifierStopped
|
||||
}
|
||||
|
||||
sub := &bufferedSubscription{
|
||||
id: id,
|
||||
unsub: callback,
|
||||
notifier: n,
|
||||
pending: make(chan interface{}),
|
||||
flushed: make(chan interface{}),
|
||||
lastNotification: time.Now(),
|
||||
}
|
||||
|
||||
n.subscriptions[id] = sub
|
||||
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
// Remove the given subscription. If subscription is not found notificationNotFoundErr is returned.
|
||||
func (n *bufferedNotifier) Unsubscribe(subid string) error {
|
||||
n.mu.Lock()
|
||||
sub, found := n.subscriptions[subid]
|
||||
n.mu.Unlock()
|
||||
|
||||
if found {
|
||||
// send the unsubscribe signal, this will cause the notifier not to accept new events
|
||||
// for this subscription and will close the flushed channel after the last (buffered)
|
||||
// notification was send to the client.
|
||||
if err := n.send(subid, unsubSignal); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for confirmation that all (buffered) events are send for this subscription.
|
||||
// this ensures that the unsubscribe method response is not send before all buffered
|
||||
// events for this subscription are send.
|
||||
<-sub.flushed
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNotificationNotFound
|
||||
}
|
||||
|
||||
// Send enques the given data for the subscription with public ID on the internal queue. t returns
|
||||
// an error when the notifier is stopped or the queue is full. If data is the unsubscribe signal it
|
||||
// will remove the subscription with the given id from the subscription collection.
|
||||
func (n *bufferedNotifier) send(id string, data interface{}) error {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
if n.stopped {
|
||||
return errNotifierStopped
|
||||
}
|
||||
|
||||
var (
|
||||
subscription *bufferedSubscription
|
||||
found bool
|
||||
)
|
||||
|
||||
// check if subscription is associated with this connection, it might be cancelled
|
||||
// (subscribe/connection closed)
|
||||
if subscription, found = n.subscriptions[id]; !found {
|
||||
glog.V(logger.Error).Infof("received notification for unknown subscription %s\n", id)
|
||||
return ErrNotificationNotFound
|
||||
}
|
||||
|
||||
// received the unsubscribe signal. Add it to the queue to make sure any pending notifications
|
||||
// for this subscription are send. When the run loop receives this singal it will signal that
|
||||
// all pending subscriptions are flushed and that the confirmation of the unsubscribe can be
|
||||
// send to the user. Remove the subscriptions to make sure new notifications are not accepted.
|
||||
if data == unsubSignal {
|
||||
delete(n.subscriptions, id)
|
||||
if subscription.unsub != nil {
|
||||
subscription.unsubOnce.Do(func() { subscription.unsub(id) })
|
||||
}
|
||||
}
|
||||
|
||||
subscription.lastNotification = time.Now()
|
||||
|
||||
if len(n.queue) >= n.queueSize {
|
||||
glog.V(logger.Warn).Infoln("too many buffered notifications -> close connection")
|
||||
n.codec.Close()
|
||||
return errNotificationQueueFull
|
||||
}
|
||||
|
||||
n.queue <- ¬ification{subscription, data}
|
||||
return nil
|
||||
}
|
||||
|
||||
// run reads notifications from the internal queue and sends them to the client. In case of an
|
||||
// error, or when the codec is closed it will cancel all active subscriptions and returns.
|
||||
func (n *bufferedNotifier) run() {
|
||||
defer func() {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
n.stopped = true
|
||||
close(n.queue)
|
||||
|
||||
// on exit call unsubscribe callback
|
||||
for id, sub := range n.subscriptions {
|
||||
if sub.unsub != nil {
|
||||
sub.unsubOnce.Do(func() { sub.unsub(id) })
|
||||
}
|
||||
close(sub.flushed)
|
||||
delete(n.subscriptions, id)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case notification := <-n.queue:
|
||||
// It can happen that an event is raised before the RPC server was able to send the sub
|
||||
// id to the client. Therefore subscriptions are marked as pending until the sub id was
|
||||
// send. The RPC server will activate the subscription by closing the pending chan.
|
||||
<-notification.sub.pending
|
||||
|
||||
if notification.data == unsubSignal {
|
||||
// unsubSignal is the last accepted message for this subscription. Raise the signal
|
||||
// that all buffered notifications are sent by closing the flushed channel. This
|
||||
// indicates that the response for the unsubscribe can be send to the client.
|
||||
close(notification.sub.flushed)
|
||||
} else {
|
||||
msg := n.codec.CreateNotification(notification.sub.id, notification.data)
|
||||
if err := n.codec.Write(msg); err != nil {
|
||||
n.codec.Close()
|
||||
// unable to send notification to client, unsubscribe all subscriptions
|
||||
glog.V(logger.Warn).Infof("unable to send notification - %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
case <-n.codec.Closed(): // connection was closed
|
||||
glog.V(logger.Debug).Infoln("codec closed, stop subscriptions")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Marks the subscription as active. This will causes the notifications for this subscription to be
|
||||
// forwarded to the client.
|
||||
func (n *bufferedNotifier) activate(subid string) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
if sub, found := n.subscriptions[subid]; found {
|
||||
close(sub.pending)
|
||||
}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"golang.org/x/net/websocket"
|
||||
"gopkg.in/fatih/set.v0"
|
||||
)
|
||||
|
||||
// wsReaderWriterCloser reads and write payloads from and to a websocket connection.
|
||||
type wsReaderWriterCloser struct {
|
||||
c *websocket.Conn
|
||||
}
|
||||
|
||||
// Read will read incoming payload data into p.
|
||||
func (rw *wsReaderWriterCloser) Read(p []byte) (int, error) {
|
||||
return rw.c.Read(p)
|
||||
}
|
||||
|
||||
// Write writes p to the websocket.
|
||||
func (rw *wsReaderWriterCloser) Write(p []byte) (int, error) {
|
||||
return rw.c.Write(p)
|
||||
}
|
||||
|
||||
// Close closes the websocket connection.
|
||||
func (rw *wsReaderWriterCloser) Close() error {
|
||||
return rw.c.Close()
|
||||
}
|
||||
|
||||
// wsHandshakeValidator returns a handler that verifies the origin during the
|
||||
// websocket upgrade process. When a '*' is specified as an allowed origins all
|
||||
// connections are accepted.
|
||||
func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http.Request) error {
|
||||
origins := set.New()
|
||||
allowAllOrigins := false
|
||||
|
||||
for _, origin := range allowedOrigins {
|
||||
if origin == "*" {
|
||||
allowAllOrigins = true
|
||||
}
|
||||
if origin != "" {
|
||||
origins.Add(strings.ToLower(origin))
|
||||
}
|
||||
}
|
||||
|
||||
// allow localhost if no allowedOrigins are specified.
|
||||
if len(origins.List()) == 0 {
|
||||
origins.Add("http://localhost")
|
||||
if hostname, err := os.Hostname(); err == nil {
|
||||
origins.Add("http://" + strings.ToLower(hostname))
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(logger.Debug).Infof("Allowed origin(s) for WS RPC interface %v\n", origins.List())
|
||||
|
||||
f := func(cfg *websocket.Config, req *http.Request) error {
|
||||
origin := strings.ToLower(req.Header.Get("Origin"))
|
||||
if allowAllOrigins || origins.Has(origin) {
|
||||
return nil
|
||||
}
|
||||
glog.V(logger.Debug).Infof("origin '%s' not allowed on WS-RPC interface\n", origin)
|
||||
return fmt.Errorf("origin %s not allowed", origin)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// NewWSServer creates a new websocket RPC server around an API provider.
|
||||
func NewWSServer(allowedOrigins string, handler *Server) *http.Server {
|
||||
return &http.Server{
|
||||
Handler: websocket.Server{
|
||||
Handshake: wsHandshakeValidator(strings.Split(allowedOrigins, ",")),
|
||||
Handler: func(conn *websocket.Conn) {
|
||||
handler.ServeCodec(NewJSONCodec(&wsReaderWriterCloser{conn}),
|
||||
OptionMethodInvocation|OptionSubscriptions)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// wsClient represents a RPC client that communicates over websockets with a
|
||||
// RPC server.
|
||||
type wsClient struct {
|
||||
endpoint string
|
||||
connMu sync.Mutex
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
// NewWSClientj creates a new RPC client that communicates with a RPC server
|
||||
// that is listening on the given endpoint using JSON encoding.
|
||||
func NewWSClient(endpoint string) (Client, error) {
|
||||
return &wsClient{endpoint: endpoint}, nil
|
||||
}
|
||||
|
||||
// connection will return a websocket connection to the RPC server. It will
|
||||
// (re)connect when necessary.
|
||||
func (client *wsClient) connection() (*websocket.Conn, error) {
|
||||
if client.conn != nil {
|
||||
return client.conn, nil
|
||||
}
|
||||
|
||||
origin, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
origin = "http://" + origin
|
||||
client.conn, err = websocket.Dial(client.endpoint, "", origin)
|
||||
|
||||
return client.conn, err
|
||||
}
|
||||
|
||||
// SupportedModules is the collection of modules the RPC server offers.
|
||||
func (client *wsClient) SupportedModules() (map[string]string, error) {
|
||||
return SupportedModules(client)
|
||||
}
|
||||
|
||||
// Send writes the JSON serialized msg to the websocket. It will create a new
|
||||
// websocket connection to the server if the client is currently not connected.
|
||||
func (client *wsClient) Send(msg interface{}) (err error) {
|
||||
client.connMu.Lock()
|
||||
defer client.connMu.Unlock()
|
||||
|
||||
var conn *websocket.Conn
|
||||
if conn, err = client.connection(); err == nil {
|
||||
if err = websocket.JSON.Send(conn, msg); err != nil {
|
||||
client.conn.Close()
|
||||
client.conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Recv reads a JSON message from the websocket and unmarshals it into msg.
|
||||
func (client *wsClient) Recv(msg interface{}) (err error) {
|
||||
client.connMu.Lock()
|
||||
defer client.connMu.Unlock()
|
||||
|
||||
var conn *websocket.Conn
|
||||
if conn, err = client.connection(); err == nil {
|
||||
if err = websocket.JSON.Receive(conn, msg); err != nil {
|
||||
client.conn.Close()
|
||||
client.conn = nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the underlaying websocket connection.
|
||||
func (client *wsClient) Close() {
|
||||
client.connMu.Lock()
|
||||
defer client.connMu.Unlock()
|
||||
|
||||
if client.conn != nil {
|
||||
client.conn.Close()
|
||||
client.conn = 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
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue