Moved the handlers from stack into modules

This commit is contained in:
Ethan Frey
2017-07-06 16:00:54 +02:00
parent 768427dcc0
commit a047e210fa
18 changed files with 104 additions and 72 deletions
+77
View File
@@ -0,0 +1,77 @@
package auth
import (
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
)
//nolint
const (
NameSigs = "sigs"
)
// Signatures parses out go-crypto signatures and adds permissions to the
// context for use inside the application
type Signatures struct {
stack.PassOption
}
// Name of the module - fulfills Middleware interface
func (Signatures) Name() string {
return NameSigs
}
var _ stack.Middleware = Signatures{}
// SigPerm takes the binary address from PubKey.Address and makes it an Actor
func SigPerm(addr []byte) basecoin.Actor {
return basecoin.NewActor(NameSigs, addr)
}
// Signed allows us to use txs.OneSig and txs.MultiSig (and others??)
type Signed interface {
basecoin.TxLayer
Signers() ([]crypto.PubKey, error)
}
// CheckTx verifies the signatures are correct - fulfills Middlware interface
func (Signatures) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
sigs, tnext, err := getSigners(tx)
if err != nil {
return res, err
}
ctx2 := addSigners(ctx, sigs)
return next.CheckTx(ctx2, store, tnext)
}
// DeliverTx verifies the signatures are correct - fulfills Middlware interface
func (Signatures) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
sigs, tnext, err := getSigners(tx)
if err != nil {
return res, err
}
ctx2 := addSigners(ctx, sigs)
return next.DeliverTx(ctx2, store, tnext)
}
func addSigners(ctx basecoin.Context, sigs []crypto.PubKey) basecoin.Context {
perms := make([]basecoin.Actor, len(sigs))
for i, s := range sigs {
perms[i] = SigPerm(s.Address())
}
// add the signers to the context and continue
return ctx.WithPermissions(perms...)
}
func getSigners(tx basecoin.Tx) ([]crypto.PubKey, basecoin.Tx, error) {
stx, ok := tx.Unwrap().(Signed)
if !ok {
return nil, basecoin.Tx{}, errors.ErrUnauthorized()
}
sig, err := stx.Signers()
return sig, stx.Next(), err
}
+97
View File
@@ -0,0 +1,97 @@
package auth
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/txs"
)
func TestSignatureChecks(t *testing.T) {
assert := assert.New(t)
// generic args
ctx := stack.NewContext("test-chain", log.NewNopLogger())
store := state.NewMemKVStore()
raw := txs.NewRaw([]byte{1, 2, 3, 4})
// let's make some keys....
priv1 := crypto.GenPrivKeyEd25519().Wrap()
actor1 := SigPerm(priv1.PubKey().Address())
priv2 := crypto.GenPrivKeySecp256k1().Wrap()
actor2 := SigPerm(priv2.PubKey().Address())
// test cases to make sure signature checks are solid
cases := []struct {
useMultiSig bool
keys []crypto.PrivKey
check basecoin.Actor
valid bool
}{
// test with single sigs
{false, []crypto.PrivKey{priv1}, actor1, true},
{false, []crypto.PrivKey{priv1}, actor2, false},
{false, []crypto.PrivKey{priv2}, actor2, true},
{false, []crypto.PrivKey{}, actor2, false},
// same with multi sigs
{true, []crypto.PrivKey{priv1}, actor1, true},
{true, []crypto.PrivKey{priv1}, actor2, false},
{true, []crypto.PrivKey{priv2}, actor2, true},
{true, []crypto.PrivKey{}, actor2, false},
// make sure both match on a multisig
{true, []crypto.PrivKey{priv1, priv2}, actor1, true},
{true, []crypto.PrivKey{priv1, priv2}, actor2, true},
}
for i, tc := range cases {
idx := strconv.Itoa(i)
// make the stack check for the given permission
app := stack.New(
Signatures{},
stack.CheckMiddleware{Required: tc.check},
).Use(stack.OKHandler{})
var tx basecoin.Tx
// this does the signing as needed
if tc.useMultiSig {
mtx := txs.NewMulti(raw)
for _, k := range tc.keys {
err := txs.Sign(mtx, k)
assert.Nil(err, "%d: %+v", i, err)
}
tx = mtx.Wrap()
} else {
otx := txs.NewSig(raw)
for _, k := range tc.keys {
err := txs.Sign(otx, k)
assert.Nil(err, "%d: %+v", i, err)
}
tx = otx.Wrap()
}
_, err := app.CheckTx(ctx, store, tx)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
} else {
assert.NotNil(err, idx)
}
_, err = app.DeliverTx(ctx, store, tx)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
} else {
assert.NotNil(err, idx)
}
}
}
+56
View File
@@ -0,0 +1,56 @@
package base
import (
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/txs"
)
//nolint
const (
NameChain = "chan"
)
// Chain enforces that this tx was bound to the named chain
type Chain struct {
stack.PassOption
}
// Name of the module - fulfills Middleware interface
func (Chain) Name() string {
return NameChain
}
var _ stack.Middleware = Chain{}
// CheckTx makes sure we are on the proper chain - fulfills Middlware interface
func (c Chain) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
stx, err := c.checkChain(ctx.ChainID(), tx)
if err != nil {
return res, err
}
return next.CheckTx(ctx, store, stx)
}
// DeliverTx makes sure we are on the proper chain - fulfills Middlware interface
func (c Chain) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
stx, err := c.checkChain(ctx.ChainID(), tx)
if err != nil {
return res, err
}
return next.DeliverTx(ctx, store, stx)
}
// checkChain makes sure the tx is a txs.Chain and
func (c Chain) checkChain(chainID string, tx basecoin.Tx) (basecoin.Tx, error) {
ctx, ok := tx.Unwrap().(*txs.Chain)
if !ok {
return tx, errors.ErrNoChain()
}
if ctx.ChainID != chainID {
return tx, errors.ErrWrongChain(ctx.ChainID)
}
return ctx.Tx, nil
}
+66
View File
@@ -0,0 +1,66 @@
package base
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/txs"
)
func TestChain(t *testing.T) {
assert := assert.New(t)
msg := "got it"
chainID := "my-chain"
raw := txs.NewRaw([]byte{1, 2, 3, 4})
cases := []struct {
tx basecoin.Tx
valid bool
errorMsg string
}{
{txs.NewChain(chainID, raw), true, ""},
{txs.NewChain("someone-else", raw), false, "someone-else"},
{raw, false, "No chain id provided"},
}
// generic args here...
ctx := stack.NewContext(chainID, log.NewNopLogger())
store := state.NewMemKVStore()
// build the stack
ok := stack.OKHandler{Log: msg}
app := stack.New(Chain{}).Use(ok)
for idx, tc := range cases {
i := strconv.Itoa(idx)
// make sure check returns error, not a panic crash
res, err := app.CheckTx(ctx, store, tc.tx)
if tc.valid {
assert.Nil(err, "%d: %+v", idx, err)
assert.Equal(msg, res.Log, i)
} else {
if assert.NotNil(err, i) {
assert.Contains(err.Error(), tc.errorMsg, i)
}
}
// make sure deliver returns error, not a panic crash
res, err = app.DeliverTx(ctx, store, tc.tx)
if tc.valid {
assert.Nil(err, "%d: %+v", idx, err)
assert.Equal(msg, res.Log, i)
} else {
if assert.NotNil(err, i) {
assert.Contains(err.Error(), tc.errorMsg, i)
}
}
}
}
+76
View File
@@ -0,0 +1,76 @@
package base
import (
"time"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
)
// nolint
const (
NameLogger = "lggr"
)
// Logger catches any panics and returns them as errors instead
type Logger struct{}
// Name of the module - fulfills Middleware interface
func (Logger) Name() string {
return NameLogger
}
var _ stack.Middleware = Logger{}
// CheckTx logs time and result - fulfills Middlware interface
func (Logger) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
start := time.Now()
res, err = next.CheckTx(ctx, store, tx)
delta := time.Now().Sub(start)
// TODO: log some info on the tx itself?
l := ctx.With("duration", micros(delta))
if err == nil {
l.Debug("CheckTx", "log", res.Log)
} else {
l.Info("CheckTx", "err", err)
}
return
}
// DeliverTx logs time and result - fulfills Middlware interface
func (Logger) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
start := time.Now()
res, err = next.DeliverTx(ctx, store, tx)
delta := time.Now().Sub(start)
// TODO: log some info on the tx itself?
l := ctx.With("duration", micros(delta))
if err == nil {
l.Info("DeliverTx", "log", res.Log)
} else {
l.Error("DeliverTx", "err", err)
}
return
}
// SetOption logs time and result - fulfills Middlware interface
func (Logger) SetOption(l log.Logger, store state.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
start := time.Now()
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("mod", module).With("key", key)
if err == nil {
l.Info("SetOption", "log", res)
} else {
l.Error("SetOption", "err", err)
}
return res, err
}
// micros returns how many microseconds passed in a call
func micros(d time.Duration) int {
return int(d.Seconds() * 1000000)
}
+74
View File
@@ -0,0 +1,74 @@
package base
import (
"strings"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/txs"
)
//nolint
const (
NameMultiplexer = "mplx"
)
// Multiplexer grabs a MultiTx and sends them sequentially down the line
type Multiplexer struct {
stack.PassOption
}
// Name of the module - fulfills Middleware interface
func (Multiplexer) Name() string {
return NameMultiplexer
}
var _ stack.Middleware = Multiplexer{}
// CheckTx splits the input tx and checks them all - fulfills Middlware interface
func (Multiplexer) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
if mtx, ok := tx.Unwrap().(*txs.MultiTx); ok {
return runAll(ctx, store, mtx.Txs, next.CheckTx)
}
return next.CheckTx(ctx, store, tx)
}
// DeliverTx splits the input tx and checks them all - fulfills Middlware interface
func (Multiplexer) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
if mtx, ok := tx.Unwrap().(*txs.MultiTx); ok {
return runAll(ctx, store, mtx.Txs, next.DeliverTx)
}
return next.DeliverTx(ctx, store, tx)
}
func runAll(ctx basecoin.Context, store state.KVStore, txs []basecoin.Tx, next basecoin.CheckerFunc) (res basecoin.Result, err error) {
// store all results, unless anything errors
rs := make([]basecoin.Result, len(txs))
for i, stx := range txs {
rs[i], err = next(ctx, store, stx)
if err != nil {
return
}
}
// now combine the results into one...
return combine(rs), nil
}
// combines all data bytes as a go-wire array.
// joins all log messages with \n
func combine(all []basecoin.Result) basecoin.Result {
datas := make([]data.Bytes, len(all))
logs := make([]string, len(all))
for i, r := range all {
datas[i] = r.Data
logs[i] = r.Log
}
return basecoin.Result{
Data: wire.BinaryBytes(datas),
Log: strings.Join(logs, "\n"),
}
}
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/state"
)
@@ -98,7 +98,7 @@ func (h Handler) SetOption(l log.Logger, store state.KVStore, module, key, value
return "", ErrInvalidAddress()
}
// this sets the permission for a public key signature, use that app
actor := stack.SigPerm(addr)
actor := auth.SigPerm(addr)
err = storeAccount(store, h.MakeKey(actor), acc.ToAccount())
if err != nil {
return "", err
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
)
@@ -172,7 +173,7 @@ func TestSetOption(t *testing.T) {
// some sample settings
pk := crypto.GenPrivKeySecp256k1().Wrap()
addr := pk.PubKey().Address()
actor := basecoin.Actor{App: stack.NameSigs, Address: addr}
actor := auth.SigPerm(addr)
someCoins := Coins{{"atom", 123}}
otherCoins := Coins{{"eth", 11}}
+2 -2
View File
@@ -1,11 +1,11 @@
package coin
import (
"github.com/tendermint/basecoin/modules/auth"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
)
// AccountWithKey is a helper for tests, that includes and account
@@ -31,7 +31,7 @@ func (a *AccountWithKey) Address() []byte {
// Actor returns the basecoin actor associated with this account
func (a *AccountWithKey) Actor() basecoin.Actor {
return stack.SigPerm(a.Key.PubKey().Address())
return auth.SigPerm(a.Key.PubKey().Address())
}
// MakeOption returns a string to use with SetOption to initialize this account