WIP: refactor

Refactor

* No more decorators, but rather types.AntiHandler
* No more handlers, but rather types.MsgHandler
* Ability to pass "stores" in NewXYZHandler()
* Coins live in types, and Accounts have coins
* Coinstore -> bank
This commit is contained in:
Jae Kwon
2018-01-12 19:17:17 -08:00
parent 620bdf409f
commit ba2b4f0f21
28 changed files with 698 additions and 717 deletions
+11 -4
View File
@@ -7,7 +7,8 @@ import (
// Account is a standard account using a sequence number for replay protection
// and a pubkey for authentication.
type Account interface {
Address() crypto.Address
GetAddress() crypto.Address
SetAddress(crypto.Address) error // errors if already set.
GetPubKey() crypto.PubKey // can return nil.
SetPubKey(crypto.PubKey) error
@@ -15,13 +16,19 @@ type Account interface {
GetSequence() int64
SetSequence(int64) error
GetCoins() Coins
SetCoins(Coins)
Get(key interface{}) (value interface{}, err error)
Set(key interface{}, value interface{}) error
}
// AccountStore indexes accounts by address.
type AccountStore interface {
NewAccountWithAddress(addr crypto.Address) Account
GetAccount(addr crypto.Address) Account
SetAccount(acc Account)
NewAccountWithAddress(ctx Context, addr crypto.Address) Account
GetAccount(ctx Context, addr crypto.Address) Account
SetAccount(ctx Context, acc Account)
}
// new(AccountStoreKey) is a capabilities key.
type AccountStoreKey struct{}
+14
View File
@@ -0,0 +1,14 @@
package types
// A generic codec for a fixed type.
type Codec interface {
// Returns a prototype (empty) object.
Prototype() interface{}
// Encodes the object.
Encode(o interface{}) ([]byte, error)
// Decodes an object.
Decode(bz []byte) (interface{}, error)
}
+31 -41
View File
@@ -7,48 +7,21 @@ import (
abci "github.com/tendermint/abci/types"
)
// TODO: Add a default logger.
/*
A note on Context security:
The intent of Context is for it to be an immutable object that can be
cloned and updated cheaply with WithValue() and passed forward to the
next decorator or handler. For example,
```golang
func Decorator(ctx Context, tx Tx, next Handler) Result {
// Clone and update context with new kv pair.
ctx2 := ctx.WithValueSDK(key, value)
// Call the next decorator/handler.
res := next(ctx2, ms, tx)
func MsgHandler(ctx Context, tx Tx) Result {
...
ctx = ctx.WithValue(key, value)
...
}
```
While `ctx` and `ctx2`'s shallow values haven't changed, it's
possible that slices or addressable struct fields have been modified
by the call to `next(...)`.
This is generally undesirable because it prevents a decorator from
rolling back all side effects--which is the intent of immutable
Context's and store cache-wraps.
While well-written decorators wouldn't mutate any mutable context
values, a malicious or buggy plugin can create unwanted side-effects,
so it is highly advised for users of Context to only set immutable
values. To help enforce this contract, we require values to be
certain primitive types, a cloner, or a CacheWrapper.
If an outer (higher) decorator wants to know what an inner decorator
had set on the context, it can consult `context.GetOp(ver int64) Op`,
which retrieves the ver'th opertion to the context, globally since `NewContext()`.
TODO: Add a default logger.
*/
type Context struct {
context.Context
pst *thePast
@@ -57,12 +30,13 @@ type Context struct {
// it's probably not what you want to do.
}
func NewContext(header abci.Header, isCheckTx bool, txBytes []byte) Context {
func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte) Context {
c := Context{
Context: context.Background(),
pst: newThePast(),
gen: 0,
}
c = c.withMultiStore(ms)
c = c.withBlockHeader(header)
c = c.withBlockHeight(header.Height)
c = c.withChainID(header.ChainID)
@@ -72,7 +46,7 @@ func NewContext(header abci.Header, isCheckTx bool, txBytes []byte) Context {
}
//----------------------------------------
// Get a value
// Getting a value
func (c Context) Value(key interface{}) interface{} {
value := c.Context.Value(key)
@@ -85,10 +59,15 @@ func (c Context) Value(key interface{}) interface{} {
return value
}
//----------------------------------------
// Set a value
// KVStore fetches a KVStore from the MultiStore.
func (c Context) KVStore(key *KVStoreKey) KVStore {
return c.multiStore().GetKVStore(key)
}
func (c Context) WithValueUnsafe(key interface{}, value interface{}) Context {
//----------------------------------------
// With* (setting a value)
func (c Context) WithValue(key interface{}, value interface{}) Context {
return c.withValue(key, value)
}
@@ -104,6 +83,10 @@ func (c Context) WithProtoMsg(key interface{}, value proto.Message) Context {
return c.withValue(key, value)
}
func (c Context) WithMultiStore(key *MultiStoreKey, ms MultiStore) Context {
return c.withValue(key, ms)
}
func (c Context) WithString(key interface{}, value string) Context {
return c.withValue(key, value)
}
@@ -135,18 +118,24 @@ func (c Context) withValue(key interface{}, value interface{}) Context {
}
//----------------------------------------
// Our extensions
// Values that require no key.
type contextKey int // local to the context module
const (
contextKeyBlockHeader contextKey = iota
contextKeyMultiStore contextKey = iota
contextKeyBlockHeader
contextKeyBlockHeight
contextKeyChainID
contextKeyIsCheckTx
contextKeyTxBytes
)
// NOTE: Do not expose MultiStore, to require the store key.
func (c Context) multiStore() MultiStore {
return c.Value(contextKeyMultiStore).(MultiStore)
}
func (c Context) BlockHeader() abci.Header {
return c.Value(contextKeyBlockHeader).(abci.Header)
}
@@ -167,8 +156,9 @@ func (c Context) TxBytes() []byte {
return c.Value(contextKeyTxBytes).([]byte)
}
func (c Context) KVStore(key interface{}) KVStore {
return c.Value(key).(KVStore)
// Unexposed to prevent overriding.
func (c Context) withMultiStore(ms MultiStore) Context {
return c.withValue(contextKeyMultiStore, ms)
}
// Unexposed to prevent overriding.
-56
View File
@@ -1,56 +0,0 @@
package types
// A Decorator executes before/during/after a handler to enhance functionality.
type Decorator func(ctx Context, tx Tx, next Handler) Result
// Return a decorated handler
func Decorate(dec Decorator, next Handler) Handler {
return func(ctx Context, tx Tx) Result {
return dec(ctx, tx, next)
}
}
//----------------------------------------
/*
Helper to construct a decorated Handler from a stack of Decorators
(first-decorator-first-call as in Python @decorators) , w/ Handler provided
last for syntactic sugar of ChainDecorators().WithHandler()
Usage:
handler := sdk.ChainDecorators(
decorator1,
decorator2,
...,
).WithHandler(myHandler)
*/
func ChainDecorators(decorators ...Decorator) stack {
return stack{
decs: decorators,
}
}
// No need to expose this.
type stack struct {
decs []Decorator
}
// WithHandler sets the final handler for the stack and
// returns the decoratored Handler.
func (s stack) WithHandler(handler Handler) Handler {
if handler == nil {
panic("WithHandler() requires a non-nil Handler")
}
return build(s.decs, handler)
}
// build wraps each decorator around the next, so that
// the last in the list is closest to the handler
func build(stack []Decorator, end Handler) Handler {
if len(stack) == 0 {
return end
}
return Decorate(stack[0], build(stack[1:], end))
}
-38
View File
@@ -1,38 +0,0 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDecorate(t *testing.T) {
var calledDec1, calledDec2, calledHandler bool
dec1 := func(ctx Context, ms MultiStore, tx Tx, next Handler) Result {
calledDec1 = true
next(ctx, ms, tx)
return Result{}
}
dec2 := func(ctx Context, ms MultiStore, tx Tx, next Handler) Result {
calledDec2 = true
next(ctx, ms, tx)
return Result{}
}
handler := func(ctx Context, ms MultiStore, tx Tx) Result {
calledHandler = true
return Result{}
}
decoratedHandler := ChainDecorators(dec1, dec2).WithHandler(handler)
var ctx Context
var ms MultiStore
var tx Tx
decoratedHandler(ctx, ms, tx)
assert.True(t, calledDec1)
assert.True(t, calledDec2)
assert.True(t, calledHandler)
}
+2 -2
View File
@@ -1,5 +1,5 @@
package types
// Handler handles both ABCI DeliverTx and CheckTx requests.
// Iff ABCI.CheckTx, ctx.IsCheckTx() returns true.
type Handler func(ctx Context, tx Tx) Result
type AnteHandler func(ctx Context, tx Tx) (Result, abort bool)
-15
View File
@@ -1,15 +0,0 @@
package types
import crypto "github.com/tendermint/go-crypto"
type Model interface {
Address() crypto.Address
Get(key interface{}) interface{}
Set(key interface{}, value interface{})
}
type ModelStore interface {
Load(addr crypto.Address) Model
Store(m Model)
}
+10 -6
View File
@@ -80,12 +80,6 @@ type CommitStoreLoader func(id CommitID) (CommitStore, error)
// KVStore is a simple interface to get/set data
type KVStore interface {
// TODO Not yet implemented.
// CreateSubKVStore(key *storeKey) (KVStore, error)
// TODO Not yet implemented.
// GetSubKVStore(key *storeKey) KVStore
// Get returns nil iff key doesn't exist. Panics on nil key.
Get(key []byte) []byte
@@ -107,6 +101,13 @@ type KVStore interface {
// Start must be greater than end, or the Iterator is invalid.
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
ReverseIterator(start, end []byte) Iterator
// TODO Not yet implemented.
// CreateSubKVStore(key *storeKey) (KVStore, error)
// TODO Not yet implemented.
// GetSubKVStore(key *storeKey) KVStore
}
// dbm.DB implements KVStore so we can CacheKVStore it.
@@ -162,3 +163,6 @@ func (cid CommitID) IsZero() bool {
func (cid CommitID) String() string {
return fmt.Sprintf("CommitID{%v:%X}", cid.Hash, cid.Version)
}
// new(KVStoreKey) is a capabilities key.
type KVStoreKey struct{}
+18 -3
View File
@@ -4,6 +4,10 @@ import crypto "github.com/tendermint/go-crypto"
type Msg interface {
// Return the message type.
// Must be alphanumeric or empty.
Type() string
// Get some property of the Msg.
Get(key interface{}) (value interface{})
@@ -17,15 +21,19 @@ type Msg interface {
// Signers returns the addrs of signers that must sign.
// CONTRACT: All signatures must be present to be valid.
// CONTRACT: Returns addrs in some deterministic order.
Signers() []crypto.Address
GetSigners() []crypto.Address
}
type Tx interface {
Msg
// The address that pays the base fee for this message. The fee is
// deducted before the Msg is processed.
GetFeePayer() crypto.Address
// Get the canonical byte representation of the Tx.
// Includes any signatures (or empty slots).
TxBytes() []byte
GetTxBytes() []byte
// Signatures returns the signature of signers who signed the Msg.
// CONTRACT: Length returned is same as length of
@@ -34,5 +42,12 @@ type Tx interface {
// CONTRACT: If the signature is missing (ie the Msg is
// invalid), then the corresponding signature is
// .Empty().
Signatures() []StdSignature
GetSignatures() []StdSignature
}
type StdTx struct {
Msg
Signatures []StdSignature
}
type TxDecoder func(txBytes []byte) (Tx, error)