diff options
32 files changed, 411 insertions, 324 deletions
diff --git a/cmd/ethtest/main.go b/cmd/ethtest/main.go index 952ba1bd6..3c5b2cedf 100644 --- a/cmd/ethtest/main.go +++ b/cmd/ethtest/main.go @@ -176,23 +176,23 @@ func RunVmTest(r io.Reader) (failed int) { failed = 1 } else { for i, log := range test.Logs { - if common.HexToAddress(log.AddressF) != logs[i].Address() { - helper.Log.Infof("'%s' log address failed. Expected %v got %x", name, log.AddressF, logs[i].Address()) + if common.HexToAddress(log.AddressF) != logs[i].Address { + helper.Log.Infof("'%s' log address failed. Expected %v got %x", name, log.AddressF, logs[i].Address) failed = 1 } - if !bytes.Equal(logs[i].Data(), helper.FromHex(log.DataF)) { - helper.Log.Infof("'%s' log data failed. Expected %v got %x", name, log.DataF, logs[i].Data()) + if !bytes.Equal(logs[i].Data, helper.FromHex(log.DataF)) { + helper.Log.Infof("'%s' log data failed. Expected %v got %x", name, log.DataF, logs[i].Data) failed = 1 } - if len(log.TopicsF) != len(logs[i].Topics()) { - helper.Log.Infof("'%s' log topics length failed. Expected %d got %d", name, len(log.TopicsF), logs[i].Topics()) + if len(log.TopicsF) != len(logs[i].Topics) { + helper.Log.Infof("'%s' log topics length failed. Expected %d got %d", name, len(log.TopicsF), logs[i].Topics) failed = 1 } else { for j, topic := range log.TopicsF { - if common.HexToHash(topic) != logs[i].Topics()[j] { - helper.Log.Infof("'%s' log topic[%d] failed. Expected %v got %x", name, j, topic, logs[i].Topics()[j]) + if common.HexToHash(topic) != logs[i].Topics[j] { + helper.Log.Infof("'%s' log topic[%d] failed. Expected %v got %x", name, j, topic, logs[i].Topics[j]) failed = 1 } } diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 5eb753fa8..561f1a943 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -133,7 +133,7 @@ func (self *VMEnv) GetHash(n uint64) common.Hash { } return common.Hash{} } -func (self *VMEnv) AddLog(log state.Log) { +func (self *VMEnv) AddLog(log *state.Log) { self.state.AddLog(log) } func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) error { diff --git a/cmd/mist/assets/examples/coin.html b/cmd/mist/assets/examples/coin.html index a734e144f..257a19977 100644 --- a/cmd/mist/assets/examples/coin.html +++ b/cmd/mist/assets/examples/coin.html @@ -72,16 +72,19 @@ // deploy if not exist if(address === null) { var code = "0x60056013565b61014f8061003a6000396000f35b620f42406000600033600160a060020a0316815260200190815260200160002081905550560060e060020a600035048063d0679d3414610020578063e3d670d71461003457005b61002e600435602435610049565b60006000f35b61003f600435610129565b8060005260206000f35b806000600033600160a060020a03168152602001908152602001600020541061007157610076565b610125565b806000600033600160a060020a03168152602001908152602001600020908154039081905550806000600084600160a060020a031681526020019081526020016000209081540190819055508033600160a060020a03167fb52dda022b6c1a1f40905a85f257f689aa5d69d850e49cf939d688fbe5af594660006000a38082600160a060020a03167fb52dda022b6c1a1f40905a85f257f689aa5d69d850e49cf939d688fbe5af594660006000a35b5050565b60006000600083600160a060020a0316815260200190815260200160002054905091905056"; - address = web3.eth.sendTransaction({from: eth.coinbase, data: code, gas: "1000000"}); + address = web3.eth.sendTransaction({from: eth.accounts[0], data: code, gas: "1000000"}); localStorage.setItem("address", address); } document.querySelector("#contract_addr").innerHTML = address; var Contract = web3.eth.contract(desc); contract = new Contract(address); - contract.Changed({from: eth.accounts[0]}).watch(function() { + var filter = contract.Changed({from: eth.accounts[0]}) + filter.watch(function(logs) { + console.log(logs); refresh(); }); +window.filter = filter; function refresh() { document.querySelector("#balance").innerHTML = contract.call({from:eth.coinbase}).balance(eth.coinbase); diff --git a/cmd/utils/customflags.go b/cmd/utils/customflags.go new file mode 100644 index 000000000..a623ae19c --- /dev/null +++ b/cmd/utils/customflags.go @@ -0,0 +1,133 @@ +package utils + +import ( + "flag" + "fmt" + "os" + "os/user" + "path/filepath" + "strings" + + "github.com/codegangsta/cli" +) + +// Custom type which is registered in the flags library which cli uses for +// argument parsing. This allows us to expand Value to an absolute path when +// the argument is parsed +type DirectoryString struct { + Value string +} + +func (self DirectoryString) String() string { + return self.Value +} + +func (self DirectoryString) Set(value string) error { + self.Value = expandPath(value) + return nil +} + +// Custom cli.Flag type which expand the received string to an absolute path. +// e.g. ~/.ethereum -> /home/username/.ethereum +type DirectoryFlag struct { + cli.GenericFlag + Name string + Value DirectoryString + Usage string + EnvVar string +} + +func (self DirectoryFlag) String() string { + var fmtString string + fmtString = "%s %v\t%v" + + if len(self.Value.Value) > 0 { + fmtString = "%s \"%v\"\t%v" + } else { + fmtString = "%s %v\t%v" + } + + return withEnvHint(self.EnvVar, fmt.Sprintf(fmtString, prefixedNames(self.Name), self.Value.Value, self.Usage)) +} + +func eachName(longName string, fn func(string)) { + parts := strings.Split(longName, ",") + for _, name := range parts { + name = strings.Trim(name, " ") + fn(name) + } +} + +// called by cli library, grabs variable from environment (if in env) +// and adds variable to flag set for parsing. +func (self DirectoryFlag) Apply(set *flag.FlagSet) { + if self.EnvVar != "" { + for _, envVar := range strings.Split(self.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + self.Value.Value = envVal + break + } + } + } + + eachName(self.Name, func(name string) { + set.Var(self.Value, self.Name, "a: "+self.Usage) + }) + +} + +func prefixFor(name string) (prefix string) { + if len(name) == 1 { + prefix = "-" + } else { + prefix = "--" + } + + return +} + +func prefixedNames(fullName string) (prefixed string) { + parts := strings.Split(fullName, ",") + for i, name := range parts { + name = strings.Trim(name, " ") + prefixed += prefixFor(name) + name + if i < len(parts)-1 { + prefixed += ", " + } + } + return +} + +func withEnvHint(envVar, str string) string { + envText := "" + if envVar != "" { + envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $")) + } + return str + envText +} + +func (self DirectoryFlag) getName() string { + return self.Name +} + +func (self *DirectoryFlag) Set(value string) { + self.Value.Value = value +} + +// Expands a file path +// 1. replace tilde with users home dir +// 2. expands embedded environment variables +// 3. cleans the path, e.g. /a/b/../c -> /a/c +// Note, it has limitations, e.g. ~someuser/tmp will not be expanded +func expandPath(p string) string { + if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { + if user, err := user.Current(); err == nil { + if err == nil { + p = strings.Replace(p, "~", user.HomeDir, 1) + } + } + } + + return filepath.Clean(os.ExpandEnv(p)) +} diff --git a/cmd/utils/customflags_test.go b/cmd/utils/customflags_test.go new file mode 100644 index 000000000..11deb38ef --- /dev/null +++ b/cmd/utils/customflags_test.go @@ -0,0 +1,28 @@ +package utils + +import ( + "os" + "os/user" + "testing" +) + +func TestPathExpansion(t *testing.T) { + + user, _ := user.Current() + + tests := map[string]string{ + "/home/someuser/tmp": "/home/someuser/tmp", + "~/tmp": user.HomeDir + "/tmp", + "$DDDXXX/a/b": "/tmp/a/b", + "/a/b/": "/a/b", + } + + os.Setenv("DDDXXX", "/tmp") + + for test, expected := range tests { + got := expandPath(test) + if got != expected { + t.Errorf("test %s, got %s, expected %s\n", test, got, expected) + } + } +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 51844a68e..3ad06653e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -68,10 +68,10 @@ func NewApp(version, usage string) *cli.App { var ( // General settings - DataDirFlag = cli.StringFlag{ + DataDirFlag = DirectoryFlag{ Name: "datadir", Usage: "Data directory to be used", - Value: common.DefaultDataDir(), + Value: DirectoryString{common.DefaultDataDir()}, } ProtocolVersionFlag = cli.IntFlag{ Name: "protocolversion", @@ -231,7 +231,8 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { // Set verbosity on glog glog.SetV(ctx.GlobalInt(LogLevelFlag.Name)) // Set the log type - glog.SetToStderr(ctx.GlobalBool(LogToStdErrFlag.Name)) + //glog.SetToStderr(ctx.GlobalBool(LogToStdErrFlag.Name)) + glog.SetToStderr(true) // Set the log dir glog.SetLogDir(ctx.GlobalString(LogFileFlag.Name)) diff --git a/core/block_processor.go b/core/block_processor.go index 39134c63e..7aded346a 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -73,7 +73,7 @@ func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, statedb *state.StateDB, block *types.Block, tx *types.Transaction, usedGas *big.Int, transientProcess bool) (*types.Receipt, *big.Int, error) { // If we are mining this block and validating we want to set the logs back to 0 - statedb.EmptyLogs() + //statedb.EmptyLogs() cb := statedb.GetStateObject(coinbase.Address()) _, gas, err := ApplyMessage(NewEnv(statedb, self.bc, tx, block), tx, cb) @@ -89,7 +89,9 @@ func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, stated cumulative := new(big.Int).Set(usedGas.Add(usedGas, gas)) receipt := types.NewReceipt(statedb.Root().Bytes(), cumulative) - receipt.SetLogs(statedb.Logs()) + + logs := statedb.GetLogs(tx.Hash()) + receipt.SetLogs(logs) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) glog.V(logger.Debug).Infoln(receipt) @@ -97,7 +99,6 @@ func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, stated // Notify all subscribers if !transientProcess { go self.eventMux.Post(TxPostEvent{tx}) - logs := statedb.Logs() go self.eventMux.Post(logs) } @@ -115,7 +116,9 @@ func (self *BlockProcessor) ApplyTransactions(coinbase *state.StateObject, state cumulativeSum = new(big.Int) ) - for _, tx := range txs { + for i, tx := range txs { + statedb.StartRecord(tx.Hash(), block.Hash(), i) + receipt, txGas, err := self.ApplyTransaction(coinbase, statedb, block, tx, totalUsedGas, transientProcess) if err != nil && (IsNonceErr(err) || state.IsGasLimitErr(err) || IsInvalidTxErr(err)) { return nil, err diff --git a/core/chain_makers.go b/core/chain_makers.go index bbf1b1439..810741820 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -45,8 +45,8 @@ func NewChainMan(block *types.Block, eventMux *event.TypeMux, db common.Database return newChainManager(block, eventMux, db) } -func NewBlockProc(db common.Database, txpool *TxPool, cman *ChainManager, eventMux *event.TypeMux) *BlockProcessor { - return newBlockProcessor(db, txpool, cman, eventMux) +func NewBlockProc(db common.Database, cman *ChainManager, eventMux *event.TypeMux) *BlockProcessor { + return newBlockProcessor(db, cman, eventMux) } func NewCanonical(n int, db common.Database) (*BlockProcessor, error) { @@ -120,8 +120,10 @@ func newChainManager(block *types.Block, eventMux *event.TypeMux, db common.Data } // block processor with fake pow -func newBlockProcessor(db common.Database, txpool *TxPool, cman *ChainManager, eventMux *event.TypeMux) *BlockProcessor { - bman := NewBlockProcessor(db, db, FakePow{}, txpool, newChainManager(nil, eventMux, db), eventMux) +func newBlockProcessor(db common.Database, cman *ChainManager, eventMux *event.TypeMux) *BlockProcessor { + chainMan := newChainManager(nil, eventMux, db) + txpool := NewTxPool(eventMux, chainMan.State) + bman := NewBlockProcessor(db, db, FakePow{}, txpool, chainMan, eventMux) return bman } @@ -129,9 +131,8 @@ func newBlockProcessor(db common.Database, txpool *TxPool, cman *ChainManager, e // on result of makeChain func newCanonical(n int, db common.Database) (*BlockProcessor, error) { eventMux := &event.TypeMux{} - txpool := NewTxPool(eventMux) - bman := newBlockProcessor(db, txpool, newChainManager(nil, eventMux, db), eventMux) + bman := newBlockProcessor(db, newChainManager(nil, eventMux, db), eventMux) bman.bc.SetProcessor(bman) parent := bman.bc.CurrentBlock() if n == 0 { diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go index bf172f3bf..19afe0d5c 100644 --- a/core/chain_manager_test.go +++ b/core/chain_manager_test.go @@ -255,7 +255,7 @@ func TestChainInsertions(t *testing.T) { var eventMux event.TypeMux chainMan := NewChainManager(db, db, &eventMux) - txPool := NewTxPool(&eventMux) + txPool := NewTxPool(&eventMux, chainMan.State) blockMan := NewBlockProcessor(db, db, nil, txPool, chainMan, &eventMux) chainMan.SetProcessor(blockMan) @@ -301,7 +301,7 @@ func TestChainMultipleInsertions(t *testing.T) { } var eventMux event.TypeMux chainMan := NewChainManager(db, db, &eventMux) - txPool := NewTxPool(&eventMux) + txPool := NewTxPool(&eventMux, chainMan.State) blockMan := NewBlockProcessor(db, db, nil, txPool, chainMan, &eventMux) chainMan.SetProcessor(blockMan) done := make(chan bool, max) diff --git a/core/filter.go b/core/filter.go index 1dca5501d..dd15db27d 100644 --- a/core/filter.go +++ b/core/filter.go @@ -124,17 +124,17 @@ func (self *Filter) FilterLogs(logs state.Logs) state.Logs { // Filter the logs for interesting stuff Logs: for _, log := range logs { - if len(self.address) > 0 && !includes(self.address, log.Address()) { + if len(self.address) > 0 && !includes(self.address, log.Address) { continue } logTopics := make([]common.Hash, len(self.topics)) - copy(logTopics, log.Topics()) + copy(logTopics, log.Topics) for i, topics := range self.topics { for _, topic := range topics { var match bool - if log.Topics()[i] == topic { + if log.Topics[i] == topic { match = true } if !match { diff --git a/core/state/log.go b/core/state/log.go index f8aa4c08c..a7aa784e2 100644 --- a/core/state/log.go +++ b/core/state/log.go @@ -8,87 +8,31 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -type Log interface { - Address() common.Address - Topics() []common.Hash - Data() []byte +type Log struct { + Address common.Address + Topics []common.Hash + Data []byte + Number uint64 - Number() uint64 + TxHash common.Hash + TxIndex uint + BlockHash common.Hash + Index uint } -type StateLog struct { - address common.Address - topics []common.Hash - data []byte - number uint64 +func NewLog(address common.Address, topics []common.Hash, data []byte, number uint64) *Log { + return &Log{Address: address, Topics: topics, Data: data, Number: number} } -func NewLog(address common.Address, topics []common.Hash, data []byte, number uint64) *StateLog { - return &StateLog{address, topics, data, number} +func (self *Log) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{self.Address, self.Topics, self.Data}) } -func (self *StateLog) Address() common.Address { - return self.address +func (self *Log) String() string { + return fmt.Sprintf(`log: %x %x %x`, self.Address, self.Topics, self.Data) } -func (self *StateLog) Topics() []common.Hash { - return self.topics -} - -func (self *StateLog) Data() []byte { - return self.data -} - -func (self *StateLog) Number() uint64 { - return self.number -} - -/* -func NewLogFromValue(decoder *common.Value) *StateLog { - var extlog struct { - - } - - log := &StateLog{ - address: decoder.Get(0).Bytes(), - data: decoder.Get(2).Bytes(), - } - - it := decoder.Get(1).NewIterator() - for it.Next() { - log.topics = append(log.topics, it.Value().Bytes()) - } - - return log -} -*/ - -func (self *StateLog) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{self.address, self.topics, self.data}) -} - -/* -func (self *StateLog) RlpData() interface{} { - return []interface{}{self.address, common.ByteSliceToInterface(self.topics), self.data} -} -*/ - -func (self *StateLog) String() string { - return fmt.Sprintf(`log: %x %x %x`, self.address, self.topics, self.data) -} - -type Logs []Log - -/* -func (self Logs) RlpData() interface{} { - data := make([]interface{}, len(self)) - for i, log := range self { - data[i] = log.RlpData() - } - - return data -} -*/ +type Logs []*Log func (self Logs) String() (ret string) { for _, log := range self { diff --git a/core/state/managed_state.go b/core/state/managed_state.go index ddf337af3..9d2fc48e7 100644 --- a/core/state/managed_state.go +++ b/core/state/managed_state.go @@ -62,7 +62,7 @@ func (ms *ManagedState) NewNonce(addr common.Address) uint64 { } } account.nonces = append(account.nonces, true) - return uint64(len(account.nonces)) + account.nstart + return uint64(len(account.nonces)-1) + account.nstart } // GetNonce returns the canonical nonce for the managed or unmanged account @@ -109,5 +109,5 @@ func (ms *ManagedState) getAccount(addr common.Address) *account { } func newAccount(so *StateObject) *account { - return &account{so, so.nonce - 1, nil} + return &account{so, so.nonce, nil} } diff --git a/core/state/managed_state_test.go b/core/state/managed_state_test.go index 766231d21..c7ef2b323 100644 --- a/core/state/managed_state_test.go +++ b/core/state/managed_state_test.go @@ -4,12 +4,15 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" ) var addr = common.BytesToAddress([]byte("test")) func create() (*ManagedState, *account) { - ms := ManageState(&StateDB{stateObjects: make(map[string]*StateObject)}) + db, _ := ethdb.NewMemDatabase() + statedb := New(common.Hash{}, db) + ms := ManageState(statedb) so := &StateObject{address: addr, nonce: 100} ms.StateDB.stateObjects[addr.Str()] = so ms.accounts[addr.Str()] = newAccount(so) @@ -95,13 +98,13 @@ func TestSetNonce(t *testing.T) { ms.SetNonce(addr, 10) if ms.GetNonce(addr) != 10 { - t.Errorf("Expected nonce of 10, got", ms.GetNonce(addr)) + t.Error("Expected nonce of 10, got", ms.GetNonce(addr)) } addr[0] = 1 ms.StateDB.SetNonce(addr, 1) if ms.GetNonce(addr) != 1 { - t.Errorf("Expected nonce of 1, got", ms.GetNonce(addr)) + t.Error("Expected nonce of 1, got", ms.GetNonce(addr)) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 065cbd607..b3050515b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -23,29 +23,44 @@ type StateDB struct { refund map[string]*big.Int - logs Logs + thash, bhash common.Hash + txIndex int + logs map[common.Hash]Logs } // Create a new state from a given trie func New(root common.Hash, db common.Database) *StateDB { trie := trie.NewSecure(root[:], db) - return &StateDB{db: db, trie: trie, stateObjects: make(map[string]*StateObject), refund: make(map[string]*big.Int)} + return &StateDB{db: db, trie: trie, stateObjects: make(map[string]*StateObject), refund: make(map[string]*big.Int), logs: make(map[common.Hash]Logs)} } func (self *StateDB) PrintRoot() { self.trie.Trie.PrintRoot() } -func (self *StateDB) EmptyLogs() { - self.logs = nil +func (self *StateDB) StartRecord(thash, bhash common.Hash, ti int) { + self.thash = thash + self.bhash = bhash + self.txIndex = ti } -func (self *StateDB) AddLog(log Log) { - self.logs = append(self.logs, log) +func (self *StateDB) AddLog(log *Log) { + log.TxHash = self.thash + log.BlockHash = self.bhash + log.TxIndex = uint(self.txIndex) + self.logs[self.thash] = append(self.logs[self.thash], log) +} + +func (self *StateDB) GetLogs(hash common.Hash) Logs { + return self.logs[hash] } func (self *StateDB) Logs() Logs { - return self.logs + var logs Logs + for _, lgs := range self.logs { + logs = append(logs, lgs...) + } + return logs } func (self *StateDB) Refund(address common.Address, gas *big.Int) { @@ -60,6 +75,10 @@ func (self *StateDB) Refund(address common.Address, gas *big.Int) { * GETTERS */ +func (self *StateDB) HasAccount(addr common.Address) bool { + return self.GetStateObject(addr) != nil +} + // Retrieve the balance from the given address or 0 if object not found func (self *StateDB) GetBalance(addr common.Address) *big.Int { stateObject := self.GetStateObject(addr) @@ -253,9 +272,10 @@ func (self *StateDB) Copy() *StateDB { state.refund[addr] = new(big.Int).Set(refund) } - logs := make(Logs, len(self.logs)) - copy(logs, self.logs) - state.logs = logs + for hash, logs := range self.logs { + state.logs[hash] = make(Logs, len(logs)) + copy(state.logs[hash], logs) + } return state } diff --git a/core/state_transition.go b/core/state_transition.go index e67abb951..d95cbd35a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -74,6 +74,19 @@ func MessageGasValue(msg Message) *big.Int { return new(big.Int).Mul(msg.Gas(), msg.GasPrice()) } +func IntrinsicGas(msg Message) *big.Int { + igas := new(big.Int).Set(params.TxGas) + for _, byt := range msg.Data() { + if byt != 0 { + igas.Add(igas, params.TxDataNonZeroGas) + } else { + igas.Add(igas, params.TxDataZeroGas) + } + } + + return igas +} + func ApplyMessage(env vm.Environment, msg Message, coinbase *state.StateObject) ([]byte, *big.Int, error) { return NewStateTransition(env, msg, coinbase).transitionState() } @@ -177,22 +190,8 @@ func (self *StateTransition) transitionState() (ret []byte, usedGas *big.Int, er sender = self.From() ) - // Transaction gas - if err = self.UseGas(params.TxGas); err != nil { - return nil, nil, InvalidTxError(err) - } - - // Pay data gas - dgas := new(big.Int) - for _, byt := range self.data { - if byt != 0 { - dgas.Add(dgas, params.TxDataNonZeroGas) - } else { - dgas.Add(dgas, params.TxDataZeroGas) - } - } - - if err = self.UseGas(dgas); err != nil { + // Pay intrinsic gas + if err = self.UseGas(IntrinsicGas(self.msg)); err != nil { return nil, nil, InvalidTxError(err) } diff --git a/core/transaction_pool.go b/core/transaction_pool.go index 930efdaec..94a94f93d 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -3,19 +3,24 @@ package core import ( "errors" "fmt" + "math/big" "sync" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" "gopkg.in/fatih/set.v0" ) var ( - txplogger = logger.NewLogger("TXP") - - ErrInvalidSender = errors.New("Invalid sender") + ErrInvalidSender = errors.New("Invalid sender") + ErrImpossibleNonce = errors.New("Impossible nonce") + ErrNonExistentAccount = errors.New("Account does not exist") + ErrInsufficientFunds = errors.New("Insufficient funds") + ErrIntrinsicGas = errors.New("Intrinsic gas too low") ) const txPoolQueueSize = 50 @@ -41,52 +46,62 @@ type TxPool struct { queueChan chan *types.Transaction // Quiting channel quit chan bool + // The state function which will allow us to do some pre checkes + currentState func() *state.StateDB // The actual pool - //pool *list.List txs map[common.Hash]*types.Transaction invalidHashes *set.Set - SecondaryProcessor TxProcessor - subscribers []chan TxMsg eventMux *event.TypeMux } -func NewTxPool(eventMux *event.TypeMux) *TxPool { +func NewTxPool(eventMux *event.TypeMux, currentStateFn func() *state.StateDB) *TxPool { return &TxPool{ txs: make(map[common.Hash]*types.Transaction), queueChan: make(chan *types.Transaction, txPoolQueueSize), quit: make(chan bool), eventMux: eventMux, invalidHashes: set.New(), + currentState: currentStateFn, } } func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error { // Validate sender - if _, err := tx.From(); err != nil { + var ( + from common.Address + err error + ) + + if from, err = tx.From(); err != nil { return ErrInvalidSender } + // Validate curve param v, _, _ := tx.Curve() if v > 28 || v < 27 { return fmt.Errorf("tx.v != (28 || 27) => %v", v) } - return nil - /* XXX this kind of validation needs to happen elsewhere in the gui when sending txs. - Other clients should do their own validation. Value transfer could throw error - but doesn't necessarily invalidate the tx. Gas can still be payed for and miner - can still be rewarded for their inclusion and processing. - sender := pool.stateQuery.GetAccount(senderAddr) - totAmount := new(big.Int).Set(tx.Value()) - // Make sure there's enough in the sender's account. Having insufficient - // funds won't invalidate this transaction but simple ignores it. - if sender.Balance().Cmp(totAmount) < 0 { - return fmt.Errorf("Insufficient amount in sender's (%x) account", tx.From()) + if !pool.currentState().HasAccount(from) { + return ErrNonExistentAccount } - */ + + if pool.currentState().GetBalance(from).Cmp(new(big.Int).Mul(tx.Price, tx.GasLimit)) < 0 { + return ErrInsufficientFunds + } + + if tx.GasLimit.Cmp(IntrinsicGas(tx)) < 0 { + return ErrIntrinsicGas + } + + if pool.currentState().GetNonce(from) > tx.Nonce() { + return ErrImpossibleNonce + } + + return nil } func (self *TxPool) addTx(tx *types.Transaction) { @@ -96,10 +111,12 @@ func (self *TxPool) addTx(tx *types.Transaction) { func (self *TxPool) add(tx *types.Transaction) error { hash := tx.Hash() + /* XXX I'm unsure about this. This is extremely dangerous and may result + in total black listing of certain transactions if self.invalidHashes.Has(hash) { return fmt.Errorf("Invalid transaction (%x)", hash[:4]) } - + */ if self.txs[hash] != nil { return fmt.Errorf("Known transaction (%x)", hash[:4]) } @@ -120,7 +137,10 @@ func (self *TxPool) add(tx *types.Transaction) error { // verified in ValidateTransaction. f, _ := tx.From() from := common.Bytes2Hex(f[:4]) - txplogger.Debugf("(t) %x => %s (%v) %x\n", from, toname, tx.Value, tx.Hash()) + + if glog.V(logger.Debug) { + glog.Infof("(t) %x => %s (%v) %x\n", from, toname, tx.Value, tx.Hash()) + } // Notify the subscribers go self.eventMux.Post(TxPreEvent{tx}) @@ -145,10 +165,10 @@ func (self *TxPool) AddTransactions(txs []*types.Transaction) { for _, tx := range txs { if err := self.add(tx); err != nil { - txplogger.Debugln(err) + glog.V(logger.Debug).Infoln(err) } else { h := tx.Hash() - txplogger.Debugf("tx %x\n", h[:4]) + glog.V(logger.Debug).Infof("tx %x\n", h[:4]) } } } @@ -167,23 +187,6 @@ func (self *TxPool) GetTransactions() (txs types.Transactions) { return } -func (pool *TxPool) RemoveInvalid(query StateQuery) { - pool.mu.Lock() - - var removedTxs types.Transactions - for _, tx := range pool.txs { - from, _ := tx.From() - sender := query.GetAccount(from[:]) - err := pool.ValidateTransaction(tx) - if err != nil || sender.Nonce() >= tx.Nonce() { - removedTxs = append(removedTxs, tx) - } - } - pool.mu.Unlock() - - pool.RemoveSet(removedTxs) -} - func (self *TxPool) RemoveSet(txs types.Transactions) { self.mu.Lock() defer self.mu.Unlock() @@ -213,5 +216,5 @@ func (pool *TxPool) Start() { func (pool *TxPool) Stop() { pool.Flush() - txplogger.Infoln("Stopped") + glog.V(logger.Info).Infoln("TX Pool stopped") } diff --git a/core/transaction_pool_test.go b/core/transaction_pool_test.go index abdc2709f..b7486adb3 100644 --- a/core/transaction_pool_test.go +++ b/core/transaction_pool_test.go @@ -13,87 +13,50 @@ import ( "github.com/ethereum/go-ethereum/event" ) -// State query interface -type stateQuery struct{ db common.Database } - -func SQ() stateQuery { - db, _ := ethdb.NewMemDatabase() - return stateQuery{db: db} -} - -func (self stateQuery) GetAccount(addr []byte) *state.StateObject { - return state.NewStateObject(common.BytesToAddress(addr), self.db) -} - func transaction() *types.Transaction { - return types.NewTransactionMessage(common.Address{}, common.Big0, common.Big0, common.Big0, nil) + return types.NewTransactionMessage(common.Address{}, big.NewInt(100), big.NewInt(100), big.NewInt(100), nil) } -func setup() (*TxPool, *ecdsa.PrivateKey) { +func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { + db, _ := ethdb.NewMemDatabase() + statedb := state.New(common.Hash{}, db) + var m event.TypeMux key, _ := crypto.GenerateKey() - return NewTxPool(&m), key + return NewTxPool(&m, func() *state.StateDB { return statedb }), key } -func TestTxAdding(t *testing.T) { - pool, key := setup() - tx1 := transaction() - tx1.SignECDSA(key) - err := pool.Add(tx1) - if err != nil { - t.Error(err) - } - - err = pool.Add(tx1) - if err == nil { - t.Error("added tx twice") - } -} +func TestInvalidTransactions(t *testing.T) { + pool, key := setupTxPool() -func TestAddInvalidTx(t *testing.T) { - pool, _ := setup() - tx1 := transaction() - err := pool.Add(tx1) - if err == nil { - t.Error("expected error") + tx := transaction() + tx.SignECDSA(key) + err := pool.Add(tx) + if err != ErrNonExistentAccount { + t.Error("expected", ErrNonExistentAccount) } -} -func TestRemoveSet(t *testing.T) { - pool, _ := setup() - tx1 := transaction() - pool.addTx(tx1) - pool.RemoveSet(types.Transactions{tx1}) - if pool.Size() > 0 { - t.Error("expected pool size to be 0") + from, _ := tx.From() + pool.currentState().AddBalance(from, big.NewInt(1)) + err = pool.Add(tx) + if err != ErrInsufficientFunds { + t.Error("expected", ErrInsufficientFunds) } -} -func TestRemoveInvalid(t *testing.T) { - pool, key := setup() - tx1 := transaction() - pool.addTx(tx1) - pool.RemoveInvalid(SQ()) - if pool.Size() > 0 { - t.Error("expected pool size to be 0") + pool.currentState().AddBalance(from, big.NewInt(100*100)) + err = pool.Add(tx) + if err != ErrIntrinsicGas { + t.Error("expected", ErrIntrinsicGas) } - tx1.SetNonce(1) - tx1.SignECDSA(key) - pool.addTx(tx1) - pool.RemoveInvalid(SQ()) - if pool.Size() != 1 { - t.Error("expected pool size to be 1, is", pool.Size()) - } -} + pool.currentState().SetNonce(from, 1) + pool.currentState().AddBalance(from, big.NewInt(0xffffffffffffff)) + tx.GasLimit = big.NewInt(100000) + tx.Price = big.NewInt(1) + tx.SignECDSA(key) -func TestInvalidSender(t *testing.T) { - pool, _ := setup() - tx := new(types.Transaction) - tx.R = new(big.Int) - tx.S = new(big.Int) - err := pool.ValidateTransaction(tx) - if err != ErrInvalidSender { - t.Errorf("expected %v, got %v", ErrInvalidSender, err) + err = pool.Add(tx) + if err != ErrImpossibleNonce { + t.Error("expected", ErrImpossibleNonce) } } diff --git a/core/types/bloom9.go b/core/types/bloom9.go index af90679ce..0d37cb19f 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -4,8 +4,8 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/crypto" ) func CreateBloom(receipts Receipts) Bloom { @@ -20,10 +20,10 @@ func CreateBloom(receipts Receipts) Bloom { func LogsBloom(logs state.Logs) *big.Int { bin := new(big.Int) for _, log := range logs { - data := make([]common.Hash, len(log.Topics())) - bin.Or(bin, bloom9(log.Address().Bytes())) + data := make([]common.Hash, len(log.Topics)) + bin.Or(bin, bloom9(log.Address.Bytes())) - for i, topic := range log.Topics() { + for i, topic := range log.Topics { data[i] = topic } diff --git a/core/types/receipt.go b/core/types/receipt.go index 83c981f93..414e4d364 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -7,8 +7,8 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/rlp" ) type Receipt struct { @@ -30,12 +30,6 @@ func (self *Receipt) EncodeRLP(w io.Writer) error { return rlp.Encode(w, []interface{}{self.PostState, self.CumulativeGasUsed, self.Bloom, self.logs}) } -/* -func (self *Receipt) RlpData() interface{} { - return []interface{}{self.PostState, self.CumulativeGasUsed, self.Bloom, self.logs.RlpData()} -} -*/ - func (self *Receipt) RlpEncode() []byte { bytes, err := rlp.EncodeToBytes(self) if err != nil { @@ -58,17 +52,6 @@ func (self *Receipt) String() string { type Receipts []*Receipt -/* -func (self Receipts) RlpData() interface{} { - data := make([]interface{}, len(self)) - for i, receipt := range self { - data[i] = receipt.RlpData() - } - - return data -} -*/ - func (self Receipts) RlpEncode() []byte { bytes, err := rlp.EncodeToBytes(self) if err != nil { diff --git a/core/vm/environment.go b/core/vm/environment.go index 72e18c353..cc9570fc8 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -22,7 +22,7 @@ type Environment interface { Difficulty() *big.Int GasLimit() *big.Int Transfer(from, to Account, amount *big.Int) error - AddLog(state.Log) + AddLog(*state.Log) VmType() Type diff --git a/core/vm/vm.go b/core/vm/vm.go index 118f60076..927b67293 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -557,7 +557,8 @@ func (self *Vm) Run(context *Context, callData []byte) (ret []byte, err error) { } data := mem.Get(mStart.Int64(), mSize.Int64()) - log := &Log{context.Address(), topics, data, self.env.BlockNumber().Uint64()} + log := state.NewLog(context.Address(), topics, data, self.env.BlockNumber().Uint64()) + //log := &Log{context.Address(), topics, data, self.env.BlockNumber().Uint64()} self.env.AddLog(log) self.Printf(" => %v", log) diff --git a/core/vm_env.go b/core/vm_env.go index 6a604fccd..c439d2946 100644 --- a/core/vm_env.go +++ b/core/vm_env.go @@ -47,7 +47,7 @@ func (self *VMEnv) GetHash(n uint64) common.Hash { return common.Hash{} } -func (self *VMEnv) AddLog(log state.Log) { +func (self *VMEnv) AddLog(log *state.Log) { self.state.AddLog(log) } func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) error { diff --git a/eth/backend.go b/eth/backend.go index 317ee7373..c7a5b233f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -195,7 +195,7 @@ func New(config *Config) (*Ethereum, error) { eth.chainManager = core.NewChainManager(blockDb, stateDb, eth.EventMux()) eth.pow = ethash.New(eth.chainManager) - eth.txPool = core.NewTxPool(eth.EventMux()) + eth.txPool = core.NewTxPool(eth.EventMux(), eth.chainManager.State) eth.blockProcessor = core.NewBlockProcessor(stateDb, extraDb, eth.pow, eth.txPool, eth.chainManager, eth.EventMux()) eth.chainManager.SetProcessor(eth.blockProcessor) eth.whisper = whisper.New() @@ -449,7 +449,7 @@ func (self *Ethereum) syncAccounts(tx *types.Transaction) { if self.accountManager.HasAccount(from.Bytes()) { if self.chainManager.TxState().GetNonce(from) < tx.Nonce() { - self.chainManager.TxState().SetNonce(from, tx.Nonce()+1) + self.chainManager.TxState().SetNonce(from, tx.Nonce()) } } } diff --git a/miner/worker.go b/miner/worker.go index 7f2728f9c..b74b67552 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -266,6 +266,8 @@ func (self *worker) commitNewWork() { ) gasLimit: for i, tx := range transactions { + self.current.state.StartRecord(tx.Hash(), common.Hash{}, 0) + err := self.commitTransaction(tx) switch { case core.IsNonceErr(err) || core.IsInvalidTxErr(err): @@ -286,7 +288,7 @@ gasLimit: tcount++ } } - self.eth.TxPool().InvalidateSet(remove) + //self.eth.TxPool().InvalidateSet(remove) var ( uncles []*types.Header diff --git a/rpc/args.go b/rpc/args.go index 5b1c271cc..4bc36f5d9 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -279,11 +279,6 @@ func (args *CallArgs) UnmarshalJSON(b []byte) (err error) { return NewDecodeParamError(err.Error()) } - if len(ext.From) == 0 { - return NewValidationError("from", "is required") - } - args.From = ext.From - if len(ext.To) == 0 { return NewValidationError("to", "is required") } diff --git a/rpc/args_test.go b/rpc/args_test.go index 050f8e472..cfe6c0c45 100644 --- a/rpc/args_test.go +++ b/rpc/args_test.go @@ -671,10 +671,6 @@ func TestCallArgs(t *testing.T) { t.Error(err) } - if expected.From != args.From { - t.Errorf("From shoud be %#v but is %#v", expected.From, args.From) - } - if expected.To != args.To { t.Errorf("To shoud be %#v but is %#v", expected.To, args.To) } @@ -895,19 +891,8 @@ func TestCallArgsNotStrings(t *testing.T) { } } -func TestCallArgsFromEmpty(t *testing.T) { - input := `[{"to": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]` - - args := new(CallArgs) - str := ExpectValidationError(json.Unmarshal([]byte(input), &args)) - if len(str) > 0 { - t.Error(str) - } -} - func TestCallArgsToEmpty(t *testing.T) { input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155"}]` - args := new(CallArgs) str := ExpectValidationError(json.Unmarshal([]byte(input), &args)) if len(str) > 0 { diff --git a/rpc/responses.go b/rpc/responses.go index 52a2f714c..3620f643e 100644 --- a/rpc/responses.go +++ b/rpc/responses.go @@ -284,15 +284,19 @@ type LogRes struct { TransactionIndex *hexnum `json:"transactionIndex"` } -func NewLogRes(log state.Log) LogRes { +func NewLogRes(log *state.Log) LogRes { var l LogRes - l.Topics = make([]*hexdata, len(log.Topics())) - for j, topic := range log.Topics() { + l.Topics = make([]*hexdata, len(log.Topics)) + for j, topic := range log.Topics { l.Topics[j] = newHexData(topic) } - l.Address = newHexData(log.Address()) - l.Data = newHexData(log.Data()) - l.BlockNumber = newHexNum(log.Number()) + l.Address = newHexData(log.Address) + l.Data = newHexData(log.Data) + l.BlockNumber = newHexNum(log.Number) + l.LogIndex = newHexNum(log.Index) + l.TransactionHash = newHexData(log.TxHash) + l.TransactionIndex = newHexNum(log.TxIndex) + l.BlockHash = newHexData(log.BlockHash) return l } diff --git a/rpc/responses_test.go b/rpc/responses_test.go index e04a064ce..66323e5f5 100644 --- a/rpc/responses_test.go +++ b/rpc/responses_test.go @@ -217,7 +217,7 @@ func TestNewLogRes(t *testing.T) { } func TestNewLogsRes(t *testing.T) { - logs := make([]state.Log, 3) + logs := make([]*state.Log, 3) logs[0] = makeStateLog(1) logs[1] = makeStateLog(2) logs[2] = makeStateLog(3) @@ -235,7 +235,7 @@ func TestNewLogsRes(t *testing.T) { } -func makeStateLog(num int) state.Log { +func makeStateLog(num int) *state.Log { address := common.HexToAddress("0x0") data := []byte{1, 2, 3} number := uint64(num) diff --git a/tests/helper/vm.go b/tests/helper/vm.go index 9f62ada95..fb3fb40a5 100644 --- a/tests/helper/vm.go +++ b/tests/helper/vm.go @@ -66,7 +66,7 @@ func (self *Env) VmType() vm.Type { return vm.StdVmTy } func (self *Env) GetHash(n uint64) common.Hash { return common.BytesToHash(crypto.Sha3([]byte(big.NewInt(int64(n)).String()))) } -func (self *Env) AddLog(log state.Log) { +func (self *Env) AddLog(log *state.Log) { self.logs = append(self.logs, log) } func (self *Env) Depth() int { return self.depth } diff --git a/tests/vm/gh_test.go b/tests/vm/gh_test.go index a96a3eba6..a5bc273f3 100644 --- a/tests/vm/gh_test.go +++ b/tests/vm/gh_test.go @@ -9,10 +9,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "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/logger" - "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/tests/helper" ) @@ -82,13 +80,15 @@ func RunVmTest(p string, t *testing.T) { tests := make(map[string]VmTest) helper.CreateFileTests(t, p, &tests) - vm.Debug = true - glog.SetV(4) - glog.SetToStderr(true) for name, test := range tests { - if name != "stackLimitPush32_1024" { - continue - } + /* + vm.Debug = true + glog.SetV(4) + glog.SetToStderr(true) + if name != "stackLimitPush32_1024" { + continue + } + */ db, _ := ethdb.NewMemDatabase() statedb := state.New(common.Hash{}, db) for addr, account := range test.Pre { @@ -182,20 +182,20 @@ func RunVmTest(p string, t *testing.T) { t.Errorf("log length mismatch. Expected %d, got %d", len(test.Logs), len(logs)) } else { for i, log := range test.Logs { - if common.HexToAddress(log.AddressF) != logs[i].Address() { - t.Errorf("'%s' log address expected %v got %x", name, log.AddressF, logs[i].Address()) + if common.HexToAddress(log.AddressF) != logs[i].Address { + t.Errorf("'%s' log address expected %v got %x", name, log.AddressF, logs[i].Address) } - if !bytes.Equal(logs[i].Data(), helper.FromHex(log.DataF)) { - t.Errorf("'%s' log data expected %v got %x", name, log.DataF, logs[i].Data()) + if !bytes.Equal(logs[i].Data, helper.FromHex(log.DataF)) { + t.Errorf("'%s' log data expected %v got %x", name, log.DataF, logs[i].Data) } - if len(log.TopicsF) != len(logs[i].Topics()) { - t.Errorf("'%s' log topics length expected %d got %d", name, len(log.TopicsF), logs[i].Topics()) + if len(log.TopicsF) != len(logs[i].Topics) { + t.Errorf("'%s' log topics length expected %d got %d", name, len(log.TopicsF), logs[i].Topics) } else { for j, topic := range log.TopicsF { - if common.HexToHash(topic) != logs[i].Topics()[j] { - t.Errorf("'%s' log topic[%d] expected %v got %x", name, j, topic, logs[i].Topics()[j]) + if common.HexToHash(topic) != logs[i].Topics[j] { + t.Errorf("'%s' log topic[%d] expected %v got %x", name, j, topic, logs[i].Topics[j]) } } } diff --git a/xeth/types.go b/xeth/types.go index 3f96f8f8b..739092474 100644 --- a/xeth/types.go +++ b/xeth/types.go @@ -77,15 +77,19 @@ func NewBlock(block *types.Block) *Block { } ptxs := make([]*Transaction, len(block.Transactions())) - for i, tx := range block.Transactions() { - ptxs[i] = NewTx(tx) - } + /* + for i, tx := range block.Transactions() { + ptxs[i] = NewTx(tx) + } + */ txlist := common.NewList(ptxs) puncles := make([]*Block, len(block.Uncles())) - for i, uncle := range block.Uncles() { - puncles[i] = NewBlock(types.NewBlockWithHeader(uncle)) - } + /* + for i, uncle := range block.Uncles() { + puncles[i] = NewBlock(types.NewBlockWithHeader(uncle)) + } + */ ulist := common.NewList(puncles) return &Block{ diff --git a/xeth/xeth.go b/xeth/xeth.go index b8d9ecb08..407fe69d5 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -393,7 +393,7 @@ func (self *XEth) NewFilterString(word string) int { self.logMut.Lock() defer self.logMut.Unlock() - self.logs[id].add(&state.StateLog{}) + self.logs[id].add(&state.Log{}) } case "latest": filter.BlockCallback = func(block *types.Block, logs state.Logs) { @@ -403,7 +403,7 @@ func (self *XEth) NewFilterString(word string) int { for _, log := range logs { self.logs[id].add(log) } - self.logs[id].add(&state.StateLog{}) + self.logs[id].add(&state.Log{}) } } @@ -572,8 +572,20 @@ func (self *XEth) PushTx(encodedTx string) (string, error) { func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { statedb := self.State().State() //self.eth.ChainManager().TransState() + var from *state.StateObject + if len(fromStr) == 0 { + accounts, err := self.backend.AccountManager().Accounts() + if err != nil || len(accounts) == 0 { + from = statedb.GetOrNewStateObject(common.Address{}) + } else { + from = statedb.GetOrNewStateObject(common.BytesToAddress(accounts[0].Address)) + } + } else { + from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr)) + } + msg := callmsg{ - from: statedb.GetOrNewStateObject(common.HexToAddress(fromStr)), + from: from, to: common.HexToAddress(toStr), gas: common.Big(gasStr), gasPrice: common.Big(gasPriceStr), @@ -729,7 +741,7 @@ type logFilter struct { id int } -func (l *logFilter) add(logs ...state.Log) { +func (l *logFilter) add(logs ...*state.Log) { l.logs = append(l.logs, logs...) } |