diff --git a/app/app.go b/app/app.go index 87522f612..228a46f0c 100644 --- a/app/app.go +++ b/app/app.go @@ -20,11 +20,13 @@ type Basecoin struct { } func NewBasecoin(eyesCli *eyes.Client) *Basecoin { + state_ := state.NewState(eyesCli) govMint := gov.NewGovernmint(eyesCli) + state_.RegisterPlugin([]byte("gov"), govMint) return &Basecoin{ eyesCli: eyesCli, govMint: govMint, - state: state.NewState(eyesCli), + state: state_, } } diff --git a/state/account_cache.go b/state/account_cache.go new file mode 100644 index 000000000..47253c838 --- /dev/null +++ b/state/account_cache.go @@ -0,0 +1,46 @@ +package state + +import ( + "github.com/tendermint/basecoin/types" + "sort" +) + +type AccountCache struct { + state *State + accounts map[string]*types.Account +} + +func NewAccountCache(state *State) *AccountCache { + return &AccountCache{ + state: state, + accounts: make(map[string]*types.Account), + } +} + +func (cache *AccountCache) GetAccount(addr []byte) *types.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 *types.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]) + } +} diff --git a/state/execution.go b/state/execution.go index b86620f87..2f0b47e60 100644 --- a/state/execution.go +++ b/state/execution.go @@ -2,7 +2,6 @@ package state import ( "bytes" - "strings" "github.com/tendermint/basecoin/types" . "github.com/tendermint/go-common" @@ -94,9 +93,8 @@ func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp } // Validate call address - if strings.HasPrefix(string(tx.Address), "gov/") { - // This is a gov call. - } else { + plugin := state.GetPlugin(tx.Address) + if plugin != nil { return tmsp.ErrBaseUnknownAddress.AppendLog(Fmt("Unrecognized address %X", tx.Address)) } @@ -105,16 +103,21 @@ func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp inAcc.Sequence += 1 inAcc.Balance -= tx.Input.Amount state.SetCheckAccount(tx.Input.Address, inAcc.Sequence, inAcc.Balance) + inAccCopy := inAcc.Copy() // If this is AppendTx, actually save accounts - if !isCheckTx { - state.SetAccount(tx.Input.Address, inAcc) - // NOTE: value is dangling. - // XXX: don't just give it back - inAcc.Balance += value - // TODO: logic. - // TODO: persist - // state.SetAccount(tx.Input.Address, inAcc) + if isCheckTx { + return tmsp.OK + } + + // Run the tx. + cache := NewAccountCache(state) + cache.SetAccount(tx.Input.Address, inAcc) + gas := int64(1) // TODO + ctx := types.NewCallContext(cache, inAcc, value, &gas) + res = plugin.CallTx(ctx, tx.Data) + if res.IsOK() { + cache.Sync() log.Info("Successful execution") // Fire events /* @@ -127,9 +130,14 @@ func ExecTx(state *State, tx types.Tx, isCheckTx bool, evc events.Fireable) tmsp evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventDataTx{tx, ret, exception}) } */ + } else { + log.Info("CallTx failed", "error", res) + // Just return the value and return. + // TODO: return gas? + inAccCopy.Balance += value + state.SetAccount(tx.Input.Address, inAccCopy) } - - return tmsp.OK + return res default: return tmsp.ErrBaseEncodingError.SetLog("Unknown tx type") diff --git a/state/state.go b/state/state.go index 3d2794c79..7d3309a00 100644 --- a/state/state.go +++ b/state/state.go @@ -11,6 +11,7 @@ type State struct { chainID string eyesCli *eyes.Client checkCache map[string]checkAccount + plugins map[string]types.Plugin LastBlockHeight uint64 LastBlockHash []byte @@ -37,6 +38,14 @@ func (s *State) GetChainID() string { return s.chainID } +func (s *State) RegisterPlugin(addr []byte, plugin types.Plugin) { + s.plugins[string(addr)] = plugin +} + +func (s *State) GetPlugin(addr []byte) types.Plugin { + return s.plugins[string(addr)] +} + //---------------------------------------- // CheckTx state diff --git a/types/account.go b/types/account.go index d17083968..67d601605 100644 --- a/types/account.go +++ b/types/account.go @@ -40,5 +40,11 @@ type AccountGetter interface { type AccountGetterSetter interface { GetAccount(addr []byte) *Account - SetAccount(acc *Account) + SetAccount(addr []byte, acc *Account) +} + +type AccountCacher interface { + GetAccount(addr []byte) *Account + SetAccount(addr []byte, acc *Account) + Sync() } diff --git a/types/native.go b/types/native.go deleted file mode 100644 index 7f0f9048e..000000000 --- a/types/native.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -type Plugin func(ags AccountGetterSetter, - caller *Account, - input []byte, - gas *int64) (result []byte, err error) diff --git a/types/plugin.go b/types/plugin.go new file mode 100644 index 000000000..6cc1b8f0a --- /dev/null +++ b/types/plugin.go @@ -0,0 +1,28 @@ +package types + +import ( + tmsp "github.com/tendermint/tmsp/types" +) + +// Value is any floating value. It must be given to someone. +// Gas is a pointer to remainig gas. Decrement as you go, +// if any gas is left the user is +type Plugin interface { + CallTx(ctx CallContext, txBytes []byte) tmsp.Result +} + +type CallContext struct { + Cache AccountCacher + Caller *Account + Value int64 + Gas *int64 +} + +func NewCallContext(cache AccountCacher, caller *Account, value int64, gas *int64) CallContext { + return CallContext{ + Cache: cache, + Caller: caller, + Value: value, + Gas: gas, + } +}