Plugin interface methods take store

This commit is contained in:
Jae Kwon 2016-05-01 13:52:08 -07:00
parent fefcbbf3b0
commit 324e72f36d
7 changed files with 170 additions and 159 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -163,6 +163,7 @@ func testGov() {
}
fmt.Println(res)
// XXX Difficult to debug without a stacktrace...
// TODO more tests...
}

View File

@ -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()
}

81
types/kvstore.go Normal file
View File

@ -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()
}

View File

@ -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,
}