Merge branch 'rpcxeth' into rpcfrontier

Conflicts:
	rpc/api.go
This commit is contained in:
Taylor Gerring 2015-03-20 15:25:43 +01:00
commit 28e1971272
7 changed files with 554 additions and 667 deletions

@ -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 // 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. // 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.earliest = options.Earliest
self.latest = options.Latest self.latest = options.Latest
self.skip = options.Skip self.skip = options.Skip

@ -48,7 +48,9 @@ func (self *FilterManager) InstallFilter(filter *core.Filter) (id int) {
func (self *FilterManager) UninstallFilter(id int) { func (self *FilterManager) UninstallFilter(id int) {
self.filterMu.Lock() self.filterMu.Lock()
defer self.filterMu.Unlock() 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. // GetFilter retrieves a filter installed using InstallFilter.

File diff suppressed because it is too large Load Diff

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

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

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

@ -6,6 +6,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -13,13 +15,19 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/event/filter"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/whisper" "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 // to resolve the import cycle
type Backend interface { type Backend interface {
@ -62,6 +70,13 @@ type Frontend interface {
ConfirmTransaction(tx *types.Transaction) bool 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 { type XEth struct {
eth Backend eth Backend
blockProcessor *core.BlockProcessor blockProcessor *core.BlockProcessor
@ -71,15 +86,20 @@ type XEth struct {
whisper *Whisper whisper *Whisper
frontend Frontend 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. // New creates an XEth that uses the given frontend.
// If a nil Frontend is provided, a default frontend which // If a nil Frontend is provided, a default frontend which
// confirms all transactions will be used. // confirms all transactions will be used.
@ -90,15 +110,76 @@ func New(eth Backend, frontend Frontend) *XEth {
chainManager: eth.ChainManager(), chainManager: eth.ChainManager(),
accountManager: eth.AccountManager(), accountManager: eth.AccountManager(),
whisper: NewWhisper(eth.Whisper()), whisper: NewWhisper(eth.Whisper()),
quit: make(chan struct{}),
filterManager: filter.NewFilterManager(eth.EventMux()),
frontend: frontend, frontend: frontend,
logs: make(map[int]*logFilter),
messages: make(map[int]*whisperFilter),
} }
if frontend == nil { if frontend == nil {
xeth.frontend = dummyFrontend{} xeth.frontend = dummyFrontend{}
} }
xeth.state = NewState(xeth, xeth.chainManager.TransState()) xeth.state = NewState(xeth, xeth.chainManager.TransState())
go xeth.start()
go xeth.filterManager.Start()
return xeth 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) Backend() Backend { return self.eth }
func (self *XEth) WithState(statedb *state.StateDB) *XEth { func (self *XEth) WithState(statedb *state.StateDB) *XEth {
xeth := &XEth{ xeth := &XEth{
@ -241,6 +322,157 @@ func (self *XEth) SecretToAddress(key string) string {
return common.ToHex(pair.Address()) 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 { type KeyVal struct {
Key string `json:"key"` Key string `json:"key"`
Value string `json:"value"` Value string `json:"value"`
@ -298,11 +530,6 @@ func (self *XEth) PushTx(encodedTx string) (string, error) {
return common.ToHex(tx.Hash()), nil return common.ToHex(tx.Hash()), nil
} }
var (
defaultGasPrice = big.NewInt(10000000000000)
defaultGas = big.NewInt(90000)
)
func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) {
statedb := self.State().State() //self.chainManager.TransState() statedb := self.State().State() //self.chainManager.TransState()
msg := callmsg{ msg := callmsg{
@ -333,12 +560,44 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt
from []byte from []byte
to []byte to []byte
value = common.NewValue(valueStr) value = common.NewValue(valueStr)
gas = common.NewValue(gasStr) gas = common.Big(gasStr)
price = common.NewValue(gasPriceStr) price = common.Big(gasPriceStr)
data []byte data []byte
contractCreation bool 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
}
from = common.FromHex(fromStr) from = common.FromHex(fromStr)
data = common.FromHex(codeStr) data = common.FromHex(codeStr)
to = common.FromHex(toStr) to = common.FromHex(toStr)
@ -348,9 +607,9 @@ func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeSt
var tx *types.Transaction var tx *types.Transaction
if contractCreation { if contractCreation {
tx = types.NewContractCreationTx(value.BigInt(), gas.BigInt(), price.BigInt(), data) tx = types.NewContractCreationTx(value.BigInt(), gas, price, data)
} else { } 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() state := self.chainManager.TxState()
@ -411,3 +670,36 @@ func (m callmsg) GasPrice() *big.Int { return m.gasPrice }
func (m callmsg) Gas() *big.Int { return m.gas } func (m callmsg) Gas() *big.Int { return m.gas }
func (m callmsg) Value() *big.Int { return m.value } func (m callmsg) Value() *big.Int { return m.value }
func (m callmsg) Data() []byte { return m.data } 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
}