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:
+11
-4
@@ -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{}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user