Merge pull request #35 from farazdagi/feature/rebase

Rebased against `zsfelfoldi/light-stable` + Major refactoring
This commit is contained in:
adrian-tiberius 2016-09-15 17:23:34 +03:00 committed by GitHub
commit 40a3c76273
1120 changed files with 6589 additions and 6413 deletions

9
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
}

14
cmd/status/main.go Normal file
View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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")
}

308
geth/accounts_test.go Normal file
View File

@ -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")
}
}

299
geth/node.go Normal file
View File

@ -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
}

45
geth/node_test.go Normal file
View File

@ -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())
}

181
geth/signals.c Normal file
View File

@ -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

43
geth/txqueue.go Normal file
View File

@ -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)
}

149
geth/txqueue_test.go Normal file
View File

@ -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
}
}

View File

@ -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"`
}

189
geth/utils.go Normal file
View File

@ -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)
}
}()
}

69
geth/whisper.go Normal file
View File

@ -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
}

164
geth/whisper_test.go Normal file
View File

@ -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)
}
}
}

259
jail/jail.go Normal file
View File

@ -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
}

242
jail/jail_test.go Normal file
View File

@ -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)
}
}

320
jail/testdata/commands.js vendored Normal file
View File

@ -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
});

155
jail/testdata/status.js vendored Normal file
View File

@ -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
}
};

View File

@ -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){

500
src/Godeps/Godeps.json generated
View File

@ -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"
}
]
}

5
src/Godeps/Readme generated
View File

@ -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.

View File

@ -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")
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}
}
}

View File

@ -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,
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 <- &notification{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)
}
}

View File

@ -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
}
}

View File

@ -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