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
import (
"fmt"
"strings"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin"
eyes "github.com/tendermint/merkleeyes/client"
@ -15,8 +18,8 @@ import (
)
const (
PluginNameBase = "base"
ChainKey = "base/chain_id"
ModuleNameBase = "base"
ChainKey = "chain_id"
)
type Basecoin struct {
@ -66,18 +69,26 @@ func (app *Basecoin) Info() abci.ResponseInfo {
// ABCI::SetOption
func (app *Basecoin) SetOption(key string, value string) string {
if key == ChainKey {
app.state.SetChainID(value)
return "Success"
module, prefix := splitKey(key)
if module == ModuleNameBase {
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 {
return log
}
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
func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
tx, err := basecoin.LoadTx(txBytes)
@ -178,3 +189,13 @@ func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) {
// }
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) {
accBytes, err := json.Marshal(acc)
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")
}
@ -137,7 +137,7 @@ func TestSetOption(t *testing.T) {
accIn := types.MakeAcc("input0").Account
accsInBytes, err := json.Marshal(accIn)
assert.Nil(err)
res = app.SetOption("base/account", string(accsInBytes))
res = app.SetOption("coin/account", string(accsInBytes))
require.EqualValues(res, "Success")
// 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")
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")
}
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
for _, acct := range genDoc.AppOptions.Accounts {
_ = app.SetOption("base/account", string(acct))
_ = app.SetOption("coin/account", string(acct))
}
// set plugin options

View File

@ -24,6 +24,7 @@ var (
errWrongChain = rawerr.New("Wrong chain for tx")
errUnknownTxType = rawerr.New("Tx type unknown")
errInvalidFormat = rawerr.New("Invalid format")
errUnknownModule = rawerr.New("Unknown module")
)
func ErrUnknownTxType(tx basecoin.Tx) TMError {
@ -44,6 +45,14 @@ func IsInvalidFormatErr(err error) bool {
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 {
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 {
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
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) {
return c(l, store, key, value)
func (c SetOptionFunc) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) {
return c(l, store, module, key, value)
}
// 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{}
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
}

View File

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

View File

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

View File

@ -94,6 +94,7 @@ type Account struct {
}
func loadAccount(store types.KVStore, key []byte) (acct Account, err error) {
// fmt.Printf("load: %X\n", key)
data := store.Get(key)
if len(data) == 0 {
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 {
// fmt.Printf("store: %X\n", key)
bin := wire.BinaryBytes(acct)
store.Set(key, bin)
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 {
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) {
return c(l, store, key, value, next)
func (c SetOptionMiddleFunc) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
return c(l, store, module, key, value, next)
}
// holders
@ -63,6 +63,43 @@ func (_ PassDeliver) DeliverTx(ctx basecoin.Context, store types.KVStore, tx bas
type PassOption struct{}
func (_ PassOption) SetOption(l log.Logger, store types.KVStore, key, value string, next basecoin.SetOptioner) (string, error) {
return next.SetOption(l, store, key, value)
func (_ PassOption) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
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
}
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()
res, err := next.SetOption(l, store, key, value)
res, err := next.SetOption(l, store, module, key, value)
delta := time.Now().Sub(start)
// 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 {
l.Info("SetOption", "log", res)
} 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)
}
func (m *middleware) SetOption(l log.Logger, store types.KVStore, key, value string) (string, error) {
return m.middleware.SetOption(l, store, key, value, m.next)
func (m *middleware) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) {
return m.middleware.SetOption(l, store, module, key, value, m.next)
}
// 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)
}
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() {
if r := recover(); r != nil {
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