diff --git a/app/app.go b/app/app.go index af21daa62..2173cd4ec 100644 --- a/app/app.go +++ b/app/app.go @@ -3,7 +3,7 @@ package app import ( "strings" - "github.com/tendermint/basecoin/state" + sm "github.com/tendermint/basecoin/state" "github.com/tendermint/basecoin/types" . "github.com/tendermint/go-common" "github.com/tendermint/go-wire" @@ -26,22 +26,24 @@ const ( ) type Basecoin struct { - eyesCli *eyes.Client - govMint *gov.Governmint - state *state.State - plugins *types.Plugins + eyesCli *eyes.Client + govMint *gov.Governmint + state *sm.State + cacheState *sm.State + plugins *types.Plugins } func NewBasecoin(eyesCli *eyes.Client) *Basecoin { - govMint := gov.NewGovernmint(eyesCli) - state_ := state.NewState(eyesCli) + govMint := gov.NewGovernmint() + state := sm.NewState(eyesCli) plugins := types.NewPlugins() plugins.RegisterPlugin(PluginTypeByteGov, PluginNameGov, govMint) return &Basecoin{ - eyesCli: eyesCli, - govMint: govMint, - state: state_, - plugins: plugins, + eyesCli: eyesCli, + govMint: govMint, + state: state, + cacheState: nil, + plugins: plugins, } } @@ -59,7 +61,7 @@ func (app *Basecoin) SetOption(key string, value string) (log string) { if plugin == nil { return "Invalid plugin name: " + PluginName } - return plugin.SetOption(key, value) + return plugin.SetOption(app.state, key, value) } else { // Set option on basecoin switch key { @@ -92,7 +94,7 @@ func (app *Basecoin) AppendTx(txBytes []byte) (res tmsp.Result) { return tmsp.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) } // Validate and exec tx - res = state.ExecTx(app.state, app.plugins, tx, false, nil) + res = sm.ExecTx(app.state, app.plugins, tx, false, nil) if res.IsErr() { return res.PrependLog("Error in AppendTx") } @@ -111,7 +113,7 @@ func (app *Basecoin) CheckTx(txBytes []byte) (res tmsp.Result) { return tmsp.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) } // Validate tx - res = state.ExecTx(app.state, app.plugins, tx, true, nil) + res = sm.ExecTx(app.cacheState, app.plugins, tx, true, nil) if res.IsErr() { return res.PrependLog("Error in CheckTx") } @@ -130,8 +132,6 @@ func (app *Basecoin) Query(query []byte) (res tmsp.Result) { return tmsp.OK.SetLog("This type of query not yet supported") case PluginTypeByteEyes: return app.eyesCli.QuerySync(query) - case PluginTypeByteGov: - return app.govMint.Query(query) } return tmsp.ErrBaseUnknownPlugin.SetLog( Fmt("Unknown plugin with type byte %X", typeByte)) @@ -139,14 +139,7 @@ func (app *Basecoin) Query(query []byte) (res tmsp.Result) { // TMSP::Commit func (app *Basecoin) Commit() (res tmsp.Result) { - // First, commit all the plugins - for _, plugin := range app.plugins.GetList() { - res = plugin.Commit() - if res.IsErr() { - PanicSanity(Fmt("Error committing plugin %v", plugin.Name)) - } - } - // Then, commit eyes. + // Commit eyes. res = app.eyesCli.CommitSync() if res.IsErr() { PanicSanity("Error getting hash: " + res.Error()) @@ -157,32 +150,23 @@ func (app *Basecoin) Commit() (res tmsp.Result) { // TMSP::InitChain func (app *Basecoin) InitChain(validators []*tmsp.Validator) { for _, plugin := range app.plugins.GetList() { - if _, ok := plugin.Plugin.(tmsp.BlockchainAware); ok { - plugin.Plugin.(tmsp.BlockchainAware).InitChain(validators) - } + plugin.Plugin.InitChain(app.state, validators) } } // TMSP::BeginBlock func (app *Basecoin) BeginBlock(height uint64) { - app.state.ResetCacheState() for _, plugin := range app.plugins.GetList() { - if _, ok := plugin.Plugin.(tmsp.BlockchainAware); ok { - plugin.Plugin.(tmsp.BlockchainAware).BeginBlock(height) - } + plugin.Plugin.BeginBlock(app.state, height) } + app.cacheState = app.state.CacheWrap() } // TMSP::EndBlock -func (app *Basecoin) EndBlock(height uint64) (vals []*tmsp.Validator) { +func (app *Basecoin) EndBlock(height uint64) (diffs []*tmsp.Validator) { for _, plugin := range app.plugins.GetList() { - if plugin.Plugin == app.govMint { - vals = plugin.Plugin.(tmsp.BlockchainAware).EndBlock(height) - } else { - if _, ok := plugin.Plugin.(tmsp.BlockchainAware); ok { - plugin.Plugin.(tmsp.BlockchainAware).EndBlock(height) - } - } + moreDiffs := plugin.Plugin.EndBlock(app.state, height) + diffs = append(diffs, moreDiffs...) } return } diff --git a/state/execution.go b/state/execution.go index 7063461c1..2f7e7415a 100644 --- a/state/execution.go +++ b/state/execution.go @@ -8,20 +8,11 @@ import ( ) // If the tx is invalid, a TMSP error will be returned. -func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp.Result { +func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp.Result { // TODO: do something with fees fees := types.Coins{} - chainID := s.GetChainID() - - // Get the state. If isCheckTx, then we use a cache. - // The idea is to throw away this cache after every EndBlock(). - var state types.AccountGetterSetter - if isCheckTx { - state = s.GetCheckCache() - } else { - state = s - } + chainID := state.GetChainID() // Exec tx switch tx := tx.(type) { @@ -129,15 +120,16 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event } // Create inAcc checkpoint - inAccCopy := inAcc.Copy() + inAccDeducted := inAcc.Copy() // Run the tx. - cache := types.NewAccountCache(state) + // XXX cache := types.NewStateCache(state) + cache := state.CacheWrap() cache.SetAccount(tx.Input.Address, inAcc) - ctx := types.NewCallContext(cache, inAcc, coins) - res = plugin.RunTx(ctx, tx.Data) + ctx := types.NewCallContext(tx.Input.Address, coins) + res = plugin.RunTx(cache, ctx, tx.Data) if res.IsOK() { - cache.Sync() + cache.CacheSync() log.Info("Successful execution") // Fire events /* @@ -153,10 +145,10 @@ func ExecTx(s *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc event } else { log.Info("AppTx failed", "error", res) // Just return the coins and return. - inAccCopy.Balance = inAccCopy.Balance.Plus(coins) + inAccDeducted.Balance = inAccDeducted.Balance.Plus(coins) // But take the gas // TODO - state.SetAccount(tx.Input.Address, inAccCopy) + state.SetAccount(tx.Input.Address, inAccDeducted) } return res diff --git a/state/state.go b/state/state.go index 2f29184ce..3d767d6d8 100644 --- a/state/state.go +++ b/state/state.go @@ -4,26 +4,21 @@ import ( "github.com/tendermint/basecoin/types" . "github.com/tendermint/go-common" "github.com/tendermint/go-wire" - eyes "github.com/tendermint/merkleeyes/client" ) +// CONTRACT: State should be quick to copy. +// See CacheWrap(). type State struct { - chainID string - eyesCli *eyes.Client - checkCache *types.AccountCache - - LastBlockHeight uint64 - LastBlockHash []byte - GasLimit int64 + chainID string + store types.KVStore + cache *types.KVCache // optional } -func NewState(eyesCli *eyes.Client) *State { - s := &State{ +func NewState(store types.KVStore) *State { + return &State{ chainID: "", - eyesCli: eyesCli, + store: store, } - s.checkCache = types.NewAccountCache(s) - return s } func (s *State) SetChainID(chainID string) { @@ -37,36 +32,34 @@ func (s *State) GetChainID() string { return s.chainID } +func (s *State) Get(key []byte) (value []byte) { + return s.store.Get(key) +} + +func (s *State) Set(key []byte, value []byte) { + s.store.Set(key, value) +} + func (s *State) GetAccount(addr []byte) *types.Account { - res := s.eyesCli.GetSync(AccountKey(addr)) - if res.IsErr() { - panic(Fmt("Error loading account addr %X error: %v", addr, res.Error())) - } - if len(res.Data) == 0 { - return nil - } - var acc *types.Account - err := wire.ReadBinaryBytes(res.Data, &acc) - if err != nil { - panic(Fmt("Error reading account %X error: %v", res.Data, err.Error())) - } - return acc + return GetAccount(s.store, addr) } func (s *State) SetAccount(addr []byte, acc *types.Account) { - accBytes := wire.BinaryBytes(acc) - res := s.eyesCli.SetSync(AccountKey(addr), accBytes) - if res.IsErr() { - panic(Fmt("Error storing account addr %X error: %v", addr, res.Error())) + SetAccount(s.store, addr, acc) +} + +func (s *State) CacheWrap() *State { + cache := types.NewKVCache(s.store) + return &State{ + chainID: s.chainID, + store: cache, + cache: cache, } } -func (s *State) GetCheckCache() *types.AccountCache { - return s.checkCache -} - -func (s *State) ResetCacheState() { - s.checkCache = types.NewAccountCache(s) +// NOTE: errors if s is not from CacheWrap() +func (s *State) CacheSync() { + s.cache.Sync() } //---------------------------------------- @@ -74,3 +67,22 @@ func (s *State) ResetCacheState() { func AccountKey(addr []byte) []byte { return append([]byte("base/a/"), addr...) } + +func GetAccount(store types.KVStore, addr []byte) *types.Account { + data := store.Get(AccountKey(addr)) + if len(data) == 0 { + return nil + } + var acc *types.Account + err := wire.ReadBinaryBytes(data, &acc) + if err != nil { + panic(Fmt("Error reading account %X error: %v", + data, err.Error())) + } + return acc +} + +func SetAccount(store types.KVStore, addr []byte, acc *types.Account) { + accBytes := wire.BinaryBytes(acc) + store.Set(AccountKey(addr), accBytes) +} diff --git a/tests/tmsp/main.go b/tests/tmsp/main.go index c857f63c2..cbca53533 100644 --- a/tests/tmsp/main.go +++ b/tests/tmsp/main.go @@ -163,6 +163,7 @@ func testGov() { } fmt.Println(res) + // XXX Difficult to debug without a stacktrace... // TODO more tests... } diff --git a/types/account_cache.go b/types/account_cache.go deleted file mode 100644 index 69ae10603..000000000 --- a/types/account_cache.go +++ /dev/null @@ -1,57 +0,0 @@ -package types - -import ( - "sort" -) - -type AccountCache struct { - state AccountGetterSetter - accounts map[string]*Account -} - -func NewAccountCache(state AccountGetterSetter) *AccountCache { - return &AccountCache{ - state: state, - accounts: make(map[string]*Account), - } -} - -func (cache *AccountCache) GetAccount(addr []byte) *Account { - acc, ok := cache.accounts[string(addr)] - if !ok { - acc = cache.state.GetAccount(addr) - cache.accounts[string(addr)] = acc - } - return acc -} - -func (cache *AccountCache) SetAccount(addr []byte, acc *Account) { - cache.accounts[string(addr)] = acc -} - -func (cache *AccountCache) Sync() { - // MUST BE DETERMINISTIC - // First, order the addrs. - addrs := []string{} - for addr := range cache.accounts { - addrs = append(addrs, string(addr)) - } - sort.Strings(addrs) - - // Set the accounts in order. - for _, addr := range addrs { - cache.state.SetAccount([]byte(addr), cache.accounts[addr]) - } - - // Reset accounts - cache.accounts = make(map[string]*Account) -} - -//---------------------------------------- - -// NOT USED -type AccountCacher interface { - GetAccount(addr []byte) *Account - SetAccount(addr []byte, acc *Account) - Sync() -} diff --git a/types/kvstore.go b/types/kvstore.go new file mode 100644 index 000000000..a447fea4c --- /dev/null +++ b/types/kvstore.go @@ -0,0 +1,81 @@ +package types + +import ( + "container/list" +) + +type KVStore interface { + Set(key, value []byte) + Get(key []byte) (value []byte) +} + +//---------------------------------------- + +type MemKVStore struct { + m map[string][]byte +} + +func NewMemKVStore() *MemKVStore { + return &MemKVStore{ + m: make(map[string][]byte, 0), + } +} + +func (mkv *MemKVStore) Set(key []byte, value []byte) { + mkv.m[string(key)] = value +} + +func (mkv *MemKVStore) Get(key []byte) (value []byte) { + return mkv.m[string(key)] +} + +//---------------------------------------- + +// A Cache that enforces deterministic sync order. +type KVCache struct { + store KVStore + cache map[string]kvCacheValue + keys *list.List +} + +type kvCacheValue struct { + v []byte // The value of some key + e *list.Element // The KVCache.keys element +} + +func NewKVCache(store KVStore) *KVCache { + return (&KVCache{ + store: store, + }).Reset() +} + +func (kvc *KVCache) Reset() *KVCache { + kvc.cache = make(map[string]kvCacheValue) + kvc.keys = list.New() + return kvc +} + +func (kvc *KVCache) Set(key []byte, value []byte) { + cacheValue, ok := kvc.cache[string(key)] + if ok { + kvc.keys.MoveToBack(cacheValue.e) + } else { + cacheValue.e = kvc.keys.PushBack(key) + } + cacheValue.v = value + kvc.cache[string(key)] = cacheValue +} + +func (kvc *KVCache) Get(key []byte) (value []byte) { + cacheValue := kvc.cache[string(key)] + return cacheValue.v +} + +func (kvc *KVCache) Sync() { + for e := kvc.keys.Front(); e != nil; e = e.Next() { + key := e.Value.([]byte) + value := kvc.cache[string(key)] + kvc.store.Set(key, value.v) + } + kvc.Reset() +} diff --git a/types/plugin.go b/types/plugin.go index 35f2239f7..51ac122a1 100644 --- a/types/plugin.go +++ b/types/plugin.go @@ -4,12 +4,12 @@ import ( tmsp "github.com/tendermint/tmsp/types" ) -// Value is any floating value. It must be given to someone. type Plugin interface { - SetOption(key string, value string) (log string) - RunTx(ctx CallContext, txBytes []byte) (res tmsp.Result) - Query(query []byte) (res tmsp.Result) - Commit() (res tmsp.Result) + SetOption(store KVStore, key string, value string) (log string) + RunTx(store KVStore, ctx CallContext, txBytes []byte) (res tmsp.Result) + InitChain(store KVStore, vals []*tmsp.Validator) + BeginBlock(store KVStore, height uint64) + EndBlock(store KVStore, height uint64) []*tmsp.Validator } type NamedPlugin struct { @@ -21,14 +21,12 @@ type NamedPlugin struct { //---------------------------------------- type CallContext struct { - Cache AccountCacher - Caller *Account + Caller []byte Coins Coins } -func NewCallContext(cache AccountCacher, caller *Account, coins Coins) CallContext { +func NewCallContext(caller []byte, coins Coins) CallContext { return CallContext{ - Cache: cache, Caller: caller, Coins: coins, }