Initial refactoring + vendor update (after rebase)
This commit is contained in:
parent
4babe9101d
commit
5fb4aef1cc
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/les/status"
|
"github.com/ethereum/go-ethereum/les/status"
|
|
@ -0,0 +1,26 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyFile(dst, src string) error {
|
||||||
|
s, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
d, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(d, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"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) {
|
func TestBIP32Vectors(t *testing.T) {
|
||||||
|
@ -122,7 +122,7 @@ tests:
|
||||||
}
|
}
|
||||||
|
|
||||||
if !extKey.IsPrivate {
|
if !extKey.IsPrivate {
|
||||||
t.Errorf("Master node must feature private key")
|
t.Error("Master node must feature private key")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,7 +416,7 @@ func TestErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a new key and neuter it to a public extended key.
|
// 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)
|
phrase, err := mnemonic.MnemonicPhrase(128, extkeys.EnglishLanguage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -505,12 +505,12 @@ func TestBIP44ChildDerivation(t *testing.T) {
|
||||||
|
|
||||||
extKey, err := extkeys.NewKeyFromString(keyString)
|
extKey, err := extkeys.NewKeyFromString(keyString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("NewKeyFromString: cannot create extended key")
|
t.Error("NewKeyFromString: cannot create extended key")
|
||||||
}
|
}
|
||||||
|
|
||||||
accounKey1, err := extKey.BIP44Child(extkeys.CoinTypeETH, 0)
|
accounKey1, err := extKey.BIP44Child(extkeys.CoinTypeETH, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error dering BIP44-compliant key")
|
t.Error("Error dering BIP44-compliant key")
|
||||||
}
|
}
|
||||||
if accounKey1.String() != derivedKey1String {
|
if accounKey1.String() != derivedKey1String {
|
||||||
t.Errorf("BIP44Child: key mismatch -- got: %v, want: %v", 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)
|
accounKey2, err := extKey.BIP44Child(extkeys.CoinTypeETH, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error dering BIP44-compliant key")
|
t.Error("Error dering BIP44-compliant key")
|
||||||
}
|
}
|
||||||
if accounKey2.String() != derivedKey2String {
|
if accounKey2.String() != derivedKey2String {
|
||||||
t.Errorf("BIP44Child: key mismatch -- got: %v, want: %v", accounKey2.String(), derivedKey2String)
|
t.Errorf("BIP44Child: key mismatch -- got: %v, want: %v", accounKey2.String(), derivedKey2String)
|
File diff suppressed because one or more lines are too long
|
@ -1,10 +1,11 @@
|
||||||
package extkeys
|
package extkeys_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"github.com/status-im/status-go/extkeys"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VectorsFile struct {
|
type VectorsFile struct {
|
||||||
|
@ -19,13 +20,13 @@ type Vector struct {
|
||||||
// TestMnemonicPhrase
|
// TestMnemonicPhrase
|
||||||
func TestMnemonicPhrase(t *testing.T) {
|
func TestMnemonicPhrase(t *testing.T) {
|
||||||
|
|
||||||
mnemonic := NewMnemonic()
|
mnemonic := extkeys.NewMnemonic(extkeys.Salt)
|
||||||
|
|
||||||
// test mnemonic generation
|
// test mnemonic generation
|
||||||
t.Logf("Test mnemonic generation:")
|
t.Log("Test mnemonic generation:")
|
||||||
for _, language := range mnemonic.AvailableLanguages() {
|
for _, language := range mnemonic.AvailableLanguages() {
|
||||||
phrase, err := mnemonic.MnemonicPhrase(128, language)
|
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 {
|
if err != nil {
|
||||||
t.Errorf("Test failed: could not create seed: %s", err)
|
t.Errorf("Test failed: could not create seed: %s", err)
|
||||||
|
@ -42,11 +43,11 @@ func TestMnemonicPhrase(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("Test against pre-computed seed vectors:")
|
t.Log("Test against pre-computed seed vectors:")
|
||||||
stats := map[string]int{}
|
stats := map[string]int{}
|
||||||
for _, vector := range vectorsFile.vectors {
|
for _, vector := range vectorsFile.vectors {
|
||||||
stats[vector.language] += 1
|
stats[vector.language] += 1
|
||||||
mnemonic.salt = vector.salt
|
mnemonic := extkeys.NewMnemonic(vector.salt)
|
||||||
seed := mnemonic.MnemonicSeed(vector.mnemonic, vector.password)
|
seed := mnemonic.MnemonicSeed(vector.mnemonic, vector.password)
|
||||||
if fmt.Sprintf("%x", seed) != vector.seed {
|
if fmt.Sprintf("%x", seed) != vector.seed {
|
||||||
t.Errorf("Test failed (%s): incorrect seed (%x) generated (expected: %s)", vector.language, seed, vector.seed)
|
t.Errorf("Test failed (%s): incorrect seed (%x) generated (expected: %s)", vector.language, seed, vector.seed)
|
163
src/main.go
163
src/main.go
|
@ -1,177 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
"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 (
|
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"
|
gitCommit = "rely on linker: -ldflags -X main.GitCommit"
|
||||||
buildStamp = "rely on linker: -ldflags -X main.buildStamp"
|
buildStamp = "rely on linker: -ldflags -X main.buildStamp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrDataDirPreprocessingFailed = errors.New("Failed to pre-process data directory")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Printf("Status\nGit Commit: %s\nBuild Time: %s\n", gitCommit, buildStamp)
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/les"
|
||||||
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/release"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/whisper"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
clientIdentifier = "Geth" // Client identifier to advertise over the network
|
||||||
|
versionMajor = 1 // Major version component of the current release
|
||||||
|
versionMinor = 5 // Minor version component of the current release
|
||||||
|
versionPatch = 0 // Patch version component of the current release
|
||||||
|
versionMeta = "unstable" // Version metadata to append to the version string
|
||||||
|
|
||||||
|
versionOracle = "0xfa7b9770ca4cb04296cac84f37736d4041251cdf" // Ethereum address of the Geth release oracle
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
vString string // Combined textual representation of the version
|
||||||
|
rConfig release.Config // Structured version information and release oracle config
|
||||||
|
currentNode *node.Node // currently running geth node
|
||||||
|
c *cli.Context // the CLI context used to start the geth node
|
||||||
|
lightEthereum *les.LightEthereum // LES service
|
||||||
|
accountManager *accounts.Manager // the account manager attached to the currentNode
|
||||||
|
selectedAddress string // address of the account that was processed during the last call to SelectAccount()
|
||||||
|
whisperService *whisper.Whisper // whisper service
|
||||||
|
datadir string // data directory for geth
|
||||||
|
rpcport int = 8545 // RPC port (replaced in unit tests)
|
||||||
|
client *rpc.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDataDirPreprocessingFailed = errors.New("Failed to pre-process data directory")
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeNode create a geth node entity
|
||||||
|
func MakeNode(inputDir string) *node.Node {
|
||||||
|
|
||||||
|
datadir := inputDir
|
||||||
|
|
||||||
|
// TODO remove admin rpcapi flag
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("lightkdf", true, "Reduce key-derivation RAM & CPU usage at some expense of KDF strength")
|
||||||
|
set.Bool("shh", true, "whisper")
|
||||||
|
set.Bool("light", true, "disable eth")
|
||||||
|
set.Bool("testnet", true, "light test network")
|
||||||
|
set.Bool("rpc", true, "enable rpc")
|
||||||
|
set.String("rpcaddr", "localhost", "host for RPC")
|
||||||
|
set.Int("rpcport", rpcport, "rpc port")
|
||||||
|
set.String("rpccorsdomain", "*", "allow all domains")
|
||||||
|
set.String("verbosity", "3", "verbosity level")
|
||||||
|
set.String("rpcapi", "db,eth,net,web3,shh,personal,admin", "rpc api(s)")
|
||||||
|
set.String("datadir", datadir, "data directory for geth")
|
||||||
|
set.String("logdir", datadir, "log dir for glog")
|
||||||
|
c = cli.NewContext(nil, set, nil)
|
||||||
|
|
||||||
|
// Construct the textual version string from the individual components
|
||||||
|
vString = fmt.Sprintf("%d.%d.%d", versionMajor, versionMinor, versionPatch)
|
||||||
|
|
||||||
|
// Construct the version release oracle configuration
|
||||||
|
rConfig.Oracle = common.HexToAddress(versionOracle)
|
||||||
|
|
||||||
|
rConfig.Major = uint32(versionMajor)
|
||||||
|
rConfig.Minor = uint32(versionMinor)
|
||||||
|
rConfig.Patch = uint32(versionPatch)
|
||||||
|
|
||||||
|
utils.DebugSetup(c)
|
||||||
|
currentNode = makeNode(c, clientIdentifier, vString)
|
||||||
|
|
||||||
|
return currentNode
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeNode(ctx *cli.Context, name, version string) *node.Node {
|
||||||
|
nodeIn := utils.MakeNode(ctx, name, version)
|
||||||
|
utils.RegisterEthService(ctx, nodeIn, rConfig, makeDefaultExtra())
|
||||||
|
// Whisper must be explicitly enabled, but is auto-enabled in --dev mode.
|
||||||
|
shhEnabled := ctx.GlobalBool(utils.WhisperEnabledFlag.Name)
|
||||||
|
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
|
||||||
|
if shhEnabled || shhAutoEnabled {
|
||||||
|
utils.RegisterShhService(nodeIn)
|
||||||
|
}
|
||||||
|
return nodeIn
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartNode starts a geth node entity
|
||||||
|
func RunNode(nodeIn *node.Node) {
|
||||||
|
utils.StartNode(nodeIn)
|
||||||
|
|
||||||
|
if err := nodeIn.Service(&accountManager); err != nil {
|
||||||
|
glog.V(logger.Warn).Infoln("cannot get account manager:", err)
|
||||||
|
}
|
||||||
|
if err := nodeIn.Service(&whisperService); err != nil {
|
||||||
|
glog.V(logger.Warn).Infoln("cannot get whisper service:", err)
|
||||||
|
}
|
||||||
|
if err := nodeIn.Service(&lightEthereum); err != nil {
|
||||||
|
glog.V(logger.Warn).Infoln("cannot get light ethereum service:", err)
|
||||||
|
}
|
||||||
|
lightEthereum.StatusBackend.SetTransactionQueueHandler(onSendTransactionRequest)
|
||||||
|
|
||||||
|
client, _ = nodeIn.Attach()
|
||||||
|
nodeIn.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeDefaultExtra() []byte {
|
||||||
|
var clientInfo = struct {
|
||||||
|
Version uint
|
||||||
|
Name string
|
||||||
|
GoVersion string
|
||||||
|
Os string
|
||||||
|
}{uint(versionMajor<<16 | versionMinor<<8 | versionPatch), clientIdentifier, runtime.Version(), runtime.GOOS}
|
||||||
|
extra, err := rlp.EncodeToBytes(clientInfo)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Warn).Infoln("error setting canonical miner information:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint64(len(extra)) > params.MaximumExtraDataSize.Uint64() {
|
||||||
|
glog.V(logger.Warn).Infoln("error setting canonical miner information: extra exceeds", params.MaximumExtraDataSize)
|
||||||
|
glog.V(logger.Debug).Infof("extra: %x\n", extra)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return extra
|
||||||
|
}
|
||||||
|
|
||||||
|
func preprocessDataDir(dataDir string) (string, error) {
|
||||||
|
testDataDir := path.Join(dataDir, "testnet", "keystore")
|
||||||
|
if _, err := os.Stat(testDataDir); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(testDataDir, 0755); err != nil {
|
||||||
|
return dataDir, ErrDataDirPreprocessingFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy over static peer nodes list (LES auto-discovery is not stable yet)
|
||||||
|
dst := filepath.Join(dataDir, "testnet", "static-nodes.json")
|
||||||
|
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
||||||
|
src := filepath.Join("data", "static-nodes.json")
|
||||||
|
if err := copyFile(dst, src); err != nil {
|
||||||
|
return dataDir, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataDir, nil
|
||||||
|
}
|
260
src/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/backends/remote.go
generated
vendored
260
src/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/backends/remote.go
generated
vendored
|
@ -1,260 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package backends
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This nil assignment ensures compile time that rpcBackend implements bind.ContractBackend.
|
|
||||||
var _ bind.ContractBackend = (*rpcBackend)(nil)
|
|
||||||
|
|
||||||
// rpcBackend implements bind.ContractBackend, and acts as the data provider to
|
|
||||||
// Ethereum contracts bound to Go structs. It uses an RPC connection to delegate
|
|
||||||
// all its functionality.
|
|
||||||
//
|
|
||||||
// Note: The current implementation is a blocking one. This should be replaced
|
|
||||||
// by a proper async version when a real RPC client is created.
|
|
||||||
type rpcBackend struct {
|
|
||||||
client rpc.Client // RPC client connection to interact with an API server
|
|
||||||
autoid uint32 // ID number to use for the next API request
|
|
||||||
lock sync.Mutex // Singleton access until we get to request multiplexing
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRPCBackend creates a new binding backend to an RPC provider that can be
|
|
||||||
// used to interact with remote contracts.
|
|
||||||
func NewRPCBackend(client rpc.Client) bind.ContractBackend {
|
|
||||||
return &rpcBackend{
|
|
||||||
client: client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// request is a JSON RPC request package assembled internally from the client
|
|
||||||
// method calls.
|
|
||||||
type request struct {
|
|
||||||
JSONRPC string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
|
||||||
ID int `json:"id"` // Auto incrementing ID number for this request
|
|
||||||
Method string `json:"method"` // Remote procedure name to invoke on the server
|
|
||||||
Params []interface{} `json:"params"` // List of parameters to pass through (keep types simple)
|
|
||||||
}
|
|
||||||
|
|
||||||
// response is a JSON RPC response package sent back from the API server.
|
|
||||||
type response struct {
|
|
||||||
JSONRPC string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
|
||||||
ID int `json:"id"` // Auto incrementing ID number for this request
|
|
||||||
Error *failure `json:"error"` // Any error returned by the remote side
|
|
||||||
Result json.RawMessage `json:"result"` // Whatever the remote side sends us in reply
|
|
||||||
}
|
|
||||||
|
|
||||||
// failure is a JSON RPC response error field sent back from the API server.
|
|
||||||
type failure struct {
|
|
||||||
Code int `json:"code"` // JSON RPC error code associated with the failure
|
|
||||||
Message string `json:"message"` // Specific error message of the failure
|
|
||||||
}
|
|
||||||
|
|
||||||
// request forwards an API request to the RPC server, and parses the response.
|
|
||||||
//
|
|
||||||
// This is currently painfully non-concurrent, but it will have to do until we
|
|
||||||
// find the time for niceties like this :P
|
|
||||||
func (b *rpcBackend) request(ctx context.Context, method string, params []interface{}) (json.RawMessage, error) {
|
|
||||||
b.lock.Lock()
|
|
||||||
defer b.lock.Unlock()
|
|
||||||
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ugly hack to serialize an empty list properly
|
|
||||||
if params == nil {
|
|
||||||
params = []interface{}{}
|
|
||||||
}
|
|
||||||
// Assemble the request object
|
|
||||||
reqID := int(atomic.AddUint32(&b.autoid, 1))
|
|
||||||
req := &request{
|
|
||||||
JSONRPC: "2.0",
|
|
||||||
ID: reqID,
|
|
||||||
Method: method,
|
|
||||||
Params: params,
|
|
||||||
}
|
|
||||||
if err := b.client.Send(req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res := new(response)
|
|
||||||
errc := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
errc <- b.client.Recv(res)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case err := <-errc:
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
if res.Error != nil {
|
|
||||||
if res.Error.Message == bind.ErrNoCode.Error() {
|
|
||||||
return nil, bind.ErrNoCode
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("remote error: %s", res.Error.Message)
|
|
||||||
}
|
|
||||||
return res.Result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasCode implements ContractVerifier.HasCode by retrieving any code associated
|
|
||||||
// with the contract from the remote node, and checking its size.
|
|
||||||
func (b *rpcBackend) HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error) {
|
|
||||||
// Execute the RPC code retrieval
|
|
||||||
block := "latest"
|
|
||||||
if pending {
|
|
||||||
block = "pending"
|
|
||||||
}
|
|
||||||
res, err := b.request(ctx, "eth_getCode", []interface{}{contract.Hex(), block})
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
var hex string
|
|
||||||
if err := json.Unmarshal(res, &hex); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
// Convert the response back to a Go byte slice and return
|
|
||||||
return len(common.FromHex(hex)) > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
|
|
||||||
// a contract call to the remote node, returning the reply to for local processing.
|
|
||||||
func (b *rpcBackend) ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) {
|
|
||||||
// Pack up the request into an RPC argument
|
|
||||||
args := struct {
|
|
||||||
To common.Address `json:"to"`
|
|
||||||
Data string `json:"data"`
|
|
||||||
}{
|
|
||||||
To: contract,
|
|
||||||
Data: common.ToHex(data),
|
|
||||||
}
|
|
||||||
// Execute the RPC call and retrieve the response
|
|
||||||
block := "latest"
|
|
||||||
if pending {
|
|
||||||
block = "pending"
|
|
||||||
}
|
|
||||||
res, err := b.request(ctx, "eth_call", []interface{}{args, block})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var hex string
|
|
||||||
if err := json.Unmarshal(res, &hex); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Convert the response back to a Go byte slice and return
|
|
||||||
return common.FromHex(hex), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, delegating
|
|
||||||
// the current account nonce retrieval to the remote node.
|
|
||||||
func (b *rpcBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) {
|
|
||||||
res, err := b.request(ctx, "eth_getTransactionCount", []interface{}{account.Hex(), "pending"})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var hex string
|
|
||||||
if err := json.Unmarshal(res, &hex); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
nonce, ok := new(big.Int).SetString(hex, 0)
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("invalid nonce hex: %s", hex)
|
|
||||||
}
|
|
||||||
return nonce.Uint64(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice, delegating the
|
|
||||||
// gas price oracle request to the remote node.
|
|
||||||
func (b *rpcBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
|
||||||
res, err := b.request(ctx, "eth_gasPrice", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var hex string
|
|
||||||
if err := json.Unmarshal(res, &hex); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
price, ok := new(big.Int).SetString(hex, 0)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid price hex: %s", hex)
|
|
||||||
}
|
|
||||||
return price, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, delegating
|
|
||||||
// the gas estimation to the remote node.
|
|
||||||
func (b *rpcBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
|
|
||||||
// Pack up the request into an RPC argument
|
|
||||||
args := struct {
|
|
||||||
From common.Address `json:"from"`
|
|
||||||
To *common.Address `json:"to"`
|
|
||||||
Value *rpc.HexNumber `json:"value"`
|
|
||||||
Data string `json:"data"`
|
|
||||||
}{
|
|
||||||
From: sender,
|
|
||||||
To: contract,
|
|
||||||
Data: common.ToHex(data),
|
|
||||||
Value: rpc.NewHexNumber(value),
|
|
||||||
}
|
|
||||||
// Execute the RPC call and retrieve the response
|
|
||||||
res, err := b.request(ctx, "eth_estimateGas", []interface{}{args})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var hex string
|
|
||||||
if err := json.Unmarshal(res, &hex); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
estimate, ok := new(big.Int).SetString(hex, 0)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid estimate hex: %s", hex)
|
|
||||||
}
|
|
||||||
return estimate, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendTransaction implements ContractTransactor.SendTransaction, delegating the
|
|
||||||
// raw transaction injection to the remote node.
|
|
||||||
func (b *rpcBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
|
||||||
data, err := rlp.EncodeToBytes(tx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res, err := b.request(ctx, "eth_sendRawTransaction", []interface{}{common.ToHex(data)})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var hex string
|
|
||||||
if err := json.Unmarshal(res, &hex); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewRemoteRPCClient returns a RPC client which connects to a running geth instance.
|
|
||||||
// Depending on the given context this can either be a IPC or a HTTP client.
|
|
||||||
func NewRemoteRPCClient(ctx *cli.Context) (rpc.Client, error) {
|
|
||||||
if ctx.Args().Present() {
|
|
||||||
endpoint := ctx.Args().First()
|
|
||||||
return NewRemoteRPCClientFromString(endpoint)
|
|
||||||
}
|
|
||||||
// use IPC by default
|
|
||||||
return rpc.NewIPCClient(node.DefaultIPCEndpoint())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRemoteRPCClientFromString returns a RPC client which connects to the given
|
|
||||||
// endpoint. It must start with either `ipc:` or `rpc:` (HTTP).
|
|
||||||
func NewRemoteRPCClientFromString(endpoint string) (rpc.Client, error) {
|
|
||||||
if strings.HasPrefix(endpoint, "ipc:") {
|
|
||||||
return rpc.NewIPCClient(endpoint[4:])
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(endpoint, "rpc:") {
|
|
||||||
return rpc.NewHTTPClient(endpoint[4:])
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(endpoint, "http://") {
|
|
||||||
return rpc.NewHTTPClient(endpoint)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(endpoint, "ws:") {
|
|
||||||
return rpc.NewWSClient(endpoint)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("invalid endpoint")
|
|
||||||
}
|
|
|
@ -1,225 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
versionRegexp = regexp.MustCompile("[0-9]+\\.[0-9]+\\.[0-9]+")
|
|
||||||
legacyRegexp = regexp.MustCompile("0\\.(9\\..*|1\\.[01])")
|
|
||||||
paramsLegacy = []string{
|
|
||||||
"--binary", // Request to output the contract in binary (hexadecimal).
|
|
||||||
"file", //
|
|
||||||
"--json-abi", // Request to output the contract's JSON ABI interface.
|
|
||||||
"file", //
|
|
||||||
"--natspec-user", // Request to output the contract's Natspec user documentation.
|
|
||||||
"file", //
|
|
||||||
"--natspec-dev", // Request to output the contract's Natspec developer documentation.
|
|
||||||
"file",
|
|
||||||
"--add-std",
|
|
||||||
"1",
|
|
||||||
}
|
|
||||||
paramsNew = []string{
|
|
||||||
"--bin", // Request to output the contract in binary (hexadecimal).
|
|
||||||
"--abi", // Request to output the contract's JSON ABI interface.
|
|
||||||
"--userdoc", // Request to output the contract's Natspec user documentation.
|
|
||||||
"--devdoc", // Request to output the contract's Natspec developer documentation.
|
|
||||||
"--add-std", // include standard lib contracts
|
|
||||||
"--optimize", // code optimizer switched on
|
|
||||||
"-o", // output directory
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Contract struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Info ContractInfo `json:"info"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContractInfo struct {
|
|
||||||
Source string `json:"source"`
|
|
||||||
Language string `json:"language"`
|
|
||||||
LanguageVersion string `json:"languageVersion"`
|
|
||||||
CompilerVersion string `json:"compilerVersion"`
|
|
||||||
CompilerOptions string `json:"compilerOptions"`
|
|
||||||
AbiDefinition interface{} `json:"abiDefinition"`
|
|
||||||
UserDoc interface{} `json:"userDoc"`
|
|
||||||
DeveloperDoc interface{} `json:"developerDoc"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Solidity struct {
|
|
||||||
solcPath string
|
|
||||||
version string
|
|
||||||
fullVersion string
|
|
||||||
legacy bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(solcPath string) (sol *Solidity, err error) {
|
|
||||||
// set default solc
|
|
||||||
if len(solcPath) == 0 {
|
|
||||||
solcPath = "solc"
|
|
||||||
}
|
|
||||||
solcPath, err = exec.LookPath(solcPath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(solcPath, "--version")
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fullVersion := out.String()
|
|
||||||
version := versionRegexp.FindString(fullVersion)
|
|
||||||
legacy := legacyRegexp.MatchString(version)
|
|
||||||
|
|
||||||
sol = &Solidity{
|
|
||||||
solcPath: solcPath,
|
|
||||||
version: version,
|
|
||||||
fullVersion: fullVersion,
|
|
||||||
legacy: legacy,
|
|
||||||
}
|
|
||||||
glog.V(logger.Info).Infoln(sol.Info())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sol *Solidity) Info() string {
|
|
||||||
return fmt.Sprintf("%s\npath: %s", sol.fullVersion, sol.solcPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sol *Solidity) Version() string {
|
|
||||||
return sol.version
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile builds and returns all the contracts contained within a source string.
|
|
||||||
func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
|
|
||||||
// Short circuit if no source code was specified
|
|
||||||
if len(source) == 0 {
|
|
||||||
return nil, errors.New("solc: empty source string")
|
|
||||||
}
|
|
||||||
// Create a safe place to dump compilation output
|
|
||||||
wd, err := ioutil.TempDir("", "solc")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: failed to create temporary build folder: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(wd)
|
|
||||||
|
|
||||||
// Assemble the compiler command, change to the temp folder and capture any errors
|
|
||||||
stderr := new(bytes.Buffer)
|
|
||||||
|
|
||||||
var params []string
|
|
||||||
if sol.legacy {
|
|
||||||
params = paramsLegacy
|
|
||||||
} else {
|
|
||||||
params = paramsNew
|
|
||||||
params = append(params, wd)
|
|
||||||
}
|
|
||||||
compilerOptions := strings.Join(params, " ")
|
|
||||||
|
|
||||||
cmd := exec.Command(sol.solcPath, params...)
|
|
||||||
cmd.Stdin = strings.NewReader(source)
|
|
||||||
cmd.Stderr = stderr
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: %v\n%s", err, string(stderr.Bytes()))
|
|
||||||
}
|
|
||||||
// Sanity check that something was actually built
|
|
||||||
matches, _ := filepath.Glob(filepath.Join(wd, "*.bin*"))
|
|
||||||
if len(matches) < 1 {
|
|
||||||
return nil, fmt.Errorf("solc: no build results found")
|
|
||||||
}
|
|
||||||
// Compilation succeeded, assemble and return the contracts
|
|
||||||
contracts := make(map[string]*Contract)
|
|
||||||
for _, path := range matches {
|
|
||||||
_, file := filepath.Split(path)
|
|
||||||
base := strings.Split(file, ".")[0]
|
|
||||||
|
|
||||||
// Parse the individual compilation results (code binary, ABI definitions, user and dev docs)
|
|
||||||
var binary []byte
|
|
||||||
binext := ".bin"
|
|
||||||
if sol.legacy {
|
|
||||||
binext = ".binary"
|
|
||||||
}
|
|
||||||
if binary, err = ioutil.ReadFile(filepath.Join(wd, base+binext)); err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: error reading compiler output for code: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var abi interface{}
|
|
||||||
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".abi")); err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: error reading abi definition: %v", err)
|
|
||||||
} else if err = json.Unmarshal(blob, &abi); err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: error parsing abi definition: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var userdoc interface{}
|
|
||||||
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docuser")); err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: error reading user doc: %v", err)
|
|
||||||
} else if err = json.Unmarshal(blob, &userdoc); err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: error parsing user doc: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var devdoc interface{}
|
|
||||||
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docdev")); err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: error reading dev doc: %v", err)
|
|
||||||
} else if err = json.Unmarshal(blob, &devdoc); err != nil {
|
|
||||||
return nil, fmt.Errorf("solc: error parsing dev doc: %v", err)
|
|
||||||
}
|
|
||||||
// Assemble the final contract
|
|
||||||
contracts[base] = &Contract{
|
|
||||||
Code: "0x" + string(binary),
|
|
||||||
Info: ContractInfo{
|
|
||||||
Source: source,
|
|
||||||
Language: "Solidity",
|
|
||||||
LanguageVersion: sol.version,
|
|
||||||
CompilerVersion: sol.version,
|
|
||||||
CompilerOptions: compilerOptions,
|
|
||||||
AbiDefinition: abi,
|
|
||||||
UserDoc: userdoc,
|
|
||||||
DeveloperDoc: devdoc,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contracts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveInfo(info *ContractInfo, filename string) (contenthash common.Hash, err error) {
|
|
||||||
infojson, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
contenthash = common.BytesToHash(crypto.Keccak256(infojson))
|
|
||||||
err = ioutil.WriteFile(filename, infojson, 0600)
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package downloader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicDownloaderAPI provides an API which gives information about the current synchronisation status.
|
|
||||||
// It offers only methods that operates on data that can be available to anyone without security risks.
|
|
||||||
type PublicDownloaderAPI struct {
|
|
||||||
d *Downloader
|
|
||||||
mux *event.TypeMux
|
|
||||||
muSyncSubscriptions sync.Mutex
|
|
||||||
syncSubscriptions map[string]rpc.Subscription
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPublicDownloaderAPI create a new PublicDownloaderAPI.
|
|
||||||
func NewPublicDownloaderAPI(d *Downloader, m *event.TypeMux) *PublicDownloaderAPI {
|
|
||||||
api := &PublicDownloaderAPI{d: d, mux: m, syncSubscriptions: make(map[string]rpc.Subscription)}
|
|
||||||
|
|
||||||
go api.run()
|
|
||||||
|
|
||||||
return api
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *PublicDownloaderAPI) run() {
|
|
||||||
sub := api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{})
|
|
||||||
|
|
||||||
for event := range sub.Chan() {
|
|
||||||
var notification interface{}
|
|
||||||
|
|
||||||
switch event.Data.(type) {
|
|
||||||
case StartEvent:
|
|
||||||
result := &SyncingResult{Syncing: true}
|
|
||||||
result.Status.Origin, result.Status.Current, result.Status.Height, result.Status.Pulled, result.Status.Known = api.d.Progress()
|
|
||||||
notification = result
|
|
||||||
case DoneEvent, FailedEvent:
|
|
||||||
notification = false
|
|
||||||
}
|
|
||||||
|
|
||||||
api.muSyncSubscriptions.Lock()
|
|
||||||
for id, sub := range api.syncSubscriptions {
|
|
||||||
if sub.Notify(notification) == rpc.ErrNotificationNotFound {
|
|
||||||
delete(api.syncSubscriptions, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
api.muSyncSubscriptions.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress gives progress indications when the node is synchronising with the Ethereum network.
|
|
||||||
type Progress struct {
|
|
||||||
Origin uint64 `json:"startingBlock"`
|
|
||||||
Current uint64 `json:"currentBlock"`
|
|
||||||
Height uint64 `json:"highestBlock"`
|
|
||||||
Pulled uint64 `json:"pulledStates"`
|
|
||||||
Known uint64 `json:"knownStates"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncingResult provides information about the current synchronisation status for this node.
|
|
||||||
type SyncingResult struct {
|
|
||||||
Syncing bool `json:"syncing"`
|
|
||||||
Status Progress `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Syncing provides information when this nodes starts synchronising with the Ethereum network and when it's finished.
|
|
||||||
func (api *PublicDownloaderAPI) Syncing(ctx context.Context) (rpc.Subscription, error) {
|
|
||||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
|
||||||
if !supported {
|
|
||||||
return nil, rpc.ErrNotificationsUnsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription, err := notifier.NewSubscription(func(id string) {
|
|
||||||
api.muSyncSubscriptions.Lock()
|
|
||||||
delete(api.syncSubscriptions, id)
|
|
||||||
api.muSyncSubscriptions.Unlock()
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
api.muSyncSubscriptions.Lock()
|
|
||||||
api.syncSubscriptions[subscription.ID()] = subscription
|
|
||||||
api.muSyncSubscriptions.Unlock()
|
|
||||||
|
|
||||||
return subscription, nil
|
|
||||||
}
|
|
|
@ -1,655 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
filterTickerTime = 5 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
// byte will be inferred
|
|
||||||
const (
|
|
||||||
unknownFilterTy = iota
|
|
||||||
blockFilterTy
|
|
||||||
transactionFilterTy
|
|
||||||
logFilterTy
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
|
|
||||||
// information related to the Ethereum protocol such als blocks, transactions and logs.
|
|
||||||
type PublicFilterAPI struct {
|
|
||||||
apiBackend ethapi.Backend
|
|
||||||
|
|
||||||
quit chan struct{}
|
|
||||||
chainDb ethdb.Database
|
|
||||||
mux *event.TypeMux
|
|
||||||
|
|
||||||
filterManager *FilterSystem
|
|
||||||
|
|
||||||
filterMapMu sync.RWMutex
|
|
||||||
filterMapping map[string]int // maps between filter internal filter identifiers and external filter identifiers
|
|
||||||
|
|
||||||
logMu sync.RWMutex
|
|
||||||
logQueue map[int]*logQueue
|
|
||||||
|
|
||||||
blockMu sync.RWMutex
|
|
||||||
blockQueue map[int]*hashQueue
|
|
||||||
|
|
||||||
transactionMu sync.RWMutex
|
|
||||||
transactionQueue map[int]*hashQueue
|
|
||||||
|
|
||||||
transactMu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPublicFilterAPI returns a new PublicFilterAPI instance.
|
|
||||||
func NewPublicFilterAPI(apiBackend ethapi.Backend) *PublicFilterAPI {
|
|
||||||
svc := &PublicFilterAPI{
|
|
||||||
apiBackend: apiBackend,
|
|
||||||
mux: apiBackend.EventMux(),
|
|
||||||
chainDb: apiBackend.ChainDb(),
|
|
||||||
filterManager: NewFilterSystem(apiBackend.EventMux()),
|
|
||||||
filterMapping: make(map[string]int),
|
|
||||||
logQueue: make(map[int]*logQueue),
|
|
||||||
blockQueue: make(map[int]*hashQueue),
|
|
||||||
transactionQueue: make(map[int]*hashQueue),
|
|
||||||
}
|
|
||||||
go svc.start()
|
|
||||||
return svc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop quits the work loop.
|
|
||||||
func (s *PublicFilterAPI) Stop() {
|
|
||||||
close(s.quit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// start the work loop, wait and process events.
|
|
||||||
func (s *PublicFilterAPI) start() {
|
|
||||||
timer := time.NewTicker(2 * time.Second)
|
|
||||||
defer timer.Stop()
|
|
||||||
done:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
s.logMu.Lock()
|
|
||||||
for id, filter := range s.logQueue {
|
|
||||||
if time.Since(filter.timeout) > filterTickerTime {
|
|
||||||
s.filterManager.Remove(id)
|
|
||||||
delete(s.logQueue, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.logMu.Unlock()
|
|
||||||
|
|
||||||
s.blockMu.Lock()
|
|
||||||
for id, filter := range s.blockQueue {
|
|
||||||
if time.Since(filter.timeout) > filterTickerTime {
|
|
||||||
s.filterManager.Remove(id)
|
|
||||||
delete(s.blockQueue, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.blockMu.Unlock()
|
|
||||||
|
|
||||||
s.transactionMu.Lock()
|
|
||||||
for id, filter := range s.transactionQueue {
|
|
||||||
if time.Since(filter.timeout) > filterTickerTime {
|
|
||||||
s.filterManager.Remove(id)
|
|
||||||
delete(s.transactionQueue, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.transactionMu.Unlock()
|
|
||||||
case <-s.quit:
|
|
||||||
break done
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBlockFilter create a new filter that returns blocks that are included into the canonical chain.
|
|
||||||
func (s *PublicFilterAPI) NewBlockFilter() (string, error) {
|
|
||||||
externalId, err := newFilterId()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.blockMu.Lock()
|
|
||||||
filter := New(s.apiBackend)
|
|
||||||
id, err := s.filterManager.Add(filter, ChainFilter)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.blockQueue[id] = &hashQueue{timeout: time.Now()}
|
|
||||||
|
|
||||||
filter.BlockCallback = func(block *types.Block, logs vm.Logs) {
|
|
||||||
s.blockMu.Lock()
|
|
||||||
defer s.blockMu.Unlock()
|
|
||||||
|
|
||||||
if queue := s.blockQueue[id]; queue != nil {
|
|
||||||
queue.add(block.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defer s.blockMu.Unlock()
|
|
||||||
|
|
||||||
s.filterMapMu.Lock()
|
|
||||||
s.filterMapping[externalId] = id
|
|
||||||
s.filterMapMu.Unlock()
|
|
||||||
|
|
||||||
return externalId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPendingTransactionFilter creates a filter that returns new pending transactions.
|
|
||||||
func (s *PublicFilterAPI) NewPendingTransactionFilter() (string, error) {
|
|
||||||
externalId, err := newFilterId()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.transactionMu.Lock()
|
|
||||||
defer s.transactionMu.Unlock()
|
|
||||||
|
|
||||||
filter := New(s.apiBackend)
|
|
||||||
id, err := s.filterManager.Add(filter, PendingTxFilter)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.transactionQueue[id] = &hashQueue{timeout: time.Now()}
|
|
||||||
|
|
||||||
filter.TransactionCallback = func(tx *types.Transaction) {
|
|
||||||
s.transactionMu.Lock()
|
|
||||||
defer s.transactionMu.Unlock()
|
|
||||||
|
|
||||||
if queue := s.transactionQueue[id]; queue != nil {
|
|
||||||
queue.add(tx.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.filterMapMu.Lock()
|
|
||||||
s.filterMapping[externalId] = id
|
|
||||||
s.filterMapMu.Unlock()
|
|
||||||
|
|
||||||
return externalId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newLogFilter creates a new log filter.
|
|
||||||
func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []common.Address, topics [][]common.Hash, callback func(log *vm.Log, removed bool)) (int, error) {
|
|
||||||
s.logMu.Lock()
|
|
||||||
defer s.logMu.Unlock()
|
|
||||||
|
|
||||||
filter := New(s.apiBackend)
|
|
||||||
id, err := s.filterManager.Add(filter, LogFilter)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.logQueue[id] = &logQueue{timeout: time.Now()}
|
|
||||||
|
|
||||||
filter.SetBeginBlock(earliest)
|
|
||||||
filter.SetEndBlock(latest)
|
|
||||||
filter.SetAddresses(addresses)
|
|
||||||
filter.SetTopics(topics)
|
|
||||||
filter.LogCallback = func(log *vm.Log, removed bool) {
|
|
||||||
if callback != nil {
|
|
||||||
callback(log, removed)
|
|
||||||
} else {
|
|
||||||
s.logMu.Lock()
|
|
||||||
defer s.logMu.Unlock()
|
|
||||||
if queue := s.logQueue[id]; queue != nil {
|
|
||||||
queue.add(vmlog{log, removed})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logs creates a subscription that fires for all new log that match the given filter criteria.
|
|
||||||
func (s *PublicFilterAPI) Logs(ctx context.Context, args NewFilterArgs) (rpc.Subscription, error) {
|
|
||||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
|
||||||
if !supported {
|
|
||||||
return nil, rpc.ErrNotificationsUnsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
externalId string
|
|
||||||
subscription rpc.Subscription
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if externalId, err = newFilterId(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// uninstall filter when subscription is unsubscribed/cancelled
|
|
||||||
if subscription, err = notifier.NewSubscription(func(string) {
|
|
||||||
s.UninstallFilter(externalId)
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
notifySubscriber := func(log *vm.Log, removed bool) {
|
|
||||||
rpcLog := toRPCLogs(vm.Logs{log}, removed)
|
|
||||||
if err := subscription.Notify(rpcLog); err != nil {
|
|
||||||
subscription.Cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// from and to block number are not used since subscriptions don't allow you to travel to "time"
|
|
||||||
var id int
|
|
||||||
if len(args.Addresses) > 0 {
|
|
||||||
id, err = s.newLogFilter(-1, -1, args.Addresses, args.Topics, notifySubscriber)
|
|
||||||
} else {
|
|
||||||
id, err = s.newLogFilter(-1, -1, nil, args.Topics, notifySubscriber)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
subscription.Cancel()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.filterMapMu.Lock()
|
|
||||||
s.filterMapping[externalId] = id
|
|
||||||
s.filterMapMu.Unlock()
|
|
||||||
|
|
||||||
return subscription, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFilterArgs represents a request to create a new filter.
|
|
||||||
type NewFilterArgs struct {
|
|
||||||
FromBlock rpc.BlockNumber
|
|
||||||
ToBlock rpc.BlockNumber
|
|
||||||
Addresses []common.Address
|
|
||||||
Topics [][]common.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON sets *args fields with given data.
|
|
||||||
func (args *NewFilterArgs) UnmarshalJSON(data []byte) error {
|
|
||||||
type input struct {
|
|
||||||
From *rpc.BlockNumber `json:"fromBlock"`
|
|
||||||
ToBlock *rpc.BlockNumber `json:"toBlock"`
|
|
||||||
Addresses interface{} `json:"address"`
|
|
||||||
Topics []interface{} `json:"topics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var raw input
|
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw.From == nil || raw.From.Int64() < 0 {
|
|
||||||
args.FromBlock = rpc.LatestBlockNumber
|
|
||||||
} else {
|
|
||||||
args.FromBlock = *raw.From
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw.ToBlock == nil || raw.ToBlock.Int64() < 0 {
|
|
||||||
args.ToBlock = rpc.LatestBlockNumber
|
|
||||||
} else {
|
|
||||||
args.ToBlock = *raw.ToBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
args.Addresses = []common.Address{}
|
|
||||||
|
|
||||||
if raw.Addresses != nil {
|
|
||||||
// raw.Address can contain a single address or an array of addresses
|
|
||||||
var addresses []common.Address
|
|
||||||
if strAddrs, ok := raw.Addresses.([]interface{}); ok {
|
|
||||||
for i, addr := range strAddrs {
|
|
||||||
if strAddr, ok := addr.(string); ok {
|
|
||||||
if len(strAddr) >= 2 && strAddr[0] == '0' && (strAddr[1] == 'x' || strAddr[1] == 'X') {
|
|
||||||
strAddr = strAddr[2:]
|
|
||||||
}
|
|
||||||
if decAddr, err := hex.DecodeString(strAddr); err == nil {
|
|
||||||
addresses = append(addresses, common.BytesToAddress(decAddr))
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid address given")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid address on index %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if singleAddr, ok := raw.Addresses.(string); ok {
|
|
||||||
if len(singleAddr) >= 2 && singleAddr[0] == '0' && (singleAddr[1] == 'x' || singleAddr[1] == 'X') {
|
|
||||||
singleAddr = singleAddr[2:]
|
|
||||||
}
|
|
||||||
if decAddr, err := hex.DecodeString(singleAddr); err == nil {
|
|
||||||
addresses = append(addresses, common.BytesToAddress(decAddr))
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid address given")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("invalid address(es) given")
|
|
||||||
}
|
|
||||||
args.Addresses = addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function which parses a string to a topic hash
|
|
||||||
topicConverter := func(raw string) (common.Hash, error) {
|
|
||||||
if len(raw) == 0 {
|
|
||||||
return common.Hash{}, nil
|
|
||||||
}
|
|
||||||
if len(raw) >= 2 && raw[0] == '0' && (raw[1] == 'x' || raw[1] == 'X') {
|
|
||||||
raw = raw[2:]
|
|
||||||
}
|
|
||||||
if len(raw) != 2*common.HashLength {
|
|
||||||
return common.Hash{}, errors.New("invalid topic(s)")
|
|
||||||
}
|
|
||||||
if decAddr, err := hex.DecodeString(raw); err == nil {
|
|
||||||
return common.BytesToHash(decAddr), nil
|
|
||||||
}
|
|
||||||
return common.Hash{}, errors.New("invalid topic(s)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// topics is an array consisting of strings and/or arrays of strings.
|
|
||||||
// JSON null values are converted to common.Hash{} and ignored by the filter manager.
|
|
||||||
if len(raw.Topics) > 0 {
|
|
||||||
args.Topics = make([][]common.Hash, len(raw.Topics))
|
|
||||||
for i, t := range raw.Topics {
|
|
||||||
if t == nil { // ignore topic when matching logs
|
|
||||||
args.Topics[i] = []common.Hash{common.Hash{}}
|
|
||||||
} else if topic, ok := t.(string); ok { // match specific topic
|
|
||||||
top, err := topicConverter(topic)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
args.Topics[i] = []common.Hash{top}
|
|
||||||
} else if topics, ok := t.([]interface{}); ok { // or case e.g. [null, "topic0", "topic1"]
|
|
||||||
for _, rawTopic := range topics {
|
|
||||||
if rawTopic == nil {
|
|
||||||
args.Topics[i] = append(args.Topics[i], common.Hash{})
|
|
||||||
} else if topic, ok := rawTopic.(string); ok {
|
|
||||||
parsed, err := topicConverter(topic)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
args.Topics[i] = append(args.Topics[i], parsed)
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid topic(s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid topic(s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFilter creates a new filter and returns the filter id. It can be uses to retrieve logs.
|
|
||||||
func (s *PublicFilterAPI) NewFilter(args NewFilterArgs) (string, error) {
|
|
||||||
externalId, err := newFilterId()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id int
|
|
||||||
if len(args.Addresses) > 0 {
|
|
||||||
id, err = s.newLogFilter(args.FromBlock.Int64(), args.ToBlock.Int64(), args.Addresses, args.Topics, nil)
|
|
||||||
} else {
|
|
||||||
id, err = s.newLogFilter(args.FromBlock.Int64(), args.ToBlock.Int64(), nil, args.Topics, nil)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.filterMapMu.Lock()
|
|
||||||
s.filterMapping[externalId] = id
|
|
||||||
s.filterMapMu.Unlock()
|
|
||||||
|
|
||||||
return externalId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogs returns the logs matching the given argument.
|
|
||||||
func (s *PublicFilterAPI) GetLogs(ctx context.Context, args NewFilterArgs) ([]vmlog, error) {
|
|
||||||
filter := New(s.apiBackend)
|
|
||||||
filter.SetBeginBlock(args.FromBlock.Int64())
|
|
||||||
filter.SetEndBlock(args.ToBlock.Int64())
|
|
||||||
filter.SetAddresses(args.Addresses)
|
|
||||||
filter.SetTopics(args.Topics)
|
|
||||||
|
|
||||||
logs, err := filter.Find(ctx)
|
|
||||||
return toRPCLogs(logs, false), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UninstallFilter removes the filter with the given filter id.
|
|
||||||
func (s *PublicFilterAPI) UninstallFilter(filterId string) bool {
|
|
||||||
s.filterMapMu.Lock()
|
|
||||||
defer s.filterMapMu.Unlock()
|
|
||||||
|
|
||||||
id, ok := s.filterMapping[filterId]
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
defer s.filterManager.Remove(id)
|
|
||||||
delete(s.filterMapping, filterId)
|
|
||||||
|
|
||||||
if _, ok := s.logQueue[id]; ok {
|
|
||||||
s.logMu.Lock()
|
|
||||||
defer s.logMu.Unlock()
|
|
||||||
delete(s.logQueue, id)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := s.blockQueue[id]; ok {
|
|
||||||
s.blockMu.Lock()
|
|
||||||
defer s.blockMu.Unlock()
|
|
||||||
delete(s.blockQueue, id)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := s.transactionQueue[id]; ok {
|
|
||||||
s.transactionMu.Lock()
|
|
||||||
defer s.transactionMu.Unlock()
|
|
||||||
delete(s.transactionQueue, id)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFilterType is a helper utility that determine the type of filter for the given filter id.
|
|
||||||
func (s *PublicFilterAPI) getFilterType(id int) byte {
|
|
||||||
if _, ok := s.blockQueue[id]; ok {
|
|
||||||
return blockFilterTy
|
|
||||||
} else if _, ok := s.transactionQueue[id]; ok {
|
|
||||||
return transactionFilterTy
|
|
||||||
} else if _, ok := s.logQueue[id]; ok {
|
|
||||||
return logFilterTy
|
|
||||||
}
|
|
||||||
|
|
||||||
return unknownFilterTy
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockFilterChanged returns a collection of block hashes for the block filter with the given id.
|
|
||||||
func (s *PublicFilterAPI) blockFilterChanged(id int) []common.Hash {
|
|
||||||
s.blockMu.Lock()
|
|
||||||
defer s.blockMu.Unlock()
|
|
||||||
|
|
||||||
if s.blockQueue[id] != nil {
|
|
||||||
res := s.blockQueue[id].get()
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// transactionFilterChanged returns a collection of transaction hashes for the pending
|
|
||||||
// transaction filter with the given id.
|
|
||||||
func (s *PublicFilterAPI) transactionFilterChanged(id int) []common.Hash {
|
|
||||||
s.blockMu.Lock()
|
|
||||||
defer s.blockMu.Unlock()
|
|
||||||
|
|
||||||
if s.transactionQueue[id] != nil {
|
|
||||||
return s.transactionQueue[id].get()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// logFilterChanged returns a collection of logs for the log filter with the given id.
|
|
||||||
func (s *PublicFilterAPI) logFilterChanged(id int) []vmlog {
|
|
||||||
s.logMu.Lock()
|
|
||||||
defer s.logMu.Unlock()
|
|
||||||
|
|
||||||
if s.logQueue[id] != nil {
|
|
||||||
return s.logQueue[id].get()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFilterLogs returns the logs for the filter with the given id.
|
|
||||||
func (s *PublicFilterAPI) GetFilterLogs(ctx context.Context, filterId string) ([]vmlog, error) {
|
|
||||||
id, ok := s.filterMapping[filterId]
|
|
||||||
if !ok {
|
|
||||||
return toRPCLogs(nil, false), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter := s.filterManager.Get(id); filter != nil {
|
|
||||||
logs, err := filter.Find(ctx)
|
|
||||||
return toRPCLogs(logs, false), err
|
|
||||||
}
|
|
||||||
|
|
||||||
return toRPCLogs(nil, false), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFilterChanges returns the logs for the filter with the given id since last time is was called.
|
|
||||||
// This can be used for polling.
|
|
||||||
func (s *PublicFilterAPI) GetFilterChanges(filterId string) interface{} {
|
|
||||||
s.filterMapMu.Lock()
|
|
||||||
id, ok := s.filterMapping[filterId]
|
|
||||||
s.filterMapMu.Unlock()
|
|
||||||
|
|
||||||
if !ok { // filter not found
|
|
||||||
return []interface{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s.getFilterType(id) {
|
|
||||||
case blockFilterTy:
|
|
||||||
return returnHashes(s.blockFilterChanged(id))
|
|
||||||
case transactionFilterTy:
|
|
||||||
return returnHashes(s.transactionFilterChanged(id))
|
|
||||||
case logFilterTy:
|
|
||||||
return s.logFilterChanged(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []interface{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type vmlog struct {
|
|
||||||
*vm.Log
|
|
||||||
Removed bool `json:"removed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type logQueue struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
logs []vmlog
|
|
||||||
timeout time.Time
|
|
||||||
id int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logQueue) add(logs ...vmlog) {
|
|
||||||
l.mu.Lock()
|
|
||||||
defer l.mu.Unlock()
|
|
||||||
|
|
||||||
l.logs = append(l.logs, logs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logQueue) get() []vmlog {
|
|
||||||
l.mu.Lock()
|
|
||||||
defer l.mu.Unlock()
|
|
||||||
|
|
||||||
l.timeout = time.Now()
|
|
||||||
tmp := l.logs
|
|
||||||
l.logs = nil
|
|
||||||
return tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
type hashQueue struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
hashes []common.Hash
|
|
||||||
timeout time.Time
|
|
||||||
id int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *hashQueue) add(hashes ...common.Hash) {
|
|
||||||
l.mu.Lock()
|
|
||||||
defer l.mu.Unlock()
|
|
||||||
|
|
||||||
l.hashes = append(l.hashes, hashes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *hashQueue) get() []common.Hash {
|
|
||||||
l.mu.Lock()
|
|
||||||
defer l.mu.Unlock()
|
|
||||||
|
|
||||||
l.timeout = time.Now()
|
|
||||||
tmp := l.hashes
|
|
||||||
l.hashes = nil
|
|
||||||
return tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFilterId generates a new random filter identifier that can be exposed to the outer world. By publishing random
|
|
||||||
// identifiers it is not feasible for DApp's to guess filter id's for other DApp's and uninstall or poll for them
|
|
||||||
// causing the affected DApp to miss data.
|
|
||||||
func newFilterId() (string, error) {
|
|
||||||
var subid [16]byte
|
|
||||||
n, _ := rand.Read(subid[:])
|
|
||||||
if n != 16 {
|
|
||||||
return "", errors.New("Unable to generate filter id")
|
|
||||||
}
|
|
||||||
return "0x" + hex.EncodeToString(subid[:]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// toRPCLogs is a helper that will convert a vm.Logs array to an structure which
|
|
||||||
// can hold additional information about the logs such as whether it was deleted.
|
|
||||||
// Additionally when nil is given it will by default instead create an empty slice
|
|
||||||
// instead. This is required by the RPC specification.
|
|
||||||
func toRPCLogs(logs vm.Logs, removed bool) []vmlog {
|
|
||||||
convertedLogs := make([]vmlog, len(logs))
|
|
||||||
for i, log := range logs {
|
|
||||||
convertedLogs[i] = vmlog{Log: log, Removed: removed}
|
|
||||||
}
|
|
||||||
return convertedLogs
|
|
||||||
}
|
|
||||||
|
|
||||||
// returnHashes is a helper that will return an empty hash array case the given hash array is nil, otherwise is will
|
|
||||||
// return the given hashes. The RPC interfaces defines that always an array is returned.
|
|
||||||
func returnHashes(hashes []common.Hash) []common.Hash {
|
|
||||||
if hashes == nil {
|
|
||||||
return []common.Hash{}
|
|
||||||
}
|
|
||||||
return hashes
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// package filters implements an ethereum filtering system for block,
|
|
||||||
// transactions and log events.
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterType determines the type of filter and is used to put the filter in to
|
|
||||||
// the correct bucket when added.
|
|
||||||
type FilterType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ChainFilter FilterType = iota // new block events filter
|
|
||||||
PendingTxFilter // pending transaction filter
|
|
||||||
LogFilter // new or removed log filter
|
|
||||||
PendingLogFilter // pending log filter
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterSystem manages filters that filter specific events such as
|
|
||||||
// block, transaction and log events. The Filtering system can be used to listen
|
|
||||||
// for specific LOG events fired by the EVM (Ethereum Virtual Machine).
|
|
||||||
type FilterSystem struct {
|
|
||||||
filterMu sync.RWMutex
|
|
||||||
filterId int
|
|
||||||
|
|
||||||
chainFilters map[int]*Filter
|
|
||||||
pendingTxFilters map[int]*Filter
|
|
||||||
logFilters map[int]*Filter
|
|
||||||
pendingLogFilters map[int]*Filter
|
|
||||||
|
|
||||||
// generic is an ugly hack for Get
|
|
||||||
generic map[int]*Filter
|
|
||||||
|
|
||||||
sub event.Subscription
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFilterSystem returns a newly allocated filter manager
|
|
||||||
func NewFilterSystem(mux *event.TypeMux) *FilterSystem {
|
|
||||||
fs := &FilterSystem{
|
|
||||||
chainFilters: make(map[int]*Filter),
|
|
||||||
pendingTxFilters: make(map[int]*Filter),
|
|
||||||
logFilters: make(map[int]*Filter),
|
|
||||||
pendingLogFilters: make(map[int]*Filter),
|
|
||||||
generic: make(map[int]*Filter),
|
|
||||||
}
|
|
||||||
fs.sub = mux.Subscribe(
|
|
||||||
core.PendingLogsEvent{},
|
|
||||||
core.RemovedLogsEvent{},
|
|
||||||
core.ChainEvent{},
|
|
||||||
core.TxPreEvent{},
|
|
||||||
vm.Logs(nil),
|
|
||||||
)
|
|
||||||
go fs.filterLoop()
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop quits the filter loop required for polling events
|
|
||||||
func (fs *FilterSystem) Stop() {
|
|
||||||
fs.sub.Unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a filter to the filter manager
|
|
||||||
func (fs *FilterSystem) Add(filter *Filter, filterType FilterType) (int, error) {
|
|
||||||
fs.filterMu.Lock()
|
|
||||||
defer fs.filterMu.Unlock()
|
|
||||||
|
|
||||||
id := fs.filterId
|
|
||||||
filter.created = time.Now()
|
|
||||||
|
|
||||||
switch filterType {
|
|
||||||
case ChainFilter:
|
|
||||||
fs.chainFilters[id] = filter
|
|
||||||
case PendingTxFilter:
|
|
||||||
fs.pendingTxFilters[id] = filter
|
|
||||||
case LogFilter:
|
|
||||||
fs.logFilters[id] = filter
|
|
||||||
case PendingLogFilter:
|
|
||||||
fs.pendingLogFilters[id] = filter
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unknown filter type %v", filterType)
|
|
||||||
}
|
|
||||||
fs.generic[id] = filter
|
|
||||||
|
|
||||||
fs.filterId++
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes a filter by filter id
|
|
||||||
func (fs *FilterSystem) Remove(id int) {
|
|
||||||
fs.filterMu.Lock()
|
|
||||||
defer fs.filterMu.Unlock()
|
|
||||||
|
|
||||||
delete(fs.chainFilters, id)
|
|
||||||
delete(fs.pendingTxFilters, id)
|
|
||||||
delete(fs.logFilters, id)
|
|
||||||
delete(fs.pendingLogFilters, id)
|
|
||||||
delete(fs.generic, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *FilterSystem) Get(id int) *Filter {
|
|
||||||
fs.filterMu.RLock()
|
|
||||||
defer fs.filterMu.RUnlock()
|
|
||||||
|
|
||||||
return fs.generic[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterLoop waits for specific events from ethereum and fires their handlers
|
|
||||||
// when the filter matches the requirements.
|
|
||||||
func (fs *FilterSystem) filterLoop() {
|
|
||||||
for event := range fs.sub.Chan() {
|
|
||||||
switch ev := event.Data.(type) {
|
|
||||||
case core.ChainEvent:
|
|
||||||
fs.filterMu.RLock()
|
|
||||||
for _, filter := range fs.chainFilters {
|
|
||||||
if filter.BlockCallback != nil && !filter.created.After(event.Time) {
|
|
||||||
filter.BlockCallback(ev.Block, ev.Logs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.filterMu.RUnlock()
|
|
||||||
case core.TxPreEvent:
|
|
||||||
fs.filterMu.RLock()
|
|
||||||
for _, filter := range fs.pendingTxFilters {
|
|
||||||
if filter.TransactionCallback != nil && !filter.created.After(event.Time) {
|
|
||||||
filter.TransactionCallback(ev.Tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.filterMu.RUnlock()
|
|
||||||
|
|
||||||
case vm.Logs:
|
|
||||||
fs.filterMu.RLock()
|
|
||||||
for _, filter := range fs.logFilters {
|
|
||||||
if filter.LogCallback != nil && !filter.created.After(event.Time) {
|
|
||||||
for _, log := range filter.FilterLogs(ev) {
|
|
||||||
filter.LogCallback(log, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.filterMu.RUnlock()
|
|
||||||
case core.RemovedLogsEvent:
|
|
||||||
fs.filterMu.RLock()
|
|
||||||
for _, filter := range fs.logFilters {
|
|
||||||
if filter.LogCallback != nil && !filter.created.After(event.Time) {
|
|
||||||
for _, removedLog := range filter.FilterLogs(ev.Logs) {
|
|
||||||
filter.LogCallback(removedLog, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.filterMu.RUnlock()
|
|
||||||
case core.PendingLogsEvent:
|
|
||||||
fs.filterMu.RLock()
|
|
||||||
for _, filter := range fs.pendingLogFilters {
|
|
||||||
if filter.LogCallback != nil && !filter.created.After(event.Time) {
|
|
||||||
for _, pendingLog := range ev.Logs {
|
|
||||||
filter.LogCallback(pendingLog, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.filterMu.RUnlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package rpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rs/cors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxHTTPRequestContentLength = 1024 * 128
|
|
||||||
)
|
|
||||||
|
|
||||||
// httpClient connects to a geth RPC server over HTTP.
|
|
||||||
type httpClient struct {
|
|
||||||
endpoint *url.URL // HTTP-RPC server endpoint
|
|
||||||
httpClient http.Client // reuse connection
|
|
||||||
lastRes []byte // HTTP requests are synchronous, store last response
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTTPClient create a new RPC clients that connection to a geth RPC server
|
|
||||||
// over HTTP.
|
|
||||||
func NewHTTPClient(endpoint string) (Client, error) {
|
|
||||||
url, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &httpClient{endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send will serialize the given msg to JSON and sends it to the RPC server.
|
|
||||||
// Since HTTP is synchronous the response is stored until Recv is called.
|
|
||||||
func (client *httpClient) Send(msg interface{}) error {
|
|
||||||
var body []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
client.lastRes = nil
|
|
||||||
if body, err = json.Marshal(msg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.httpClient.Post(client.endpoint.String(), "application/json", bytes.NewReader(body))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
client.lastRes, err = ioutil.ReadAll(resp.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("request failed: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recv will try to deserialize the last received response into the given msg.
|
|
||||||
func (client *httpClient) Recv(msg interface{}) error {
|
|
||||||
return json.Unmarshal(client.lastRes, &msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is not necessary for httpClient
|
|
||||||
func (client *httpClient) Close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportedModules will return the collection of offered RPC modules.
|
|
||||||
func (client *httpClient) SupportedModules() (map[string]string, error) {
|
|
||||||
return SupportedModules(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpReadWriteNopCloser wraps a io.Reader and io.Writer with a NOP Close method.
|
|
||||||
type httpReadWriteNopCloser struct {
|
|
||||||
io.Reader
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close does nothing and returns always nil
|
|
||||||
func (t *httpReadWriteNopCloser) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newJSONHTTPHandler creates a HTTP handler that will parse incoming JSON requests,
|
|
||||||
// send the request to the given API provider and sends the response back to the caller.
|
|
||||||
func newJSONHTTPHandler(srv *Server) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.ContentLength > maxHTTPRequestContentLength {
|
|
||||||
http.Error(w,
|
|
||||||
fmt.Sprintf("content length too large (%d>%d)", r.ContentLength, maxHTTPRequestContentLength),
|
|
||||||
http.StatusRequestEntityTooLarge)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("content-type", "application/json")
|
|
||||||
|
|
||||||
// create a codec that reads direct from the request body until
|
|
||||||
// EOF and writes the response to w and order the server to process
|
|
||||||
// a single request.
|
|
||||||
codec := NewJSONCodec(&httpReadWriteNopCloser{r.Body, w})
|
|
||||||
defer codec.Close()
|
|
||||||
srv.ServeSingleRequest(codec, OptionMethodInvocation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTTPServer creates a new HTTP RPC server around an API provider.
|
|
||||||
func NewHTTPServer(corsString string, srv *Server) *http.Server {
|
|
||||||
var allowedOrigins []string
|
|
||||||
for _, domain := range strings.Split(corsString, ",") {
|
|
||||||
allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
c := cors.New(cors.Options{
|
|
||||||
AllowedOrigins: allowedOrigins,
|
|
||||||
AllowedMethods: []string{"POST", "GET"},
|
|
||||||
})
|
|
||||||
|
|
||||||
handler := c.Handler(newJSONHTTPHandler(srv))
|
|
||||||
|
|
||||||
return &http.Server{
|
|
||||||
Handler: handler,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package rpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// inProcClient is an in-process buffer stream attached to an RPC server.
|
|
||||||
type inProcClient struct {
|
|
||||||
server *Server
|
|
||||||
cl io.Closer
|
|
||||||
enc *json.Encoder
|
|
||||||
dec *json.Decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close tears down the request channel of the in-proc client.
|
|
||||||
func (c *inProcClient) Close() {
|
|
||||||
c.cl.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInProcRPCClient creates an in-process buffer stream attachment to a given
|
|
||||||
// RPC server.
|
|
||||||
func NewInProcRPCClient(handler *Server) Client {
|
|
||||||
p1, p2 := net.Pipe()
|
|
||||||
go handler.ServeCodec(NewJSONCodec(p1), OptionMethodInvocation|OptionSubscriptions)
|
|
||||||
return &inProcClient{handler, p2, json.NewEncoder(p2), json.NewDecoder(p2)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send marshals a message into a json format and injects in into the client
|
|
||||||
// request channel.
|
|
||||||
func (c *inProcClient) Send(msg interface{}) error {
|
|
||||||
return c.enc.Encode(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recv reads a message from the response channel and tries to parse it into the
|
|
||||||
// given msg interface.
|
|
||||||
func (c *inProcClient) Recv(msg interface{}) error {
|
|
||||||
return c.dec.Decode(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the collection of modules the RPC server offers.
|
|
||||||
func (c *inProcClient) SupportedModules() (map[string]string, error) {
|
|
||||||
return SupportedModules(c)
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package rpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateIPCListener creates an listener, on Unix platforms this is a unix socket, on Windows this is a named pipe
|
|
||||||
func CreateIPCListener(endpoint string) (net.Listener, error) {
|
|
||||||
return ipcListen(endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ipcClient represent an IPC RPC client. It will connect to a given endpoint and tries to communicate with a node using
|
|
||||||
// JSON serialization.
|
|
||||||
type ipcClient struct {
|
|
||||||
endpoint string
|
|
||||||
conn net.Conn
|
|
||||||
out *json.Encoder
|
|
||||||
in *json.Decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIPCClient create a new IPC client that will connect on the given endpoint. Messages are JSON encoded and encoded.
|
|
||||||
// On Unix it assumes the endpoint is the full path to a unix socket, and Windows the endpoint is an identifier for a
|
|
||||||
// named pipe.
|
|
||||||
func NewIPCClient(endpoint string) (Client, error) {
|
|
||||||
conn, err := newIPCConnection(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ipcClient{endpoint: endpoint, conn: conn, in: json.NewDecoder(conn), out: json.NewEncoder(conn)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send will serialize the given message and send it to the server.
|
|
||||||
// When sending the message fails it will try to reconnect once and send the message again.
|
|
||||||
func (client *ipcClient) Send(msg interface{}) error {
|
|
||||||
if err := client.out.Encode(msg); err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// retry once
|
|
||||||
client.conn.Close()
|
|
||||||
|
|
||||||
conn, err := newIPCConnection(client.endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client.conn = conn
|
|
||||||
client.in = json.NewDecoder(conn)
|
|
||||||
client.out = json.NewEncoder(conn)
|
|
||||||
|
|
||||||
return client.out.Encode(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recv will read a message from the connection and tries to parse it. It assumes the received message is JSON encoded.
|
|
||||||
func (client *ipcClient) Recv(msg interface{}) error {
|
|
||||||
return client.in.Decode(&msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close will close the underlying IPC connection
|
|
||||||
func (client *ipcClient) Close() {
|
|
||||||
client.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportedModules will return the collection of offered RPC modules.
|
|
||||||
func (client *ipcClient) SupportedModules() (map[string]string, error) {
|
|
||||||
return SupportedModules(client)
|
|
||||||
}
|
|
|
@ -1,297 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package rpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNotificationsUnsupported is returned when the connection doesn't support notifications
|
|
||||||
ErrNotificationsUnsupported = errors.New("notifications not supported")
|
|
||||||
|
|
||||||
// ErrNotificationNotFound is returned when the notification for the given id is not found
|
|
||||||
ErrNotificationNotFound = errors.New("notification not found")
|
|
||||||
|
|
||||||
// errNotifierStopped is returned when the notifier is stopped (e.g. codec is closed)
|
|
||||||
errNotifierStopped = errors.New("unable to send notification")
|
|
||||||
|
|
||||||
// errNotificationQueueFull is returns when there are too many notifications in the queue
|
|
||||||
errNotificationQueueFull = errors.New("too many pending notifications")
|
|
||||||
)
|
|
||||||
|
|
||||||
// unsubSignal is a signal that the subscription is unsubscribed. It is used to flush buffered
|
|
||||||
// notifications that might be pending in the internal queue.
|
|
||||||
var unsubSignal = new(struct{})
|
|
||||||
|
|
||||||
// UnsubscribeCallback defines a callback that is called when a subcription ends.
|
|
||||||
// It receives the subscription id as argument.
|
|
||||||
type UnsubscribeCallback func(id string)
|
|
||||||
|
|
||||||
// notification is a helper object that holds event data for a subscription
|
|
||||||
type notification struct {
|
|
||||||
sub *bufferedSubscription // subscription id
|
|
||||||
data interface{} // event data
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Notifier type describes the interface for objects that can send create subscriptions
|
|
||||||
type Notifier interface {
|
|
||||||
// Create a new subscription. The given callback is called when this subscription
|
|
||||||
// is cancelled (e.g. client send an unsubscribe, connection closed).
|
|
||||||
NewSubscription(UnsubscribeCallback) (Subscription, error)
|
|
||||||
// Cancel subscription
|
|
||||||
Unsubscribe(id string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type notifierKey struct{}
|
|
||||||
|
|
||||||
// NotifierFromContext returns the Notifier value stored in ctx, if any.
|
|
||||||
func NotifierFromContext(ctx context.Context) (Notifier, bool) {
|
|
||||||
n, ok := ctx.Value(notifierKey{}).(Notifier)
|
|
||||||
return n, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscription defines the interface for objects that can notify subscribers
|
|
||||||
type Subscription interface {
|
|
||||||
// Inform client of an event
|
|
||||||
Notify(data interface{}) error
|
|
||||||
// Unique identifier
|
|
||||||
ID() string
|
|
||||||
// Cancel subscription
|
|
||||||
Cancel() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// bufferedSubscription is a subscription that uses a bufferedNotifier to send
|
|
||||||
// notifications to subscribers.
|
|
||||||
type bufferedSubscription struct {
|
|
||||||
id string
|
|
||||||
unsubOnce sync.Once // call unsub method once
|
|
||||||
unsub UnsubscribeCallback // called on Unsubscribed
|
|
||||||
notifier *bufferedNotifier // forward notifications to
|
|
||||||
pending chan interface{} // closed when active
|
|
||||||
flushed chan interface{} // closed when all buffered notifications are send
|
|
||||||
lastNotification time.Time // last time a notification was send
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the subscription identifier that the client uses to refer to this instance.
|
|
||||||
func (s *bufferedSubscription) ID() string {
|
|
||||||
return s.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel informs the notifier that this subscription is cancelled by the API
|
|
||||||
func (s *bufferedSubscription) Cancel() error {
|
|
||||||
return s.notifier.Unsubscribe(s.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify the subscriber of a particular event.
|
|
||||||
func (s *bufferedSubscription) Notify(data interface{}) error {
|
|
||||||
return s.notifier.send(s.id, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bufferedNotifier is a notifier that queues notifications in an internal queue and
|
|
||||||
// send them as fast as possible to the client from this queue. It will stop if the
|
|
||||||
// queue grows past a given size.
|
|
||||||
type bufferedNotifier struct {
|
|
||||||
codec ServerCodec // underlying connection
|
|
||||||
mu sync.Mutex // guard internal state
|
|
||||||
subscriptions map[string]*bufferedSubscription // keep track of subscriptions associated with codec
|
|
||||||
queueSize int // max number of items in queue
|
|
||||||
queue chan *notification // notification queue
|
|
||||||
stopped bool // indication if this notifier is ordered to stop
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBufferedNotifier returns a notifier that queues notifications in an internal queue
|
|
||||||
// from which notifications are send as fast as possible to the client. If the queue size
|
|
||||||
// limit is reached (client is unable to keep up) it will stop and closes the codec.
|
|
||||||
func newBufferedNotifier(codec ServerCodec, size int) *bufferedNotifier {
|
|
||||||
notifier := &bufferedNotifier{
|
|
||||||
codec: codec,
|
|
||||||
subscriptions: make(map[string]*bufferedSubscription),
|
|
||||||
queue: make(chan *notification, size),
|
|
||||||
queueSize: size,
|
|
||||||
}
|
|
||||||
|
|
||||||
go notifier.run()
|
|
||||||
|
|
||||||
return notifier
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSubscription creates a new subscription that forwards events to this instance internal
|
|
||||||
// queue. The given callback is called when the subscription is unsubscribed/cancelled.
|
|
||||||
func (n *bufferedNotifier) NewSubscription(callback UnsubscribeCallback) (Subscription, error) {
|
|
||||||
id, err := newSubscriptionID()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
|
|
||||||
if n.stopped {
|
|
||||||
return nil, errNotifierStopped
|
|
||||||
}
|
|
||||||
|
|
||||||
sub := &bufferedSubscription{
|
|
||||||
id: id,
|
|
||||||
unsub: callback,
|
|
||||||
notifier: n,
|
|
||||||
pending: make(chan interface{}),
|
|
||||||
flushed: make(chan interface{}),
|
|
||||||
lastNotification: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
n.subscriptions[id] = sub
|
|
||||||
|
|
||||||
return sub, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the given subscription. If subscription is not found notificationNotFoundErr is returned.
|
|
||||||
func (n *bufferedNotifier) Unsubscribe(subid string) error {
|
|
||||||
n.mu.Lock()
|
|
||||||
sub, found := n.subscriptions[subid]
|
|
||||||
n.mu.Unlock()
|
|
||||||
|
|
||||||
if found {
|
|
||||||
// send the unsubscribe signal, this will cause the notifier not to accept new events
|
|
||||||
// for this subscription and will close the flushed channel after the last (buffered)
|
|
||||||
// notification was send to the client.
|
|
||||||
if err := n.send(subid, unsubSignal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for confirmation that all (buffered) events are send for this subscription.
|
|
||||||
// this ensures that the unsubscribe method response is not send before all buffered
|
|
||||||
// events for this subscription are send.
|
|
||||||
<-sub.flushed
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrNotificationNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send enques the given data for the subscription with public ID on the internal queue. t returns
|
|
||||||
// an error when the notifier is stopped or the queue is full. If data is the unsubscribe signal it
|
|
||||||
// will remove the subscription with the given id from the subscription collection.
|
|
||||||
func (n *bufferedNotifier) send(id string, data interface{}) error {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
|
|
||||||
if n.stopped {
|
|
||||||
return errNotifierStopped
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
subscription *bufferedSubscription
|
|
||||||
found bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// check if subscription is associated with this connection, it might be cancelled
|
|
||||||
// (subscribe/connection closed)
|
|
||||||
if subscription, found = n.subscriptions[id]; !found {
|
|
||||||
glog.V(logger.Error).Infof("received notification for unknown subscription %s\n", id)
|
|
||||||
return ErrNotificationNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// received the unsubscribe signal. Add it to the queue to make sure any pending notifications
|
|
||||||
// for this subscription are send. When the run loop receives this singal it will signal that
|
|
||||||
// all pending subscriptions are flushed and that the confirmation of the unsubscribe can be
|
|
||||||
// send to the user. Remove the subscriptions to make sure new notifications are not accepted.
|
|
||||||
if data == unsubSignal {
|
|
||||||
delete(n.subscriptions, id)
|
|
||||||
if subscription.unsub != nil {
|
|
||||||
subscription.unsubOnce.Do(func() { subscription.unsub(id) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription.lastNotification = time.Now()
|
|
||||||
|
|
||||||
if len(n.queue) >= n.queueSize {
|
|
||||||
glog.V(logger.Warn).Infoln("too many buffered notifications -> close connection")
|
|
||||||
n.codec.Close()
|
|
||||||
return errNotificationQueueFull
|
|
||||||
}
|
|
||||||
|
|
||||||
n.queue <- ¬ification{subscription, data}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// run reads notifications from the internal queue and sends them to the client. In case of an
|
|
||||||
// error, or when the codec is closed it will cancel all active subscriptions and returns.
|
|
||||||
func (n *bufferedNotifier) run() {
|
|
||||||
defer func() {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
|
|
||||||
n.stopped = true
|
|
||||||
close(n.queue)
|
|
||||||
|
|
||||||
// on exit call unsubscribe callback
|
|
||||||
for id, sub := range n.subscriptions {
|
|
||||||
if sub.unsub != nil {
|
|
||||||
sub.unsubOnce.Do(func() { sub.unsub(id) })
|
|
||||||
}
|
|
||||||
close(sub.flushed)
|
|
||||||
delete(n.subscriptions, id)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case notification := <-n.queue:
|
|
||||||
// It can happen that an event is raised before the RPC server was able to send the sub
|
|
||||||
// id to the client. Therefore subscriptions are marked as pending until the sub id was
|
|
||||||
// send. The RPC server will activate the subscription by closing the pending chan.
|
|
||||||
<-notification.sub.pending
|
|
||||||
|
|
||||||
if notification.data == unsubSignal {
|
|
||||||
// unsubSignal is the last accepted message for this subscription. Raise the signal
|
|
||||||
// that all buffered notifications are sent by closing the flushed channel. This
|
|
||||||
// indicates that the response for the unsubscribe can be send to the client.
|
|
||||||
close(notification.sub.flushed)
|
|
||||||
} else {
|
|
||||||
msg := n.codec.CreateNotification(notification.sub.id, notification.data)
|
|
||||||
if err := n.codec.Write(msg); err != nil {
|
|
||||||
n.codec.Close()
|
|
||||||
// unable to send notification to client, unsubscribe all subscriptions
|
|
||||||
glog.V(logger.Warn).Infof("unable to send notification - %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-n.codec.Closed(): // connection was closed
|
|
||||||
glog.V(logger.Debug).Infoln("codec closed, stop subscriptions")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marks the subscription as active. This will causes the notifications for this subscription to be
|
|
||||||
// forwarded to the client.
|
|
||||||
func (n *bufferedNotifier) activate(subid string) {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
|
|
||||||
if sub, found := n.subscriptions[subid]; found {
|
|
||||||
close(sub.pending)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package rpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
"gopkg.in/fatih/set.v0"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wsReaderWriterCloser reads and write payloads from and to a websocket connection.
|
|
||||||
type wsReaderWriterCloser struct {
|
|
||||||
c *websocket.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read will read incoming payload data into p.
|
|
||||||
func (rw *wsReaderWriterCloser) Read(p []byte) (int, error) {
|
|
||||||
return rw.c.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes p to the websocket.
|
|
||||||
func (rw *wsReaderWriterCloser) Write(p []byte) (int, error) {
|
|
||||||
return rw.c.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the websocket connection.
|
|
||||||
func (rw *wsReaderWriterCloser) Close() error {
|
|
||||||
return rw.c.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// wsHandshakeValidator returns a handler that verifies the origin during the
|
|
||||||
// websocket upgrade process. When a '*' is specified as an allowed origins all
|
|
||||||
// connections are accepted.
|
|
||||||
func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http.Request) error {
|
|
||||||
origins := set.New()
|
|
||||||
allowAllOrigins := false
|
|
||||||
|
|
||||||
for _, origin := range allowedOrigins {
|
|
||||||
if origin == "*" {
|
|
||||||
allowAllOrigins = true
|
|
||||||
}
|
|
||||||
if origin != "" {
|
|
||||||
origins.Add(strings.ToLower(origin))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow localhost if no allowedOrigins are specified.
|
|
||||||
if len(origins.List()) == 0 {
|
|
||||||
origins.Add("http://localhost")
|
|
||||||
if hostname, err := os.Hostname(); err == nil {
|
|
||||||
origins.Add("http://" + strings.ToLower(hostname))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(logger.Debug).Infof("Allowed origin(s) for WS RPC interface %v\n", origins.List())
|
|
||||||
|
|
||||||
f := func(cfg *websocket.Config, req *http.Request) error {
|
|
||||||
origin := strings.ToLower(req.Header.Get("Origin"))
|
|
||||||
if allowAllOrigins || origins.Has(origin) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
glog.V(logger.Debug).Infof("origin '%s' not allowed on WS-RPC interface\n", origin)
|
|
||||||
return fmt.Errorf("origin %s not allowed", origin)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWSServer creates a new websocket RPC server around an API provider.
|
|
||||||
func NewWSServer(allowedOrigins string, handler *Server) *http.Server {
|
|
||||||
return &http.Server{
|
|
||||||
Handler: websocket.Server{
|
|
||||||
Handshake: wsHandshakeValidator(strings.Split(allowedOrigins, ",")),
|
|
||||||
Handler: func(conn *websocket.Conn) {
|
|
||||||
handler.ServeCodec(NewJSONCodec(&wsReaderWriterCloser{conn}),
|
|
||||||
OptionMethodInvocation|OptionSubscriptions)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wsClient represents a RPC client that communicates over websockets with a
|
|
||||||
// RPC server.
|
|
||||||
type wsClient struct {
|
|
||||||
endpoint string
|
|
||||||
connMu sync.Mutex
|
|
||||||
conn *websocket.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWSClientj creates a new RPC client that communicates with a RPC server
|
|
||||||
// that is listening on the given endpoint using JSON encoding.
|
|
||||||
func NewWSClient(endpoint string) (Client, error) {
|
|
||||||
return &wsClient{endpoint: endpoint}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// connection will return a websocket connection to the RPC server. It will
|
|
||||||
// (re)connect when necessary.
|
|
||||||
func (client *wsClient) connection() (*websocket.Conn, error) {
|
|
||||||
if client.conn != nil {
|
|
||||||
return client.conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
origin, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
origin = "http://" + origin
|
|
||||||
client.conn, err = websocket.Dial(client.endpoint, "", origin)
|
|
||||||
|
|
||||||
return client.conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SupportedModules is the collection of modules the RPC server offers.
|
|
||||||
func (client *wsClient) SupportedModules() (map[string]string, error) {
|
|
||||||
return SupportedModules(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send writes the JSON serialized msg to the websocket. It will create a new
|
|
||||||
// websocket connection to the server if the client is currently not connected.
|
|
||||||
func (client *wsClient) Send(msg interface{}) (err error) {
|
|
||||||
client.connMu.Lock()
|
|
||||||
defer client.connMu.Unlock()
|
|
||||||
|
|
||||||
var conn *websocket.Conn
|
|
||||||
if conn, err = client.connection(); err == nil {
|
|
||||||
if err = websocket.JSON.Send(conn, msg); err != nil {
|
|
||||||
client.conn.Close()
|
|
||||||
client.conn = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recv reads a JSON message from the websocket and unmarshals it into msg.
|
|
||||||
func (client *wsClient) Recv(msg interface{}) (err error) {
|
|
||||||
client.connMu.Lock()
|
|
||||||
defer client.connMu.Unlock()
|
|
||||||
|
|
||||||
var conn *websocket.Conn
|
|
||||||
if conn, err = client.connection(); err == nil {
|
|
||||||
if err = websocket.JSON.Receive(conn, msg); err != nil {
|
|
||||||
client.conn.Close()
|
|
||||||
client.conn = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the underlaying websocket connection.
|
|
||||||
func (client *wsClient) Close() {
|
|
||||||
client.connMu.Lock()
|
|
||||||
defer client.connMu.Unlock()
|
|
||||||
|
|
||||||
if client.conn != nil {
|
|
||||||
client.conn.Close()
|
|
||||||
client.conn = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue