This commit is contained in:
obscuren 2015-03-20 16:02:01 +01:00
commit a59bb053f4
11 changed files with 634 additions and 729 deletions

View File

@ -34,7 +34,7 @@ type Filter struct {
topics [][]common.Hash
BlockCallback func(*types.Block, state.Logs)
PendingCallback func(*types.Block, state.Logs)
PendingCallback func(*types.Transaction)
LogsCallback func(state.Logs)
}
@ -46,7 +46,7 @@ func NewFilter(eth Backend) *Filter {
// SetOptions copies the filter options to the filter it self. The reason for this "silly" copy
// is simply because named arguments in this case is extremely nice and readable.
func (self *Filter) SetOptions(options FilterOptions) {
func (self *Filter) SetOptions(options *FilterOptions) {
self.earliest = options.Earliest
self.latest = options.Latest
self.skip = options.Skip

View File

@ -48,7 +48,9 @@ func (self *FilterManager) InstallFilter(filter *core.Filter) (id int) {
func (self *FilterManager) UninstallFilter(id int) {
self.filterMu.Lock()
defer self.filterMu.Unlock()
delete(self.filters, id)
if _, ok := self.filters[id]; ok {
delete(self.filters, id)
}
}
// GetFilter retrieves a filter installed using InstallFilter.
@ -62,8 +64,9 @@ func (self *FilterManager) GetFilter(id int) *core.Filter {
func (self *FilterManager) filterLoop() {
// Subscribe to events
events := self.eventMux.Subscribe(
core.PendingBlockEvent{},
//core.PendingBlockEvent{},
core.ChainEvent{},
core.TxPreEvent{},
state.Logs(nil))
out:
@ -82,11 +85,11 @@ out:
}
self.filterMu.RUnlock()
case core.PendingBlockEvent:
case core.TxPreEvent:
self.filterMu.RLock()
for _, filter := range self.filters {
if filter.PendingCallback != nil {
filter.PendingCallback(event.Block, event.Logs)
filter.PendingCallback(event.Tx)
}
}
self.filterMu.RUnlock()

View File

@ -2,491 +2,49 @@ package rpc
import (
"encoding/json"
"fmt"
"math/big"
"path"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/event/filter"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/xeth"
)
var (
defaultGasPrice = big.NewInt(150000000000)
defaultGas = big.NewInt(500000)
filterTickerTime = 5 * time.Minute
)
type EthereumApi struct {
eth *xeth.XEth
xethMu sync.RWMutex
mux *event.TypeMux
quit chan struct{}
filterManager *filter.FilterManager
logMut sync.RWMutex
logs map[int]*logFilter
messagesMut sync.RWMutex
messages map[int]*whisperFilter
// Register keeps a list of accounts and transaction data
regmut sync.Mutex
register map[string][]*NewTxArgs
db common.Database
db common.Database
}
func NewEthereumApi(eth *xeth.XEth, dataDir string) *EthereumApi {
// What about when dataDir is empty?
db, _ := ethdb.NewLDBDatabase(path.Join(dataDir, "dapps"))
api := &EthereumApi{
eth: eth,
mux: eth.Backend().EventMux(),
quit: make(chan struct{}),
filterManager: filter.NewFilterManager(eth.Backend().EventMux()),
logs: make(map[int]*logFilter),
messages: make(map[int]*whisperFilter),
db: db,
eth: eth,
db: db,
}
go api.filterManager.Start()
go api.start()
return api
}
func (self *EthereumApi) xethWithStateNum(num int64) *xeth.XEth {
chain := self.xeth().Backend().ChainManager()
var block *types.Block
func (self *EthereumApi) xeth() *xeth.XEth {
self.xethMu.RLock()
defer self.xethMu.RUnlock()
if num < 0 {
num = chain.CurrentBlock().Number().Int64() + num + 1
}
block = chain.GetBlockByNumber(uint64(num))
var st *state.StateDB
if block != nil {
st = state.New(block.Root(), self.xeth().Backend().StateDb())
} else {
st = chain.State()
}
return self.xeth().WithState(st)
return self.eth
}
func (self *EthereumApi) getStateWithNum(num int64) *xeth.State {
return self.xethWithStateNum(num).State()
}
func (self *EthereumApi) start() {
timer := time.NewTicker(2 * time.Second)
done:
for {
select {
case <-timer.C:
self.logMut.Lock()
self.messagesMut.Lock()
for id, filter := range self.logs {
if time.Since(filter.timeout) > filterTickerTime {
self.filterManager.UninstallFilter(id)
delete(self.logs, id)
}
}
for id, filter := range self.messages {
if time.Since(filter.timeout) > filterTickerTime {
self.xeth().Whisper().Unwatch(id)
delete(self.messages, id)
}
}
self.messagesMut.Unlock()
self.logMut.Unlock()
case <-self.quit:
break done
}
}
}
func (self *EthereumApi) stop() {
close(self.quit)
}
// func (self *EthereumApi) Register(args string, reply *interface{}) error {
// self.regmut.Lock()
// defer self.regmut.Unlock()
// if _, ok := self.register[args]; ok {
// self.register[args] = nil // register with empty
// }
// return nil
// }
// func (self *EthereumApi) Unregister(args string, reply *interface{}) error {
// self.regmut.Lock()
// defer self.regmut.Unlock()
// delete(self.register, args)
// return nil
// }
// func (self *EthereumApi) WatchTx(args string, reply *interface{}) error {
// self.regmut.Lock()
// defer self.regmut.Unlock()
// txs := self.register[args]
// self.register[args] = nil
// *reply = txs
// return nil
// }
func (self *EthereumApi) NewFilter(args *FilterOptions, reply *interface{}) error {
var id int
filter := core.NewFilter(self.xeth().Backend())
filter.SetOptions(toFilterOptions(args))
filter.LogsCallback = func(logs state.Logs) {
self.logMut.Lock()
defer self.logMut.Unlock()
self.logs[id].add(logs...)
}
id = self.filterManager.InstallFilter(filter)
self.logs[id] = &logFilter{timeout: time.Now()}
*reply = common.ToHex(big.NewInt(int64(id)).Bytes())
return nil
}
func (self *EthereumApi) UninstallFilter(id int, reply *interface{}) error {
if _, ok := self.logs[id]; ok {
delete(self.logs, id)
}
self.filterManager.UninstallFilter(id)
*reply = true
return nil
}
func (self *EthereumApi) NewFilterString(args *FilterStringArgs, reply *interface{}) error {
var id int
filter := core.NewFilter(self.xeth().Backend())
callback := func(block *types.Block, logs state.Logs) {
self.logMut.Lock()
defer self.logMut.Unlock()
for _, log := range logs {
self.logs[id].add(log)
}
self.logs[id].add(&state.StateLog{})
}
switch args.Word {
case "pending":
filter.PendingCallback = callback
case "latest":
filter.BlockCallback = callback
default:
return NewValidationError("Word", "Must be `latest` or `pending`")
}
id = self.filterManager.InstallFilter(filter)
self.logs[id] = &logFilter{timeout: time.Now()}
*reply = common.ToHex(big.NewInt(int64(id)).Bytes())
return nil
}
func (self *EthereumApi) FilterChanged(id int, reply *interface{}) error {
self.logMut.Lock()
defer self.logMut.Unlock()
if self.logs[id] != nil {
*reply = toLogs(self.logs[id].get())
}
return nil
}
func (self *EthereumApi) Logs(id int, reply *interface{}) error {
self.logMut.Lock()
defer self.logMut.Unlock()
filter := self.filterManager.GetFilter(id)
if filter != nil {
*reply = toLogs(filter.Find())
}
return nil
}
func (self *EthereumApi) AllLogs(args *FilterOptions, reply *interface{}) error {
filter := core.NewFilter(self.xeth().Backend())
filter.SetOptions(toFilterOptions(args))
*reply = toLogs(filter.Find())
return nil
}
func (p *EthereumApi) Transact(args *NewTxArgs, reply *interface{}) (err error) {
// TODO if no_private_key then
//if _, exists := p.register[args.From]; exists {
// p.register[args.From] = append(p.register[args.From], args)
//} else {
/*
account := accounts.Get(common.FromHex(args.From))
if account != nil {
if account.Unlocked() {
if !unlockAccount(account) {
return
}
}
result, _ := account.Transact(common.FromHex(args.To), common.FromHex(args.Value), common.FromHex(args.Gas), common.FromHex(args.GasPrice), common.FromHex(args.Data))
if len(result) > 0 {
*reply = common.ToHex(result)
}
} else if _, exists := p.register[args.From]; exists {
p.register[ags.From] = append(p.register[args.From], args)
}
*/
if err := args.requirements(); err != nil {
return err
}
// TODO: align default values to have the same type, e.g. not depend on
// common.Value conversions later on
if args.Gas.Cmp(big.NewInt(0)) == 0 {
args.Gas = defaultGas
}
if args.GasPrice.Cmp(big.NewInt(0)) == 0 {
args.GasPrice = defaultGasPrice
}
*reply, err = p.xeth().Transact(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
if err != nil {
fmt.Println("err:", err)
return err
}
return nil
}
func (p *EthereumApi) Call(args *NewTxArgs, reply *interface{}) error {
result, err := p.xethWithStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
if err != nil {
return err
}
*reply = result
return nil
}
func (p *EthereumApi) GetBalance(args *GetBalanceArgs, reply *interface{}) error {
if err := args.requirements(); err != nil {
return err
}
state := p.getStateWithNum(args.BlockNumber).SafeGet(args.Address)
*reply = common.ToHex(state.Balance().Bytes())
return nil
}
func (p *EthereumApi) GetStorage(args *GetStorageArgs, reply *interface{}) error {
if err := args.requirements(); err != nil {
return err
}
*reply = p.getStateWithNum(args.BlockNumber).SafeGet(args.Address).Storage()
return nil
}
func (p *EthereumApi) GetStorageAt(args *GetStorageAtArgs, reply *interface{}) error {
if err := args.requirements(); err != nil {
return err
}
state := p.getStateWithNum(args.BlockNumber).SafeGet(args.Address)
value := state.StorageString(args.Key)
var hx string
if strings.Index(args.Key, "0x") == 0 {
hx = string([]byte(args.Key)[2:])
} else {
// Convert the incoming string (which is a bigint) into hex
i, _ := new(big.Int).SetString(args.Key, 10)
hx = common.Bytes2Hex(i.Bytes())
}
rpclogger.Debugf("GetStateAt(%s, %s)\n", args.Address, hx)
*reply = map[string]string{args.Key: value.Str()}
return nil
}
func (p *EthereumApi) GetTxCountAt(args *GetTxCountArgs, reply *interface{}) error {
err := args.requirements()
if err != nil {
return err
}
*reply = p.xethWithStateNum(args.BlockNumber).TxCountAt(args.Address)
return nil
}
func (p *EthereumApi) GetData(args *GetDataArgs, reply *interface{}) error {
if err := args.requirements(); err != nil {
return err
}
*reply = p.xethWithStateNum(args.BlockNumber).CodeAt(args.Address)
return nil
}
func (p *EthereumApi) GetCompilers(reply *interface{}) error {
c := []string{""}
*reply = c
return nil
}
func (p *EthereumApi) DbPut(args *DbArgs, reply *interface{}) error {
if err := args.requirements(); err != nil {
return err
}
p.db.Put([]byte(args.Database+args.Key), []byte(args.Value))
*reply = true
return nil
}
func (p *EthereumApi) DbGet(args *DbArgs, reply *interface{}) error {
if err := args.requirements(); err != nil {
return err
}
res, _ := p.db.Get([]byte(args.Database + args.Key))
*reply = string(res)
return nil
}
func (p *EthereumApi) NewWhisperIdentity(reply *interface{}) error {
*reply = p.xeth().Whisper().NewIdentity()
return nil
}
// func (p *EthereumApi) RemoveWhisperIdentity(args *WhisperIdentityArgs, reply *interface{}) error {
// *reply = p.xeth().Whisper().RemoveIdentity(args.Identity)
// return nil
// }
func (p *EthereumApi) NewWhisperFilter(args *WhisperFilterArgs, reply *interface{}) error {
var id int
opts := new(xeth.Options)
opts.From = args.From
opts.To = args.To
opts.Topics = args.Topics
opts.Fn = func(msg xeth.WhisperMessage) {
p.messagesMut.Lock()
defer p.messagesMut.Unlock()
p.messages[id].add(msg) // = append(p.messages[id], msg)
}
id = p.xeth().Whisper().Watch(opts)
p.messages[id] = &whisperFilter{timeout: time.Now()}
*reply = common.ToHex(big.NewInt(int64(id)).Bytes())
return nil
}
func (p *EthereumApi) UninstallWhisperFilter(id int, reply *interface{}) error {
delete(p.messages, id)
*reply = true
return nil
}
func (self *EthereumApi) MessagesChanged(id int, reply *interface{}) error {
self.messagesMut.Lock()
defer self.messagesMut.Unlock()
if self.messages[id] != nil {
*reply = self.messages[id].get()
}
return nil
}
func (p *EthereumApi) WhisperPost(args *WhisperMessageArgs, reply *interface{}) error {
err := p.xeth().Whisper().Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl)
if err != nil {
return err
}
*reply = true
return nil
}
func (p *EthereumApi) HasWhisperIdentity(args string, reply *interface{}) error {
*reply = p.xeth().Whisper().HasIdentity(args)
return nil
}
func (p *EthereumApi) WhisperMessages(id int, reply *interface{}) error {
*reply = p.xeth().Whisper().Messages(id)
return nil
}
func (p *EthereumApi) GetTransactionByHash(hash string, reply *interface{}) error {
tx := p.xeth().EthTransactionByHash(hash)
if tx != nil {
*reply = NewTransactionRes(tx)
}
return nil
}
func (p *EthereumApi) GetBlockByHash(blockhash string, includetx bool) (*BlockRes, error) {
block := p.xeth().EthBlockByHash(blockhash)
br := NewBlockRes(block)
br.fullTx = includetx
return br, nil
}
func (p *EthereumApi) GetBlockByNumber(blocknum int64, includetx bool) (*BlockRes, error) {
block := p.xeth().EthBlockByNumber(blocknum)
br := NewBlockRes(block)
br.fullTx = includetx
return br, nil
}
func (p *EthereumApi) GetBlockTransactionCountByHash(blockhash string) (int64, error) {
block := p.xeth().EthBlockByHash(blockhash)
br := NewBlockRes(block)
return int64(len(br.Transactions)), nil
}
func (p *EthereumApi) GetBlockTransactionCountByNumber(blocknum int64) (int64, error) {
block := p.xeth().EthBlockByNumber(blocknum)
br := NewBlockRes(block)
return int64(len(br.Transactions)), nil
}
func (p *EthereumApi) GetBlockUncleCountByHash(blockhash string) (int64, error) {
block := p.xeth().EthBlockByHash(blockhash)
br := NewBlockRes(block)
return int64(len(br.Uncles)), nil
}
func (p *EthereumApi) GetBlockUncleCountByNumber(blocknum int64) (int64, error) {
block := p.xeth().EthBlockByNumber(blocknum)
br := NewBlockRes(block)
return int64(len(br.Uncles)), nil
func (self *EthereumApi) xethAtStateNum(num int64) *xeth.XEth {
return self.xeth().AtStateNum(num)
}
func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error {
// Spec at https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC
rpclogger.Debugf("%s %s", req.Method, req.Params)
switch req.Method {
case "web3_sha3":
args := new(Sha3Args)
@ -501,7 +59,8 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
case "net_listening":
*reply = p.xeth().IsListening()
case "net_peerCount":
*reply = common.ToHex(big.NewInt(int64(p.xeth().PeerCount())).Bytes())
v := p.xeth().PeerCount()
*reply = common.ToHex(big.NewInt(int64(v)).Bytes())
case "eth_coinbase":
// TODO handling of empty coinbase due to lack of accounts
res := p.xeth().Coinbase()
@ -513,97 +72,131 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
case "eth_mining":
*reply = p.xeth().IsMining()
case "eth_gasPrice":
*reply = common.ToHex(defaultGasPrice.Bytes())
v := p.xeth().DefaultGas()
*reply = common.ToHex(v.Bytes())
case "eth_accounts":
*reply = p.xeth().Accounts()
case "eth_blockNumber":
*reply = common.ToHex(p.xeth().Backend().ChainManager().CurrentBlock().Number().Bytes())
v := p.xeth().Backend().ChainManager().CurrentBlock().Number()
*reply = common.ToHex(v.Bytes())
case "eth_getBalance":
args := new(GetBalanceArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.GetBalance(args, reply)
if err := args.requirements(); err != nil {
return err
}
v := p.xethAtStateNum(args.BlockNumber).State().SafeGet(args.Address).Balance()
*reply = common.ToHex(v.Bytes())
case "eth_getStorage", "eth_storageAt":
args := new(GetStorageArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.GetStorage(args, reply)
if err := args.requirements(); err != nil {
return err
}
*reply = p.xethAtStateNum(args.BlockNumber).State().SafeGet(args.Address).Storage()
case "eth_getStorageAt":
args := new(GetStorageAtArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.GetStorageAt(args, reply)
if err := args.requirements(); err != nil {
return err
}
state := p.xethAtStateNum(args.BlockNumber).State().SafeGet(args.Address)
value := state.StorageString(args.Key)
*reply = common.Bytes2Hex(value.Bytes())
case "eth_getTransactionCount":
args := new(GetTxCountArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.GetTxCountAt(args, reply)
err := args.requirements()
if err != nil {
return err
}
*reply = p.xethAtStateNum(args.BlockNumber).TxCountAt(args.Address)
case "eth_getBlockTransactionCountByHash":
args := new(GetBlockByHashArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := p.GetBlockTransactionCountByHash(args.BlockHash)
if err != nil {
return err
}
*reply = common.ToHex(big.NewInt(v).Bytes())
block := NewBlockRes(p.xeth().EthBlockByHash(args.BlockHash))
*reply = common.ToHex(big.NewInt(int64(len(block.Transactions))).Bytes())
case "eth_getBlockTransactionCountByNumber":
args := new(GetBlockByNumberArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := p.GetBlockTransactionCountByNumber(args.BlockNumber)
if err != nil {
return err
}
*reply = common.ToHex(big.NewInt(v).Bytes())
block := NewBlockRes(p.xeth().EthBlockByNumber(args.BlockNumber))
*reply = common.ToHex(big.NewInt(int64(len(block.Transactions))).Bytes())
case "eth_getUncleCountByBlockHash":
args := new(GetBlockByHashArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := p.GetBlockUncleCountByHash(args.BlockHash)
if err != nil {
return err
}
*reply = common.ToHex(big.NewInt(v).Bytes())
block := p.xeth().EthBlockByHash(args.BlockHash)
br := NewBlockRes(block)
*reply = common.ToHex(big.NewInt(int64(len(br.Uncles))).Bytes())
case "eth_getUncleCountByBlockNumber":
args := new(GetBlockByNumberArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := p.GetBlockUncleCountByNumber(args.BlockNumber)
if err != nil {
return err
}
*reply = common.ToHex(big.NewInt(v).Bytes())
block := p.xeth().EthBlockByNumber(args.BlockNumber)
br := NewBlockRes(block)
*reply = common.ToHex(big.NewInt(int64(len(br.Uncles))).Bytes())
case "eth_getData", "eth_getCode":
args := new(GetDataArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.GetData(args, reply)
if err := args.requirements(); err != nil {
return err
}
*reply = p.xethAtStateNum(args.BlockNumber).CodeAt(args.Address)
case "eth_sendTransaction", "eth_transact":
args := new(NewTxArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.Transact(args, reply)
if err := args.requirements(); err != nil {
return err
}
v, err := p.xeth().Transact(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
if err != nil {
return err
}
*reply = v
case "eth_call":
args := new(NewTxArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.Call(args, reply)
v, err := p.xethAtStateNum(args.BlockNumber).Call(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
if err != nil {
return err
}
*reply = v
case "eth_flush":
return NewNotImplementedError(req.Method)
case "eth_getBlockByHash":
@ -612,52 +205,55 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
return err
}
v, err := p.GetBlockByHash(args.BlockHash, args.Transactions)
if err != nil {
return err
}
*reply = v
block := p.xeth().EthBlockByHash(args.BlockHash)
br := NewBlockRes(block)
br.fullTx = args.IncludeTxs
*reply = br
case "eth_getBlockByNumber":
args := new(GetBlockByNumberArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := p.GetBlockByNumber(args.BlockNumber, args.Transactions)
if err != nil {
return err
}
*reply = v
block := p.xeth().EthBlockByNumber(args.BlockNumber)
br := NewBlockRes(block)
br.fullTx = args.IncludeTxs
*reply = br
case "eth_getTransactionByHash":
// HashIndexArgs used, but only the "Hash" part we need.
args := new(HashIndexArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
}
return p.GetTransactionByHash(args.Hash, reply)
tx := p.xeth().EthTransactionByHash(args.Hash)
if tx != nil {
*reply = NewTransactionRes(tx)
}
case "eth_getTransactionByBlockHashAndIndex":
args := new(HashIndexArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := p.GetBlockByHash(args.Hash, true)
if err != nil {
return err
}
if args.Index > int64(len(v.Transactions)) || args.Index < 0 {
block := p.xeth().EthBlockByHash(args.Hash)
br := NewBlockRes(block)
br.fullTx = true
if args.Index > int64(len(br.Transactions)) || args.Index < 0 {
return NewValidationError("Index", "does not exist")
}
*reply = v.Transactions[args.Index]
*reply = br.Transactions[args.Index]
case "eth_getTransactionByBlockNumberAndIndex":
args := new(BlockNumIndexArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
v, err := p.GetBlockByNumber(args.BlockNumber, true)
if err != nil {
return err
}
block := p.xeth().EthBlockByNumber(args.BlockNumber)
v := NewBlockRes(block)
v.fullTx = true
if args.Index > int64(len(v.Transactions)) || args.Index < 0 {
return NewValidationError("Index", "does not exist")
}
@ -668,18 +264,15 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
return err
}
v, err := p.GetBlockByHash(args.Hash, false)
if err != nil {
return err
}
if args.Index > int64(len(v.Uncles)) || args.Index < 0 {
br := NewBlockRes(p.xeth().EthBlockByHash(args.Hash))
if args.Index > int64(len(br.Uncles)) || args.Index < 0 {
return NewValidationError("Index", "does not exist")
}
uncle, err := p.GetBlockByHash(v.Uncles[args.Index].Hex(), false)
if err != nil {
return err
}
uhash := br.Uncles[args.Index].Hex()
uncle := NewBlockRes(p.xeth().EthBlockByHash(uhash))
*reply = uncle
case "eth_getUncleByBlockNumberAndIndex":
args := new(BlockNumIndexArgs)
@ -687,59 +280,68 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
return err
}
v, err := p.GetBlockByNumber(args.BlockNumber, true)
if err != nil {
return err
}
block := p.xeth().EthBlockByNumber(args.BlockNumber)
v := NewBlockRes(block)
v.fullTx = true
if args.Index > int64(len(v.Uncles)) || args.Index < 0 {
return NewValidationError("Index", "does not exist")
}
uncle, err := p.GetBlockByHash(v.Uncles[args.Index].Hex(), false)
if err != nil {
return err
}
uhash := v.Uncles[args.Index].Hex()
uncle := NewBlockRes(p.xeth().EthBlockByHash(uhash))
*reply = uncle
case "eth_getCompilers":
return p.GetCompilers(reply)
c := []string{""}
*reply = c
case "eth_compileSolidity", "eth_compileLLL", "eth_compileSerpent":
return NewNotImplementedError(req.Method)
case "eth_newFilter":
args := new(FilterOptions)
args := new(BlockFilterArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.NewFilter(args, reply)
opts := toFilterOptions(args)
id := p.xeth().RegisterFilter(opts)
*reply = common.ToHex(big.NewInt(int64(id)).Bytes())
case "eth_newBlockFilter":
args := new(FilterStringArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.NewFilterString(args, reply)
if err := args.requirements(); err != nil {
return err
}
id := p.xeth().NewFilterString(args.Word)
*reply = common.ToHex(big.NewInt(int64(id)).Bytes())
case "eth_uninstallFilter":
args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.UninstallFilter(args.Id, reply)
*reply = p.xeth().UninstallFilter(args.Id)
case "eth_getFilterChanges":
args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.FilterChanged(args.Id, reply)
*reply = NewLogsRes(p.xeth().FilterChanged(args.Id))
case "eth_getFilterLogs":
args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.Logs(args.Id, reply)
*reply = NewLogsRes(p.xeth().Logs(args.Id))
case "eth_getLogs":
args := new(FilterOptions)
args := new(BlockFilterArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.AllLogs(args, reply)
opts := toFilterOptions(args)
*reply = NewLogsRes(p.xeth().AllLogs(opts))
case "eth_getWork", "eth_submitWork":
return NewNotImplementedError(req.Method)
case "db_putString":
@ -747,13 +349,25 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.DbPut(args, reply)
if err := args.requirements(); err != nil {
return err
}
p.db.Put([]byte(args.Database+args.Key), []byte(args.Value))
*reply = true
case "db_getString":
args := new(DbArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.DbGet(args, reply)
if err := args.requirements(); err != nil {
return err
}
res, _ := p.db.Get([]byte(args.Database + args.Key))
*reply = string(res)
case "db_putHex", "db_getHex":
return NewNotImplementedError(req.Method)
case "shh_post":
@ -761,21 +375,27 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.WhisperPost(args, reply)
err := p.xeth().Whisper().Post(args.Payload, args.To, args.From, args.Topics, args.Priority, args.Ttl)
if err != nil {
return err
}
*reply = true
case "shh_newIdentity":
return p.NewWhisperIdentity(reply)
*reply = p.xeth().Whisper().NewIdentity()
// case "shh_removeIdentity":
// args := new(WhisperIdentityArgs)
// if err := json.Unmarshal(req.Params, &args); err != nil {
// return err
// }
// return p.RemoveWhisperIdentity(args, reply)
// *reply = p.xeth().Whisper().RemoveIdentity(args.Identity)
case "shh_hasIdentity":
args := new(WhisperIdentityArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.HasWhisperIdentity(args.Identity, reply)
*reply = p.xeth().Whisper().HasIdentity(args.Identity)
case "shh_newGroup", "shh_addToGroup":
return NewNotImplementedError(req.Method)
case "shh_newFilter":
@ -783,43 +403,49 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.NewWhisperFilter(args, reply)
opts := new(xeth.Options)
opts.From = args.From
opts.To = args.To
opts.Topics = args.Topics
id := p.xeth().NewWhisperFilter(opts)
*reply = common.ToHex(big.NewInt(int64(id)).Bytes())
case "shh_uninstallFilter":
args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.UninstallWhisperFilter(args.Id, reply)
*reply = p.xeth().UninstallWhisperFilter(args.Id)
case "shh_getFilterChanges":
args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.MessagesChanged(args.Id, reply)
*reply = p.xeth().MessagesChanged(args.Id)
case "shh_getMessages":
args := new(FilterIdArgs)
if err := json.Unmarshal(req.Params, &args); err != nil {
return err
}
return p.WhisperMessages(args.Id, reply)
*reply = p.xeth().Whisper().Messages(args.Id)
// case "eth_register":
// args, err := req.ToRegisterArgs()
// if err != nil {
// // Placeholder for actual type
// args := new(HashIndexArgs)
// if err := json.Unmarshal(req.Params, &args); err != nil {
// return err
// }
// return p.Register(args, reply)
// *reply = p.xeth().Register(args.Hash)
// case "eth_unregister":
// args, err := req.ToRegisterArgs()
// if err != nil {
// args := new(HashIndexArgs)
// if err := json.Unmarshal(req.Params, &args); err != nil {
// return err
// }
// return p.Unregister(args, reply)
// *reply = p.xeth().Unregister(args.Hash)
// case "eth_watchTx":
// args, err := req.ToWatchTxArgs()
// if err != nil {
// args := new(HashIndexArgs)
// if err := json.Unmarshal(req.Params, &args); err != nil {
// return err
// }
// return p.WatchTx(args, reply)
// *reply = p.xeth().PullWatchTx(args.Hash)
default:
return NewNotImplementedError(req.Method)
}
@ -828,14 +454,7 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
return nil
}
func (self *EthereumApi) xeth() *xeth.XEth {
self.xethMu.RLock()
defer self.xethMu.RUnlock()
return self.eth
}
func toFilterOptions(options *FilterOptions) core.FilterOptions {
func toFilterOptions(options *BlockFilterArgs) *core.FilterOptions {
var opts core.FilterOptions
// Convert optional address slice/string to byte slice
@ -867,5 +486,5 @@ func toFilterOptions(options *FilterOptions) core.FilterOptions {
}
opts.Topics = topics
return opts
return &opts
}

View File

@ -2,9 +2,9 @@ package rpc
import (
"encoding/json"
"sync"
// "sync"
"testing"
"time"
// "time"
)
func TestWeb3Sha3(t *testing.T) {
@ -24,33 +24,33 @@ func TestWeb3Sha3(t *testing.T) {
}
}
func TestFilterClose(t *testing.T) {
t.Skip()
api := &EthereumApi{
logs: make(map[int]*logFilter),
messages: make(map[int]*whisperFilter),
quit: make(chan struct{}),
}
// func TestFilterClose(t *testing.T) {
// t.Skip()
// api := &EthereumApi{
// logs: make(map[int]*logFilter),
// messages: make(map[int]*whisperFilter),
// quit: make(chan struct{}),
// }
filterTickerTime = 1
api.logs[0] = &logFilter{}
api.messages[0] = &whisperFilter{}
var wg sync.WaitGroup
wg.Add(1)
go api.start()
go func() {
select {
case <-time.After(500 * time.Millisecond):
api.stop()
wg.Done()
}
}()
wg.Wait()
if len(api.logs) != 0 {
t.Error("expected logs to be empty")
}
// filterTickerTime = 1
// api.logs[0] = &logFilter{}
// api.messages[0] = &whisperFilter{}
// var wg sync.WaitGroup
// wg.Add(1)
// go api.start()
// go func() {
// select {
// case <-time.After(500 * time.Millisecond):
// api.stop()
// wg.Done()
// }
// }()
// wg.Wait()
// if len(api.logs) != 0 {
// t.Error("expected logs to be empty")
// }
if len(api.messages) != 0 {
t.Error("expected messages to be empty")
}
}
// if len(api.messages) != 0 {
// t.Error("expected messages to be empty")
// }
// }

View File

@ -35,8 +35,8 @@ func blockAge(raw interface{}, number *int64) (err error) {
}
type GetBlockByHashArgs struct {
BlockHash string
Transactions bool
BlockHash string
IncludeTxs bool
}
func (args *GetBlockByHashArgs) UnmarshalJSON(b []byte) (err error) {
@ -57,15 +57,15 @@ func (args *GetBlockByHashArgs) UnmarshalJSON(b []byte) (err error) {
args.BlockHash = argstr
if len(obj) > 1 {
args.Transactions = obj[1].(bool)
args.IncludeTxs = obj[1].(bool)
}
return nil
}
type GetBlockByNumberArgs struct {
BlockNumber int64
Transactions bool
BlockNumber int64
IncludeTxs bool
}
func (args *GetBlockByNumberArgs) UnmarshalJSON(b []byte) (err error) {
@ -86,7 +86,7 @@ func (args *GetBlockByNumberArgs) UnmarshalJSON(b []byte) (err error) {
}
if len(obj) > 1 {
args.Transactions = obj[1].(bool)
args.IncludeTxs = obj[1].(bool)
}
return nil
@ -433,7 +433,7 @@ func (args *Sha3Args) UnmarshalJSON(b []byte) (err error) {
return nil
}
type FilterOptions struct {
type BlockFilterArgs struct {
Earliest int64
Latest int64
Address interface{}
@ -442,7 +442,7 @@ type FilterOptions struct {
Max int
}
func (args *FilterOptions) UnmarshalJSON(b []byte) (err error) {
func (args *BlockFilterArgs) UnmarshalJSON(b []byte) (err error) {
var obj []struct {
FromBlock interface{} `json:"fromBlock"`
ToBlock interface{} `json:"toBlock"`
@ -609,6 +609,16 @@ func (args *FilterStringArgs) UnmarshalJSON(b []byte) (err error) {
return nil
}
func (args *FilterStringArgs) requirements() error {
switch args.Word {
case "latest", "pending":
break
default:
return NewValidationError("Word", "Must be `latest` or `pending`")
}
return nil
}
type FilterIdArgs struct {
Id int
}

View File

@ -82,7 +82,7 @@ func TestGetBlockByHashArgs(t *testing.T) {
input := `["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", true]`
expected := new(GetBlockByHashArgs)
expected.BlockHash = "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
expected.Transactions = true
expected.IncludeTxs = true
args := new(GetBlockByHashArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
@ -93,8 +93,8 @@ func TestGetBlockByHashArgs(t *testing.T) {
t.Errorf("BlockHash should be %v but is %v", expected.BlockHash, args.BlockHash)
}
if args.Transactions != expected.Transactions {
t.Errorf("Transactions should be %v but is %v", expected.Transactions, args.Transactions)
if args.IncludeTxs != expected.IncludeTxs {
t.Errorf("IncludeTxs should be %v but is %v", expected.IncludeTxs, args.IncludeTxs)
}
}
@ -112,7 +112,7 @@ func TestGetBlockByNumberArgs(t *testing.T) {
input := `["0x1b4", false]`
expected := new(GetBlockByNumberArgs)
expected.BlockNumber = 436
expected.Transactions = false
expected.IncludeTxs = false
args := new(GetBlockByNumberArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
@ -123,8 +123,8 @@ func TestGetBlockByNumberArgs(t *testing.T) {
t.Errorf("BlockHash should be %v but is %v", expected.BlockNumber, args.BlockNumber)
}
if args.Transactions != expected.Transactions {
t.Errorf("Transactions should be %v but is %v", expected.Transactions, args.Transactions)
if args.IncludeTxs != expected.IncludeTxs {
t.Errorf("IncludeTxs should be %v but is %v", expected.IncludeTxs, args.IncludeTxs)
}
}
@ -388,7 +388,7 @@ func TestGetDataEmptyArgs(t *testing.T) {
}
}
func TestFilterOptions(t *testing.T) {
func TestBlockFilterArgs(t *testing.T) {
input := `[{
"fromBlock": "0x1",
"toBlock": "0x2",
@ -396,7 +396,7 @@ func TestFilterOptions(t *testing.T) {
"offset": "0x0",
"address": "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8",
"topics": ["0x12341234"]}]`
expected := new(FilterOptions)
expected := new(BlockFilterArgs)
expected.Earliest = 1
expected.Latest = 2
expected.Max = 3
@ -404,7 +404,7 @@ func TestFilterOptions(t *testing.T) {
expected.Address = "0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8"
// expected.Topics = []string{"0x12341234"}
args := new(FilterOptions)
args := new(BlockFilterArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
t.Error(err)
}
@ -434,16 +434,16 @@ func TestFilterOptions(t *testing.T) {
// }
}
func TestFilterOptionsWords(t *testing.T) {
func TestBlockFilterArgsWords(t *testing.T) {
input := `[{
"fromBlock": "latest",
"toBlock": "pending"
}]`
expected := new(FilterOptions)
expected := new(BlockFilterArgs)
expected.Earliest = 0
expected.Latest = -1
args := new(FilterOptions)
args := new(BlockFilterArgs)
if err := json.Unmarshal([]byte(input), &args); err != nil {
t.Error(err)
}
@ -457,13 +457,13 @@ func TestFilterOptionsWords(t *testing.T) {
}
}
func TestFilterOptionsNums(t *testing.T) {
func TestBlockFilterArgsNums(t *testing.T) {
input := `[{
"fromBlock": 2,
"toBlock": 3
}]`
args := new(FilterOptions)
args := new(BlockFilterArgs)
err := json.Unmarshal([]byte(input), &args)
switch err.(type) {
case *DecodeParamError:
@ -474,10 +474,10 @@ func TestFilterOptionsNums(t *testing.T) {
}
func TestFilterOptionsEmptyArgs(t *testing.T) {
func TestBlockFilterArgsEmptyArgs(t *testing.T) {
input := `[]`
args := new(FilterOptions)
args := new(BlockFilterArgs)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")

View File

@ -10,7 +10,7 @@ import (
"github.com/ethereum/go-ethereum/xeth"
)
var rpchttplogger = logger.NewLogger("RPC-HTTP")
var rpclogger = logger.NewLogger("RPC")
const (
jsonrpcver = "2.0"
@ -28,7 +28,7 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler {
// Limit request size to resist DoS
if req.ContentLength > maxSizeReqLength {
jsonerr := &RpcErrorObject{-32700, "Request too large"}
Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
return
}
@ -37,14 +37,14 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
jsonerr := &RpcErrorObject{-32700, "Could not read request body"}
Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
}
// Try to parse the request as a single
var reqSingle RpcRequest
if err := json.Unmarshal(body, &reqSingle); err == nil {
response := RpcResponse(api, &reqSingle)
Send(w, &response)
send(w, &response)
return
}
@ -57,13 +57,13 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler {
response := RpcResponse(api, &request)
resBatch[i] = response
}
Send(w, resBatch)
send(w, resBatch)
return
}
// Not a batch or single request, error
jsonerr := &RpcErrorObject{-32600, "Could not decode request"}
Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
})
}
@ -84,11 +84,11 @@ func RpcResponse(api *EthereumApi, request *RpcRequest) *interface{} {
response = &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: request.Id, Error: jsonerr}
}
rpchttplogger.DebugDetailf("Generated response: %T %s", response, response)
rpclogger.DebugDetailf("Generated response: %T %s", response, response)
return &response
}
func Send(writer io.Writer, v interface{}) (n int, err error) {
func send(writer io.Writer, v interface{}) (n int, err error) {
var payload []byte
payload, err = json.MarshalIndent(v, "", "\t")
if err != nil {

41
rpc/messages_test.go Normal file
View File

@ -0,0 +1,41 @@
package rpc
import (
"testing"
)
func TestInsufficientParamsError(t *testing.T) {
err := NewInsufficientParamsError(0, 1)
expected := "insufficient params, want 1 have 0"
if err.Error() != expected {
t.Error(err.Error())
}
}
func TestNotImplementedError(t *testing.T) {
err := NewNotImplementedError("foo")
expected := "foo method not implemented"
if err.Error() != expected {
t.Error(err.Error())
}
}
func TestDecodeParamError(t *testing.T) {
err := NewDecodeParamError("foo")
expected := "could not decode, foo"
if err.Error() != expected {
t.Error(err.Error())
}
}
func TestValidationError(t *testing.T) {
err := NewValidationError("foo", "should be `bar`")
expected := "foo not valid, should be `bar`"
if err.Error() != expected {
t.Error(err.Error())
}
}

View File

@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/state"
)
type BlockRes struct {
@ -215,3 +216,28 @@ type FilterWhisperRes struct {
Payload string `json:"payload"`
WorkProved string `json:"workProved"`
}
type LogRes struct {
Address string `json:"address"`
Topic []string `json:"topic"`
Data string `json:"data"`
Number uint64 `json:"number"`
}
func NewLogsRes(logs state.Logs) (ls []LogRes) {
ls = make([]LogRes, len(logs))
for i, log := range logs {
var l LogRes
l.Topic = make([]string, len(log.Topics()))
l.Address = log.Address().Hex()
l.Data = common.ToHex(log.Data())
l.Number = log.Number()
for j, topic := range log.Topics() {
l.Topic[j] = topic.Hex()
}
ls[i] = l
}
return
}

View File

@ -1,86 +0,0 @@
/*
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 rpc
import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/xeth"
)
var rpclogger = logger.NewLogger("RPC")
type Log struct {
Address string `json:"address"`
Topic []string `json:"topic"`
Data string `json:"data"`
Number uint64 `json:"number"`
}
func toLogs(logs state.Logs) (ls []Log) {
ls = make([]Log, len(logs))
for i, log := range logs {
var l Log
l.Topic = make([]string, len(log.Topics()))
l.Address = log.Address().Hex()
l.Data = common.ToHex(log.Data())
l.Number = log.Number()
for j, topic := range log.Topics() {
l.Topic[j] = topic.Hex()
}
ls[i] = l
}
return
}
type whisperFilter struct {
messages []xeth.WhisperMessage
timeout time.Time
id int
}
func (w *whisperFilter) add(msgs ...xeth.WhisperMessage) {
w.messages = append(w.messages, msgs...)
}
func (w *whisperFilter) get() []xeth.WhisperMessage {
w.timeout = time.Now()
tmp := w.messages
w.messages = nil
return tmp
}
type logFilter struct {
logs state.Logs
timeout time.Time
id int
}
func (l *logFilter) add(logs ...state.Log) {
l.logs = append(l.logs, logs...)
}
func (l *logFilter) get() state.Logs {
l.timeout = time.Now()
tmp := l.logs
l.logs = nil
return tmp
}

View File

@ -6,6 +6,8 @@ import (
"encoding/json"
"fmt"
"math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
@ -13,13 +15,19 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/event/filter"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/whisper"
)
var pipelogger = logger.NewLogger("XETH")
var (
pipelogger = logger.NewLogger("XETH")
filterTickerTime = 5 * time.Minute
defaultGasPrice = big.NewInt(10000000000000) //150000000000
defaultGas = big.NewInt(90000) //500000
)
// to resolve the import cycle
type Backend interface {
@ -62,6 +70,13 @@ type Frontend interface {
ConfirmTransaction(tx *types.Transaction) bool
}
// dummyFrontend is a non-interactive frontend that allows all
// transactions but cannot not unlock any keys.
type dummyFrontend struct{}
func (dummyFrontend) UnlockAccount([]byte) bool { return false }
func (dummyFrontend) ConfirmTransaction(*types.Transaction) bool { return true }
type XEth struct {
eth Backend
blockProcessor *core.BlockProcessor
@ -71,15 +86,20 @@ type XEth struct {
whisper *Whisper
frontend Frontend
quit chan struct{}
filterManager *filter.FilterManager
logMut sync.RWMutex
logs map[int]*logFilter
messagesMut sync.RWMutex
messages map[int]*whisperFilter
// regmut sync.Mutex
// register map[string][]*interface{} // TODO improve return type
}
// dummyFrontend is a non-interactive frontend that allows all
// transactions but cannot not unlock any keys.
type dummyFrontend struct{}
func (dummyFrontend) UnlockAccount([]byte) bool { return false }
func (dummyFrontend) ConfirmTransaction(*types.Transaction) bool { return true }
// New creates an XEth that uses the given frontend.
// If a nil Frontend is provided, a default frontend which
// confirms all transactions will be used.
@ -90,15 +110,76 @@ func New(eth Backend, frontend Frontend) *XEth {
chainManager: eth.ChainManager(),
accountManager: eth.AccountManager(),
whisper: NewWhisper(eth.Whisper()),
quit: make(chan struct{}),
filterManager: filter.NewFilterManager(eth.EventMux()),
frontend: frontend,
logs: make(map[int]*logFilter),
messages: make(map[int]*whisperFilter),
}
if frontend == nil {
xeth.frontend = dummyFrontend{}
}
xeth.state = NewState(xeth, xeth.chainManager.TransState())
go xeth.start()
go xeth.filterManager.Start()
return xeth
}
func (self *XEth) start() {
timer := time.NewTicker(2 * time.Second)
done:
for {
select {
case <-timer.C:
self.logMut.Lock()
self.messagesMut.Lock()
for id, filter := range self.logs {
if time.Since(filter.timeout) > filterTickerTime {
self.filterManager.UninstallFilter(id)
delete(self.logs, id)
}
}
for id, filter := range self.messages {
if time.Since(filter.timeout) > filterTickerTime {
self.Whisper().Unwatch(id)
delete(self.messages, id)
}
}
self.messagesMut.Unlock()
self.logMut.Unlock()
case <-self.quit:
break done
}
}
}
func (self *XEth) stop() {
close(self.quit)
}
func (self *XEth) DefaultGas() *big.Int { return defaultGas }
func (self *XEth) DefaultGasPrice() *big.Int { return defaultGasPrice }
func (self *XEth) AtStateNum(num int64) *XEth {
chain := self.Backend().ChainManager()
var block *types.Block
if num < 0 {
num = chain.CurrentBlock().Number().Int64() + num + 1
}
block = chain.GetBlockByNumber(uint64(num))
var st *state.StateDB
if block != nil {
st = state.New(block.Root(), self.Backend().StateDb())
} else {
st = chain.State()
}
return self.WithState(st)
}
func (self *XEth) Backend() Backend { return self.eth }
func (self *XEth) WithState(statedb *state.StateDB) *XEth {
xeth := &XEth{
@ -241,6 +322,157 @@ func (self *XEth) SecretToAddress(key string) string {
return common.ToHex(pair.Address())
}
func (self *XEth) RegisterFilter(args *core.FilterOptions) int {
var id int
filter := core.NewFilter(self.Backend())
filter.SetOptions(args)
filter.LogsCallback = func(logs state.Logs) {
self.logMut.Lock()
defer self.logMut.Unlock()
self.logs[id].add(logs...)
}
id = self.filterManager.InstallFilter(filter)
self.logs[id] = &logFilter{timeout: time.Now()}
return id
}
func (self *XEth) UninstallFilter(id int) bool {
if _, ok := self.logs[id]; ok {
delete(self.logs, id)
self.filterManager.UninstallFilter(id)
return true
}
return false
}
func (self *XEth) NewFilterString(word string) int {
var id int
filter := core.NewFilter(self.Backend())
switch word {
case "pending":
filter.PendingCallback = func(tx *types.Transaction) {
self.logMut.Lock()
defer self.logMut.Unlock()
self.logs[id].add(&state.StateLog{})
}
case "latest":
filter.BlockCallback = func(block *types.Block, logs state.Logs) {
self.logMut.Lock()
defer self.logMut.Unlock()
for _, log := range logs {
self.logs[id].add(log)
}
self.logs[id].add(&state.StateLog{})
}
}
id = self.filterManager.InstallFilter(filter)
self.logs[id] = &logFilter{timeout: time.Now()}
return id
}
func (self *XEth) FilterChanged(id int) state.Logs {
self.logMut.Lock()
defer self.logMut.Unlock()
if self.logs[id] != nil {
return self.logs[id].get()
}
return nil
}
func (self *XEth) Logs(id int) state.Logs {
self.logMut.Lock()
defer self.logMut.Unlock()
filter := self.filterManager.GetFilter(id)
if filter != nil {
return filter.Find()
}
return nil
}
func (self *XEth) AllLogs(args *core.FilterOptions) state.Logs {
filter := core.NewFilter(self.Backend())
filter.SetOptions(args)
return filter.Find()
}
func (p *XEth) NewWhisperFilter(opts *Options) int {
var id int
opts.Fn = func(msg WhisperMessage) {
p.messagesMut.Lock()
defer p.messagesMut.Unlock()
p.messages[id].add(msg) // = append(p.messages[id], msg)
}
id = p.Whisper().Watch(opts)
p.messages[id] = &whisperFilter{timeout: time.Now()}
return id
}
func (p *XEth) UninstallWhisperFilter(id int) bool {
if _, ok := p.messages[id]; ok {
delete(p.messages, id)
return true
}
return false
}
func (self *XEth) MessagesChanged(id int) []WhisperMessage {
self.messagesMut.Lock()
defer self.messagesMut.Unlock()
if self.messages[id] != nil {
return self.messages[id].get()
}
return nil
}
// func (self *XEth) Register(args string) bool {
// self.regmut.Lock()
// defer self.regmut.Unlock()
// if _, ok := self.register[args]; ok {
// self.register[args] = nil // register with empty
// }
// return true
// }
// func (self *XEth) Unregister(args string) bool {
// self.regmut.Lock()
// defer self.regmut.Unlock()
// if _, ok := self.register[args]; ok {
// delete(self.register, args)
// return true
// }
// return false
// }
// // TODO improve return type
// func (self *XEth) PullWatchTx(args string) []*interface{} {
// self.regmut.Lock()
// defer self.regmut.Unlock()
// txs := self.register[args]
// self.register[args] = nil
// return txs
// }
type KeyVal struct {
Key string `json:"key"`
Value string `json:"value"`
@ -298,11 +530,6 @@ func (self *XEth) PushTx(encodedTx string) (string, error) {
return tx.Hash().Hex(), nil
}
var (
defaultGasPrice = big.NewInt(10000000000000)
defaultGas = big.NewInt(90000)
)
func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) {
statedb := self.State().State() //self.chainManager.TransState()
msg := callmsg{
@ -333,12 +560,44 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt
from = common.HexToAddress(fromStr)
to = common.HexToAddress(toStr)
value = common.NewValue(valueStr)
gas = common.NewValue(gasStr)
price = common.NewValue(gasPriceStr)
gas = common.Big(gasStr)
price = common.Big(gasPriceStr)
data []byte
contractCreation bool
)
// TODO if no_private_key then
//if _, exists := p.register[args.From]; exists {
// p.register[args.From] = append(p.register[args.From], args)
//} else {
/*
account := accounts.Get(common.FromHex(args.From))
if account != nil {
if account.Unlocked() {
if !unlockAccount(account) {
return
}
}
result, _ := account.Transact(common.FromHex(args.To), common.FromHex(args.Value), common.FromHex(args.Gas), common.FromHex(args.GasPrice), common.FromHex(args.Data))
if len(result) > 0 {
*reply = common.ToHex(result)
}
} else if _, exists := p.register[args.From]; exists {
p.register[ags.From] = append(p.register[args.From], args)
}
*/
// TODO: align default values to have the same type, e.g. not depend on
// common.Value conversions later on
if gas.Cmp(big.NewInt(0)) == 0 {
gas = defaultGas
}
if price.Cmp(big.NewInt(0)) == 0 {
price = defaultGasPrice
}
data = common.FromHex(codeStr)
if len(toStr) == 0 {
contractCreation = true
@ -346,9 +605,9 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt
var tx *types.Transaction
if contractCreation {
tx = types.NewContractCreationTx(value.BigInt(), gas.BigInt(), price.BigInt(), data)
tx = types.NewContractCreationTx(value.BigInt(), gas, price, data)
} else {
tx = types.NewTransactionMessage(to, value.BigInt(), gas.BigInt(), price.BigInt(), data)
tx = types.NewTransactionMessage(to, value.BigInt(), gas, price, data)
}
state := self.chainManager.TxState()
@ -407,3 +666,36 @@ func (m callmsg) GasPrice() *big.Int { return m.gasPrice }
func (m callmsg) Gas() *big.Int { return m.gas }
func (m callmsg) Value() *big.Int { return m.value }
func (m callmsg) Data() []byte { return m.data }
type whisperFilter struct {
messages []WhisperMessage
timeout time.Time
id int
}
func (w *whisperFilter) add(msgs ...WhisperMessage) {
w.messages = append(w.messages, msgs...)
}
func (w *whisperFilter) get() []WhisperMessage {
w.timeout = time.Now()
tmp := w.messages
w.messages = nil
return tmp
}
type logFilter struct {
logs state.Logs
timeout time.Time
id int
}
func (l *logFilter) add(logs ...state.Log) {
l.logs = append(l.logs, logs...)
}
func (l *logFilter) get() state.Logs {
l.timeout = time.Now()
tmp := l.logs
l.logs = nil
return tmp
}