Write dispatcher, change SetOption arguments

This commit is contained in:
Ethan Frey 2017-07-04 12:22:06 +02:00
parent 9cd303d1fd
commit fcab8ac901
13 changed files with 215 additions and 34 deletions

View File

@ -1,6 +1,9 @@
package app package app
import ( import (
"fmt"
"strings"
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin" "github.com/tendermint/basecoin"
eyes "github.com/tendermint/merkleeyes/client" eyes "github.com/tendermint/merkleeyes/client"
@ -15,8 +18,8 @@ import (
) )
const ( const (
PluginNameBase = "base" ModuleNameBase = "base"
ChainKey = "base/chain_id" ChainKey = "chain_id"
) )
type Basecoin struct { type Basecoin struct {
@ -66,18 +69,26 @@ func (app *Basecoin) Info() abci.ResponseInfo {
// ABCI::SetOption // ABCI::SetOption
func (app *Basecoin) SetOption(key string, value string) string { func (app *Basecoin) SetOption(key string, value string) string {
if key == ChainKey { module, prefix := splitKey(key)
app.state.SetChainID(value) if module == ModuleNameBase {
return "Success" return app.setBaseOption(prefix, value)
} }
log, err := app.handler.SetOption(app.logger, app.state, key, value) log, err := app.handler.SetOption(app.logger, app.state, module, prefix, value)
if err == nil { if err == nil {
return log return log
} }
return "Error: " + err.Error() return "Error: " + err.Error()
} }
func (app *Basecoin) setBaseOption(key, value string) string {
if key == ChainKey {
app.state.SetChainID(value)
return "Success"
}
return fmt.Sprintf("Error: unknown base option: %s", key)
}
// ABCI::DeliverTx // ABCI::DeliverTx
func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result { func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
tx, err := basecoin.LoadTx(txBytes) tx, err := basecoin.LoadTx(txBytes)
@ -178,3 +189,13 @@ func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) {
// } // }
return return
} }
// Splits the string at the first '/'.
// if there are none, assign default module ("base").
func splitKey(key string) (string, string) {
if strings.Contains(key, "/") {
keyParts := strings.SplitN(key, "/", 2)
return keyParts[0], keyParts[1]
}
return ModuleNameBase, key
}

View File

@ -58,7 +58,7 @@ func (at *appTest) getTx(seq int, coins types.Coins) basecoin.Tx {
func (at *appTest) acc2app(acc types.Account) { func (at *appTest) acc2app(acc types.Account) {
accBytes, err := json.Marshal(acc) accBytes, err := json.Marshal(acc)
require.Nil(at.t, err) require.Nil(at.t, err)
res := at.app.SetOption("base/account", string(accBytes)) res := at.app.SetOption("coin/account", string(accBytes))
require.EqualValues(at.t, res, "Success") require.EqualValues(at.t, res, "Success")
} }
@ -137,7 +137,7 @@ func TestSetOption(t *testing.T) {
accIn := types.MakeAcc("input0").Account accIn := types.MakeAcc("input0").Account
accsInBytes, err := json.Marshal(accIn) accsInBytes, err := json.Marshal(accIn)
assert.Nil(err) assert.Nil(err)
res = app.SetOption("base/account", string(accsInBytes)) res = app.SetOption("coin/account", string(accsInBytes))
require.EqualValues(res, "Success") require.EqualValues(res, "Success")
// make sure it is set correctly, with some balance // make sure it is set correctly, with some balance
@ -165,7 +165,7 @@ func TestSetOption(t *testing.T) {
} }
] ]
}` }`
res = app.SetOption("base/account", unsortAcc) res = app.SetOption("coin/account", unsortAcc)
require.EqualValues(res, "Success") require.EqualValues(res, "Success")
coins, err = getAddr(unsortAddr, app.state) coins, err = getAddr(unsortAddr, app.state)
@ -234,3 +234,19 @@ func TestQuery(t *testing.T) {
}) })
assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit") assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit")
} }
func TestSplitKey(t *testing.T) {
assert := assert.New(t)
prefix, suffix := splitKey("foo/bar")
assert.EqualValues("foo", prefix)
assert.EqualValues("bar", suffix)
prefix, suffix = splitKey("foobar")
assert.EqualValues("base", prefix)
assert.EqualValues("foobar", suffix)
prefix, suffix = splitKey("some/complex/issue")
assert.EqualValues("some", prefix)
assert.EqualValues("complex/issue", suffix)
}

View File

@ -19,7 +19,7 @@ func (app *Basecoin) LoadGenesis(path string) error {
// set accounts // set accounts
for _, acct := range genDoc.AppOptions.Accounts { for _, acct := range genDoc.AppOptions.Accounts {
_ = app.SetOption("base/account", string(acct)) _ = app.SetOption("coin/account", string(acct))
} }
// set plugin options // set plugin options

View File

@ -24,6 +24,7 @@ var (
errWrongChain = rawerr.New("Wrong chain for tx") errWrongChain = rawerr.New("Wrong chain for tx")
errUnknownTxType = rawerr.New("Tx type unknown") errUnknownTxType = rawerr.New("Tx type unknown")
errInvalidFormat = rawerr.New("Invalid format") errInvalidFormat = rawerr.New("Invalid format")
errUnknownModule = rawerr.New("Unknown module")
) )
func ErrUnknownTxType(tx basecoin.Tx) TMError { func ErrUnknownTxType(tx basecoin.Tx) TMError {
@ -44,6 +45,14 @@ func IsInvalidFormatErr(err error) bool {
return IsSameError(errInvalidFormat, err) return IsSameError(errInvalidFormat, err)
} }
func ErrUnknownModule(mod string) TMError {
w := errors.Wrap(errUnknownModule, mod)
return WithCode(w, abci.CodeType_UnknownRequest)
}
func IsUnknownModuleErr(err error) bool {
return IsSameError(errUnknownModule, err)
}
func ErrInternal(msg string) TMError { func ErrInternal(msg string) TMError {
return New(msg, abci.CodeType_InternalError) return New(msg, abci.CodeType_InternalError)
} }

View File

@ -47,14 +47,14 @@ func (c DeliverFunc) DeliverTx(ctx Context, store types.KVStore, tx Tx) (Result,
} }
type SetOptioner interface { type SetOptioner interface {
SetOption(l log.Logger, store types.KVStore, key, value string) (string, error) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error)
} }
// SetOptionFunc (like http.HandlerFunc) is a shortcut for making wrapers // SetOptionFunc (like http.HandlerFunc) is a shortcut for making wrapers
type SetOptionFunc func(log.Logger, types.KVStore, string, string) (string, error) type SetOptionFunc func(log.Logger, types.KVStore, string, string, string) (string, error)
func (c SetOptionFunc) SetOption(l log.Logger, store types.KVStore, key, value string) (string, error) { func (c SetOptionFunc) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) {
return c(l, store, key, value) return c(l, store, module, key, value)
} }
// Result captures any non-error abci result // Result captures any non-error abci result
@ -83,6 +83,6 @@ func (_ NopDeliver) DeliverTx(Context, types.KVStore, Tx) (r Result, e error) {
type NopOption struct{} type NopOption struct{}
func (_ NopOption) SetOption(log.Logger, types.KVStore, string, string) (string, error) { func (_ NopOption) SetOption(log.Logger, types.KVStore, string, string, string) (string, error) {
return "", nil return "", nil
} }

View File

@ -80,8 +80,11 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoi
return basecoin.Result{}, nil return basecoin.Result{}, nil
} }
func (h Handler) SetOption(l log.Logger, store types.KVStore, key, value string) (log string, err error) { func (h Handler) SetOption(l log.Logger, store types.KVStore, module, key, value string) (log string, err error) {
if key == "base/account" { if module != NameCoin {
return "", errors.ErrUnknownModule(module)
}
if key == "account" {
var acc GenesisAccount var acc GenesisAccount
err = data.FromJSON([]byte(value), &acc) err = data.FromJSON([]byte(value), &acc)
if err != nil { if err != nil {

View File

@ -172,8 +172,7 @@ func TestSetOption(t *testing.T) {
// some sample settings // some sample settings
pk := crypto.GenPrivKeySecp256k1().Wrap() pk := crypto.GenPrivKeySecp256k1().Wrap()
addr := pk.PubKey().Address() addr := pk.PubKey().Address()
actor := basecoin.Actor{App: "coin", Address: addr} actor := basecoin.Actor{App: stack.NameSigs, Address: addr}
// actor2 := basecoin.Actor{App: "foo", Address: addr}
someCoins := types.Coins{{"atom", 123}} someCoins := types.Coins{{"atom", 123}}
otherCoins := types.Coins{{"eth", 11}} otherCoins := types.Coins{{"eth", 11}}
@ -198,13 +197,13 @@ func TestSetOption(t *testing.T) {
l := log.NewNopLogger() l := log.NewNopLogger()
for i, tc := range cases { for i, tc := range cases {
store := types.NewMemKVStore() store := types.NewMemKVStore()
key := "base/account" key := "account"
// set the options // set the options
for j, gen := range tc.init { for j, gen := range tc.init {
value, err := json.Marshal(gen) value, err := json.Marshal(gen)
require.Nil(err, "%d,%d: %+v", i, j, err) require.Nil(err, "%d,%d: %+v", i, j, err)
_, err = h.SetOption(l, store, key, string(value)) _, err = h.SetOption(l, store, NameCoin, key, string(value))
require.Nil(err) require.Nil(err)
} }

View File

@ -94,6 +94,7 @@ type Account struct {
} }
func loadAccount(store types.KVStore, key []byte) (acct Account, err error) { func loadAccount(store types.KVStore, key []byte) (acct Account, err error) {
// fmt.Printf("load: %X\n", key)
data := store.Get(key) data := store.Get(key)
if len(data) == 0 { if len(data) == 0 {
return acct, ErrNoAccount() return acct, ErrNoAccount()
@ -107,6 +108,7 @@ func loadAccount(store types.KVStore, key []byte) (acct Account, err error) {
} }
func storeAccount(store types.KVStore, key []byte, acct Account) error { func storeAccount(store types.KVStore, key []byte, acct Account) error {
// fmt.Printf("store: %X\n", key)
bin := wire.BinaryBytes(acct) bin := wire.BinaryBytes(acct)
store.Set(key, bin) store.Set(key, bin)
return nil // real stores can return error... return nil // real stores can return error...

94
stack/dispatcher.go Normal file
View File

@ -0,0 +1,94 @@
package stack
import (
"fmt"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/types"
)
const (
NameDispatcher = "disp"
)
// Dispatcher grabs a bunch of Dispatchables and groups them into one Handler.
//
// It will route tx to the proper locations and also allows them to call each
// other synchronously through the same tx methods.
type Dispatcher struct {
routes map[string]Dispatchable
}
func NewDispatcher(routes ...Dispatchable) *Dispatcher {
d := &Dispatcher{}
d.AddRoutes(routes...)
return d
}
var _ basecoin.Handler = new(Dispatcher)
// AddRoutes registers all these dispatchable choices under their subdomains
//
// Panics on attempt to double-register a route name, as this is a configuration error.
// Should I retrun an error instead?
func (d *Dispatcher) AddRoutes(routes ...Dispatchable) {
for _, r := range routes {
name := r.Name()
if _, ok := d.routes[name]; ok {
panic(fmt.Sprintf("%s already registered with dispatcher", name))
}
d.routes[name] = r
}
}
func (d *Dispatcher) Name() string {
return NameDispatcher
}
func (d *Dispatcher) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
}
// TODO: callback
return r.CheckTx(ctx, store, tx, nil)
}
func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
}
// TODO: callback
return r.DeliverTx(ctx, store, tx, nil)
}
func (d *Dispatcher) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) {
r, err := d.lookupModule(module)
if err != nil {
return "", err
}
// TODO: callback
return r.SetOption(l, store, module, key, value, nil)
}
func (d *Dispatcher) lookupTx(tx basecoin.Tx) (Dispatchable, error) {
// TODO
name := "foo"
r, ok := d.routes[name]
if !ok {
return nil, errors.ErrUnknownTxType(tx)
}
return r, nil
}
func (d *Dispatcher) lookupModule(name string) (Dispatchable, error) {
r, ok := d.routes[name]
if !ok {
return nil, errors.ErrUnknownModule(name)
}
return r, nil
}

View File

@ -39,13 +39,13 @@ func (d DeliverMiddleFunc) DeliverTx(ctx basecoin.Context, store types.KVStore,
} }
type SetOptionMiddle interface { type SetOptionMiddle interface {
SetOption(l log.Logger, store types.KVStore, key, value string, next basecoin.SetOptioner) (string, error) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error)
} }
type SetOptionMiddleFunc func(log.Logger, types.KVStore, string, string, basecoin.SetOptioner) (string, error) type SetOptionMiddleFunc func(log.Logger, types.KVStore, string, string, string, basecoin.SetOptioner) (string, error)
func (c SetOptionMiddleFunc) SetOption(l log.Logger, store types.KVStore, key, value string, next basecoin.SetOptioner) (string, error) { func (c SetOptionMiddleFunc) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
return c(l, store, key, value, next) return c(l, store, module, key, value, next)
} }
// holders // holders
@ -63,6 +63,43 @@ func (_ PassDeliver) DeliverTx(ctx basecoin.Context, store types.KVStore, tx bas
type PassOption struct{} type PassOption struct{}
func (_ PassOption) SetOption(l log.Logger, store types.KVStore, key, value string, next basecoin.SetOptioner) (string, error) { func (_ PassOption) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
return next.SetOption(l, store, key, value) return next.SetOption(l, store, module, key, value)
}
// Dispatchable is like middleware, except the meaning of "next" is different.
// Whereas in the middleware, it is the next handler that we should pass the same tx into,
// for dispatchers, it is a dispatcher, which it can use to
type Dispatchable interface {
Middleware
AssertDispatcher()
}
// WrapHandler turns a basecoin.Handler into a Dispatchable interface
func WrapHandler(h basecoin.Handler) Dispatchable {
return wrapped{h}
}
type wrapped struct {
h basecoin.Handler
}
var _ Dispatchable = wrapped{}
func (w wrapped) AssertDispatcher() {}
func (w wrapped) Name() string {
return w.h.Name()
}
func (w wrapped) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, _ basecoin.Checker) (basecoin.Result, error) {
return w.h.CheckTx(ctx, store, tx)
}
func (w wrapped) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, _ basecoin.Deliver) (basecoin.Result, error) {
return w.h.DeliverTx(ctx, store, tx)
}
func (w wrapped) SetOption(l log.Logger, store types.KVStore, module, key, value string, _ basecoin.SetOptioner) (string, error) {
return w.h.SetOption(l, store, module, key, value)
} }

View File

@ -50,12 +50,12 @@ func (_ Logger) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin
return return
} }
func (_ Logger) SetOption(l log.Logger, store types.KVStore, key, value string, next basecoin.SetOptioner) (string, error) { func (_ Logger) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
start := time.Now() start := time.Now()
res, err := next.SetOption(l, store, key, value) res, err := next.SetOption(l, store, module, key, value)
delta := time.Now().Sub(start) delta := time.Now().Sub(start)
// TODO: log the value being set also? // TODO: log the value being set also?
l = l.With("duration", micros(delta)).With("key", key) l = l.With("duration", micros(delta)).With("mod", module).With("key", key)
if err == nil { if err == nil {
l.Info("SetOption", "log", res) l.Info("SetOption", "log", res)
} else { } else {

View File

@ -39,8 +39,8 @@ func (m *middleware) DeliverTx(ctx basecoin.Context, store types.KVStore, tx bas
return m.middleware.DeliverTx(ctx, store, tx, next) return m.middleware.DeliverTx(ctx, store, tx, next)
} }
func (m *middleware) SetOption(l log.Logger, store types.KVStore, key, value string) (string, error) { func (m *middleware) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) {
return m.middleware.SetOption(l, store, key, value, m.next) return m.middleware.SetOption(l, store, module, key, value, m.next)
} }
// Stack is the entire application stack // Stack is the entire application stack

View File

@ -41,13 +41,13 @@ func (_ Recovery) DeliverTx(ctx basecoin.Context, store types.KVStore, tx baseco
return next.DeliverTx(ctx, store, tx) return next.DeliverTx(ctx, store, tx)
} }
func (_ Recovery) SetOption(l log.Logger, store types.KVStore, key, value string, next basecoin.SetOptioner) (log string, err error) { func (_ Recovery) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (log string, err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
err = normalizePanic(r) err = normalizePanic(r)
} }
}() }()
return next.SetOption(l, store, key, value) return next.SetOption(l, store, module, key, value)
} }
// normalizePanic makes sure we can get a nice TMError (with stack) out of it // normalizePanic makes sure we can get a nice TMError (with stack) out of it