From ba2b4f0f21dfdde8b9596d1002bf86bd5e9f1f05 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 12 Jan 2018 11:49:53 -0800 Subject: [PATCH] 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 --- TODO | 9 ++ app/app.go | 145 +++++++++++-------- app/router.go | 40 ++++++ errors/errors.go | 19 ++- examples/basecoin/main.go | 90 +++++------- examples/basecoin/types/account.go | 36 +++++ exports.go | 18 --- types/account.go | 15 +- types/codec.go | 14 ++ types/context.go | 72 ++++------ types/decorators.go | 56 -------- types/decorators_test.go | 38 ----- types/handler.go | 4 +- types/model.go | 15 -- types/store.go | 16 ++- types/tx_msg.go | 21 ++- x/auth/account.go | 68 +++------ x/auth/ante.go | 81 +++++++++++ x/auth/context.go | 31 ++-- x/auth/decorator.go | 59 -------- x/auth/store.go | 50 +++++++ x/{coinstore => bank}/errors.go | 2 +- x/{coinstore => bank}/handler.go | 2 +- x/bank/store.go | 51 +++++++ x/bank/tx.go | 165 +++++++++++++++++++++ x/{coinstore => bank}/tx_test.go | 2 +- x/coinstore/store.go | 74 ---------- x/coinstore/tx.go | 222 ----------------------------- 28 files changed, 698 insertions(+), 717 deletions(-) create mode 100644 TODO create mode 100644 app/router.go create mode 100644 examples/basecoin/types/account.go delete mode 100644 exports.go create mode 100644 types/codec.go delete mode 100644 types/decorators.go delete mode 100644 types/decorators_test.go delete mode 100644 types/model.go create mode 100644 x/auth/ante.go delete mode 100644 x/auth/decorator.go create mode 100644 x/auth/store.go rename x/{coinstore => bank}/errors.go (99%) rename x/{coinstore => bank}/handler.go (97%) create mode 100644 x/bank/store.go create mode 100644 x/bank/tx.go rename x/{coinstore => bank}/tx_test.go (99%) delete mode 100644 x/coinstore/store.go delete mode 100644 x/coinstore/tx.go diff --git a/TODO b/TODO new file mode 100644 index 0000000000..427a299698 --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ + +* global state dumper. + for developer to list accounts, etc. + e.g. what does the world look like? + cmd cli. + +* something that can list transactions ... + make all decorators actually use the logger + so you can see all the txs and see what's going on diff --git a/app/app.go b/app/app.go index 4581f95007..dda114c3ce 100644 --- a/app/app.go +++ b/app/app.go @@ -26,6 +26,18 @@ type App struct { // Main (uncached) state ms types.CommitMultiStore + // Unmarshal []byte into types.Tx + txDecoder types.TxDecoder + + // Ante handler for fee and auth. + defaultAnteHandler types.AnteHandler + + // Handle any kind of message. + router Router + + //-------------------- + // Volatile + // CheckTx state, a cache-wrap of `.ms`. msCheck types.CacheMultiStore @@ -35,22 +47,18 @@ type App struct { // Current block header header abci.Header - // Unmarshal []byte into types.Tx - txParser TxParser - - // Handler for CheckTx and DeliverTx. - handler types.Handler - - // Cached validator changes from DeliverTx + // Cached validator changes from DeliverTx. valUpdates []abci.Validator } var _ abci.Application = &App{} -func NewApp(name string) *App { +func NewApp(name string, ms CommitMultiStore) *App { return &App{ - name: name, logger: makeDefaultLogger(), + name: name, + ms: ms, + router: NewRouter(), } } @@ -58,26 +66,24 @@ func (app *App) Name() string { return app.name } -func (app *App) SetCommitMultiStore(ms types.CommitMultiStore) { - app.ms = ms +func (app *App) SetTxDecoder(txDecoder types.TxDecoder) { + app.txDecoder = txDecoder } -/* -SetBeginBlocker -SetEndBlocker -SetInitStater +func (app *App) SetDefaultAnteHandler(ah types.AnteHandler) { + app.defaultAnteHandler = ah +} + +func (app *App) Router() Router { + return app.router +} + +/* TODO consider: +func (app *App) SetBeginBlocker(...) {} +func (app *App) SetEndBlocker(...) {} +func (app *App) SetInitStater(...) {} */ -type TxParser func(txBytes []byte) (types.Tx, error) - -func (app *App) SetTxParser(txParser TxParser) { - app.txParser = txParser -} - -func (app *App) SetHandler(handler types.Handler) { - app.handler = handler -} - func (app *App) LoadLatestVersion() error { app.ms.LoadLatestVersion() return app.initFromStore() @@ -179,23 +185,8 @@ func (app *App) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBl // Implements ABCI func (app *App) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) { - // Initialize arguments to Handler. - var isCheckTx = true - var ctx = types.NewContext(app.header, isCheckTx, txBytes) - var tx types.Tx + result := app.runTx(true, txBytes) - var err error - tx, err = app.txParser(txBytes) - if err != nil { - return abci.ResponseCheckTx{ - Code: 1, // TODO - } - } - - // Run the handler. - var result = app.handler(ctx, app.msCheck, tx) - - // Tell the blockchain engine (i.e. Tendermint). return abci.ResponseCheckTx{ Code: result.Code, Data: result.Data, @@ -207,33 +198,21 @@ func (app *App) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) { }, Tags: result.Tags, } + } // Implements ABCI func (app *App) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) { - // Initialize arguments to Handler. - var isCheckTx = false - var ctx = types.NewContext(app.header, isCheckTx, txBytes) - var tx types.Tx - - var err error - tx, err = app.txParser(txBytes) - if err != nil { - return abci.ResponseDeliverTx{ - Code: 1, // TODO - } - } - - // Run the handler. - var result = app.handler(ctx, app.msDeliver, tx) + result := app.runTx(false, txBytes) // After-handler hooks. if result.Code == abci.CodeTypeOK { app.valUpdates = append(app.valUpdates, result.ValidatorUpdates...) } else { - // Even though the Code is not OK, there will be some side effects, - // like those caused by fee deductions or sequence incrementations. + // Even though the Code is not OK, there will be some side + // effects, like those caused by fee deductions or sequence + // incrementations. } // Tell the blockchain engine (i.e. Tendermint). @@ -247,6 +226,56 @@ func (app *App) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) { } } +func (app *App) runTx(isCheckTx bool, txBytes []byte) (result types.Result) { + + // Handle any panics. + defer func() { + if r := recover(); r != nil { + result = types.Result{ + Code: 1, // TODO + Log: fmt.Sprintf("Recovered: %v\n", r), + } + } + }() + + var store types.MultiStore + if isCheckTx { + store = app.msCheck + } else { + store = app.msDeliver + } + + // Initialize arguments to Handler. + var ctx = types.NewContext( + store, + app.header, + isCheckTx, + txBytes, + ) + + // Decode the Tx. + var err error + tx, err = app.txDecoder(txBytes) + if err != nil { + return types.Result{ + Code: 1, // TODO + } + } + + // Run the ante handler. + ctx, result, abort := app.defaultAnteHandler(ctx, tx) + if isCheckTx || abort { + return result + } + + // Match and run route. + msgType := tx.Type() + handler := app.router.Route(msgType) + result = handler(ctx, tx) + + return result +} + // Implements ABCI func (app *App) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { res.ValidatorUpdates = app.valUpdates diff --git a/app/router.go b/app/router.go new file mode 100644 index 0000000000..c90170cbcb --- /dev/null +++ b/app/router.go @@ -0,0 +1,40 @@ +package app + +type Router interface { + AddRoute(r string, h Handler) + Route(path string) (h Handler) +} + +type route struct { + r string + h Handler +} + +type router struct { + routes []route +} + +func NewRouter() router { + return router{ + routes: make([]route), + } +} + +var isAlpha = regexp.MustCompile(`^[a-zA-Z]+$`).MatchString + +func (rtr router) AddRoute(r string, h Handler) { + if !isAlpha(r) { + panic("route expressions can only contain alphanumeric characters") + } + rtr.routes = append(rtr.routes, route{r, h}) +} + +// TODO handle expressive matches. +func (rtr router) Route(path string) (h Handler) { + for _, route := range rtr.routes { + if route.r == path { + return route.h + } + } + return nil +} diff --git a/errors/errors.go b/errors/errors.go index 3bee90113c..2350242366 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -7,12 +7,13 @@ import ( const ( // ABCI Response Codes // Base SDK reserves 0 ~ 99. - CodeInternalError uint32 = 1 - CodeTxParseError = 2 - CodeBadNonce = 3 - CodeUnauthorized = 4 - CodeInsufficientFunds = 5 - CodeUnknownRequest = 6 + CodeInternalError uint32 = 1 + CodeTxParseError = 2 + CodeBadNonce = 3 + CodeUnauthorized = 4 + CodeInsufficientFunds = 5 + CodeUnknownRequest = 6 + CodeUnrecognizedAddress = 7 ) // NOTE: Don't stringer this, we'll put better messages in later. @@ -30,6 +31,8 @@ func CodeToDefaultLog(code uint32) string { return "Insufficent funds" case CodeUnknownRequest: return "Unknown request" + case CodeUnrecognizeAddress: + return "Unrecognized address" default: return fmt.Sprintf("Unknown code %d", code) } @@ -63,6 +66,10 @@ func UnknownRequest(log string) *sdkError { return newSDKError(CodeUnknownRequest, log) } +func UnrecognizedAddress(log string) *sdkError { + return newSDKError(CodeUnrecognizedAddress, log) +} + //---------------------------------------- // ABCIError & sdkError diff --git a/examples/basecoin/main.go b/examples/basecoin/main.go index a96cf9bafc..e7bd230021 100644 --- a/examples/basecoin/main.go +++ b/examples/basecoin/main.go @@ -5,75 +5,64 @@ import ( "fmt" "os" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" "github.com/tendermint/abci/server" + "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" - "github.com/cosmos/cosmos-sdk/app" - "github.com/cosmos/cosmos-sdk/store" - "github.com/cosmos/cosmos-sdk/types" - acm "github.com/cosmos/cosmos-sdk/x/account" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/coinstore" + bcm "github.com/cosmos/cosmos-sdk/examples/basecoin/types" ) func main() { - app := app.NewApp("basecoin") + // First, create the Application. + app := sdk.NewApp("basecoin") + // Create the underlying leveldb datastore which will + // persist the Merkle tree inner & leaf nodes. db, err := dbm.NewGoLevelDB("basecoin", "basecoin-data") if err != nil { fmt.Println(err) os.Exit(1) } - // create CommitStoreLoader + // Create CommitStoreLoader. cacheSize := 10000 numHistory := int64(100) mainLoader := store.NewIAVLStoreLoader(db, cacheSize, numHistory) ibcLoader := store.NewIAVLStoreLoader(db, cacheSize, numHistory) // The key to access the main KVStore. - var mainKey = storeKey("main") - var ibcKey = storeKey("ibc") + var mainStoreKey = new(KVStoreKey) + var ibcStoreKey = new(KVStoreKey) // Create MultiStore multiStore := store.NewCommitMultiStore(db) - multiStore.SetSubstoreLoader(mainKey, mainLoader) - multiStore.SetSubstoreLoader(ibcKey, ibcLoader) + multiStore.SetSubstoreLoader("main", mainStoreKey, mainLoader) + multiStore.SetSubstoreLoader("ibc", ibcStoreKey, ibcLoader) + app.SetCommitMultiStore(multiStore) - // XXX - var appAccountCodec AccountCodec = nil + // Set Tx decoder + app.SetTxDecoder(decodeTx) - // Create Handler - handler := types.ChainDecorators( - recover.Decorator(), - logger.Decorator(), - auth.Decorator(appAccountCodec), - fees.Decorator(mainKey), - rollbackDecorator(), // XXX define. - ibc.Decorator(ibcKey), // Handle IBC messages. - pos.Decorator(mainKey), // Handle staking messages. - gov.Decorator(mainKey), // Handle governance messages. - coins.Decorator(mainKey), // Handle coinstore messages. - ).WithHandler(func(ctx types.context, tx Tx) Result { - /* - switch tx.(type) { - case CustomTx1: ... - case CustomTx2: ... - } - */ - }) + var accStore = auth.NewAccountStore(mainStoreKey, bcm.AppAccountCodec{}) + var authAnteHandler = auth.NewAnteHandler(accStore) + + // Handle charging fees and checking signatures. + app.SetDefaultAnteHandler(authAnteHandler) + + // Add routes to App. + app.Router().AddRoute("bank", bank.NewHandler(accStore)) // TODO: load genesis // TODO: InitChain with validators - // accounts := acm.NewAccountStore(multiStore.GetKVStore("main")) + // accounts := auth.NewAccountStore(multiStore.GetKVStore("main")) // TODO: set the genesis accounts - // Set everything on the app and load latest - app.SetCommitMultiStore(multiStore) - app.SetTxParser(txParser) - app.SetHandler(handler) + // Load the stores. if err := app.LoadLatestVersion(); err != nil { fmt.Println(err) os.Exit(1) @@ -98,22 +87,13 @@ func main() { //---------------------------------------- // Misc. -func txParser(txBytes []byte) (types.Tx, error) { - var tx coinstore.SendTx - err := json.Unmarshal(txBytes, &tx) +func registerMsgs() { + wire.RegisterInterface((*types.Msg), nil) + wire.RegisterConcrete((*bank.SendMsg), nil) +} + +func decodeTx(txBytes []byte) (types.Tx, error) { + var tx = sdk.StdTx{} + err := wire.UnmarshalBinary(txBytes, &tx) return tx, err } - -// an unexported (private) key which no module could know of unless -// it was passed in from the app. -type storeKey struct { - writeable bool - name string -} - -func newStoreKey(name string) storeKey { - return storeKey{true, name} -} -func (s storeKey) ReadOnly() storeKey { - return storeKey{false, s.name} -} diff --git a/examples/basecoin/types/account.go b/examples/basecoin/types/account.go new file mode 100644 index 0000000000..f56c4000ac --- /dev/null +++ b/examples/basecoin/types/account.go @@ -0,0 +1,36 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/x/auth" +) + +type AppAccount struct { + auth.BaseAccount + + // Custom extensions for this application. + Name string +} + +func (acc AppAccount) GetName() string { + return acc.Name +} + +func (acc *AppAccount) SetName(name string) { + acc.Name = name +} + +//---------------------------------------- + +type AppAccountCodec struct{} + +func (_ AppAccountCodec) Prototype() interface{} { + return AppAccount{} +} + +func (_ AppAccountCodec) Encode(o interface{}) (bz []byte, err error) { + panic("not yet implemented") +} + +func (_ AppAccountCodec) Decode(bz []byte) (o interface{}, err error) { + panic("not yet implemented") +} diff --git a/exports.go b/exports.go deleted file mode 100644 index ea2ab91a01..0000000000 --- a/exports.go +++ /dev/null @@ -1,18 +0,0 @@ -package sdk - -import ( - "github.com/cosmos/cosmos-sdk/store" - types "github.com/cosmos/cosmos-sdk/types" -) - -type ( - // Type aliases for the cosmos-sdk/types module. We keep all of them in - // types/* but they are all meant to be imported as - // "github.com/cosmos/cosmos-sdk". So, add all of them. - Handler = types.Handler - Context = types.Context - Decorator = types.Decorator - - // Type aliases for other modules. - MultiStore = store.MultiStore -) diff --git a/types/account.go b/types/account.go index 0addded4a8..3fc326f6ae 100644 --- a/types/account.go +++ b/types/account.go @@ -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{} diff --git a/types/codec.go b/types/codec.go new file mode 100644 index 0000000000..950aed260b --- /dev/null +++ b/types/codec.go @@ -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) +} diff --git a/types/context.go b/types/context.go index 542a837720..4a0879a866 100644 --- a/types/context.go +++ b/types/context.go @@ -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. diff --git a/types/decorators.go b/types/decorators.go deleted file mode 100644 index e2bcb806ba..0000000000 --- a/types/decorators.go +++ /dev/null @@ -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)) -} diff --git a/types/decorators_test.go b/types/decorators_test.go deleted file mode 100644 index f04ee016e9..0000000000 --- a/types/decorators_test.go +++ /dev/null @@ -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) -} diff --git a/types/handler.go b/types/handler.go index 5825a34e41..3450bf275a 100644 --- a/types/handler.go +++ b/types/handler.go @@ -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) diff --git a/types/model.go b/types/model.go deleted file mode 100644 index 4f0a87c560..0000000000 --- a/types/model.go +++ /dev/null @@ -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) -} diff --git a/types/store.go b/types/store.go index 11d75c128f..2d7d77f70a 100644 --- a/types/store.go +++ b/types/store.go @@ -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{} diff --git a/types/tx_msg.go b/types/tx_msg.go index e9549abe83..b60393e667 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -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) diff --git a/x/auth/account.go b/x/auth/account.go index cf0c54549d..a300ea8a60 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -13,76 +13,52 @@ import ( // BaseAccount - coin account structure type BaseAccount struct { - address crypto.Address - coins coin.Coins - pubKey crypto.PubKey - sequence int64 -} - -func NewBaseAccountWithAddress(addr crypto.Address) *BaseAccount { - return &BaseAccount{ - address: addr, - } -} - -// BaseAccountWire is the account structure used for serialization -type BaseAccountWire struct { Address crypto.Address `json:"address"` Coins coin.Coins `json:"coins"` - PubKey crypto.PubKey `json:"public_key"` // can't conflict with PubKey() + PubKey crypto.PubKey `json:"public_key"` Sequence int64 `json:"sequence"` } -func (acc *BaseAccount) MarshalJSON() ([]byte, error) { - return json.Marshal(BaseAccountWire{ - Address: acc.address, - Coins: acc.coins, - PubKey: acc.pubKey, - Sequence: acc.sequence, - }) -} - -func (acc *BaseAccount) UnmarshalJSON(bz []byte) error { - accWire := new(BaseAccountWire) - err := json.Unmarshal(bz, accWire) - if err != nil { - return err +func NewBaseAccountWithAddress(addr crypto.Address) BaseAccount { + return BaseAccount{ + Address: addr, } - acc.address = accWire.Address - acc.coins = accWire.Coins - acc.pubKey = accWire.PubKey - acc.sequence = accWire.Sequence - return nil } // Implements Account -func (acc *BaseAccount) Get(key interface{}) (value interface{}, err error) { - switch key.(type) { - case string: - } - return nil, nil +func (acc BaseAccount) Get(key interface{}) (value interface{}, err error) { + panic("not implemented yet") } // Implements Account func (acc *BaseAccount) Set(key interface{}, value interface{}) error { - switch key.(type) { - case string: - } - return nil + panic("not implemented yet") } // Implements Account -func (acc *BaseAccount) Address() crypto.Address { - // TODO: assert address == pubKey.Address() +func (acc BaseAccount) GetAddress() crypto.Address { return acc.address } // Implements Account -func (acc *BaseAccount) GetPubKey() crypto.PubKey { +func (acc *BaseAccount) SetAddress(addr crypto.Address) error { + if acc.address != "" { + return errors.New("cannot override BaseAccount address") + } + acc.address = addr + return nil +} + +// Implements Account +func (acc BaseAccount) GetPubKey() crypto.PubKey { return acc.pubKey } +// Implements Account func (acc *BaseAccount) SetPubKey(pubKey crypto.PubKey) error { + if acc.pubKey != "" { + return errors.New("cannot override BaseAccount pubkey") + } acc.pubKey = pubKey return nil } diff --git a/x/auth/ante.go b/x/auth/ante.go new file mode 100644 index 0000000000..55502fb90c --- /dev/null +++ b/x/auth/ante.go @@ -0,0 +1,81 @@ +package auth + +import ( + "github.com/cosmos/cosmos-sdk/types" +) + +func NewAnteHandler(store types.AccountStore) types.AnteHandler { + return func( + ctx types.Context, tx types.Tx, + ) (newCtx types.Context, res types.Result, abort bool) { + + // Deduct the fee from the fee payer. + // This is done first because it only + // requires fetching 1 account. + payerAddr := tx.GetFeePayer() + payerAcc := store.GetAccount(ctx, payerAddr) + if payerAcc == nil { + return ctx, Result{ + Code: 1, // TODO + }, true + } + + payerAcc.Subtract + + // Ensure that signatures are correct. + var signerAddrs = tx.Signers() + var signerAccs = make([]types.Account, len(signerAddrs)) + var signatures = tx.Signatures() + + // Assert that there are signers. + if len(signatures) == 0 { + return ctx, types.Result{ + Code: 1, // TODO + }, true + } + if len(signatures) != len(signers) { + return ctx, types.Result{ + Code: 1, // TODO + }, true + } + + // Check each nonce and sig. + for i, sig := range signatures { + + var signerAcc = store.GetAccount(signers[i]) + signerAccs[i] = signerAcc + + // If no pubkey, set pubkey. + if acc.GetPubKey().Empty() { + err := acc.SetPubKey(sig.PubKey) + if err != nil { + return ctx, types.Result{ + Code: 1, // TODO + }, true + } + } + + // Check and incremenet sequence number. + seq := acc.GetSequence() + if seq != sig.Sequence { + return ctx, types.Result{ + Code: 1, // TODO + }, true + } + acc.SetSequence(seq + 1) + + // Check sig. + if !sig.PubKey.VerifyBytes(tx.SignBytes(), sig.Signature) { + return ctx, types.Result{ + Code: 1, // TODO + }, true + } + + // Save the account. + store.SetAccount(acc) + } + + ctx = WithSigners(ctx, signerAccs) + return ctx, types.Result{}, false // continue... + } +} diff --git a/x/auth/context.go b/x/auth/context.go index 07e86b7d67..101b135fbf 100644 --- a/x/auth/context.go +++ b/x/auth/context.go @@ -6,28 +6,37 @@ import ( /* - Usage: +Usage: - import "accounts" +var accountStore types.AccountStore - var acc accounts.Account +// Fetch all signer accounts. +addrs := tx.GetSigners() +signers := make([]types.Account, len(addrs)) +for i, addr := range addrs { + acc := accountStore.GetAccount(ctx) + signers[i] = acc +} +ctx = auth.SetSigners(ctx, signers) - accounts.SetAccount(ctx, acc) - acc2 := accounts.GetAccount(ctx) +// Get all signer accounts. +signers := auth.GetSigners(ctx) +for i, signer := range signers { + signer.Address() == tx.GetSigners()[i] +} */ type contextKey int // local to the auth module const ( - // A context key of the Account variety - contextKeyAccount contextKey = iota + contextKeySigners contextKey = iota ) -func SetAccount(ctx types.Context, account types.Account) types.Context { - return ctx.WithValueUnsafe(contextKeyAccount, account) +func WithSigners(ctx types.Context, accounts []types.Account) types.Context { + return ctx.WithValueUnsafe(contextKeySigners, accounts) } -func GetAccount(ctx types.Context) types.Account { - return ctx.Value(contextKeyAccount).(types.Account) +func GetSigners(ctx types.Context) []types.Account { + return ctx.Value(contextKeySigners).([]types.Account) } diff --git a/x/auth/decorator.go b/x/auth/decorator.go deleted file mode 100644 index 23966d7226..0000000000 --- a/x/auth/decorator.go +++ /dev/null @@ -1,59 +0,0 @@ -package auth - -import "github.com/cosmos/cosmos-sdk/types" - -func DecoratorFn(newAccountStore func(types.KVStore) types.AccountStore) types.Decorator { - return func(ctx types.Context, ms types.MultiStore, tx types.Tx, next types.Handler) types.Result { - - accountStore := newAccountStore(ms.GetKVStore("main")) - - signers := tx.Signers() - signatures := tx.Signatures() - - // assert len - if len(signatures) == 0 { - return types.Result{ - Code: 1, // TODO - } - } - if len(signatures) != len(signers) { - return types.Result{ - Code: 1, // TODO - } - } - - // check each nonce and sig - for i, sig := range signatures { - - // get account - acc := accountStore.GetAccount(signers[i]) - - // if no pubkey, set pubkey - if acc.GetPubKey().Empty() { - err := acc.SetPubKey(sig.PubKey) - if err != nil { - return types.Result{ - Code: 1, // TODO - } - } - } - - // check and incremenet sequence number - seq := acc.GetSequence() - if seq != sig.Sequence { - return types.Result{ - Code: 1, // TODO - } - } - acc.SetSequence(seq + 1) - - // check sig - if !sig.PubKey.VerifyBytes(tx.SignBytes(), sig.Signature) { - return types.Result{ - Code: 1, // TODO - } - } - } - return next(ctx, ms, tx) - } -} diff --git a/x/auth/store.go b/x/auth/store.go new file mode 100644 index 0000000000..2f428b6e14 --- /dev/null +++ b/x/auth/store.go @@ -0,0 +1,50 @@ +package auth + +import ( + "github.com/cosmos/cosmos-sdk/types" +) + +// Implements types.AccountStore +type accountStore struct { + key *types.KVStoreKey + codec types.Codec +} + +func NewAccountStore(key *types.KVStoreKey, codec types.Codec) accountStore { + return accountStore{ + key: key, + codec: codec, + } +} + +// Implements types.AccountStore +func (as accountStore) NewAccountWithAddress(ctx types.Context, addr crypto.Address) { + acc := as.codec.Prototype().(types.Account) + acc.SetAddress(addr) + return acc +} + +// Implements types.AccountStore +func (as accountStore) GetAccount(ctx types.Context, addr crypto.Address) types.Account { + store := ctx.KVStore(as.key) + bz := store.Get(addr) + if bz == nil { + return + } + o, err := as.codec.Decode(bz) + if err != nil { + panic(err) + } + return o.(types.Account) +} + +// Implements types.AccountStore +func (as accountStore) SetAccount(ctx types.Context, acc types.Account) { + addr := acc.GetAddress() + store := ctx.KVStore(as.key) + bz, err := as.codec.Encode(acc) + if err != nil { + panic(err) + } + store.Set(addr, bz) +} diff --git a/x/coinstore/errors.go b/x/bank/errors.go similarity index 99% rename from x/coinstore/errors.go rename to x/bank/errors.go index be2c0d996d..49abd54c3e 100644 --- a/x/coinstore/errors.go +++ b/x/bank/errors.go @@ -1,5 +1,5 @@ //nolint -package coinstore +package bank import ( "fmt" diff --git a/x/coinstore/handler.go b/x/bank/handler.go similarity index 97% rename from x/coinstore/handler.go rename to x/bank/handler.go index 2b7e8d7eca..3304413c4d 100644 --- a/x/coinstore/handler.go +++ b/x/bank/handler.go @@ -1,4 +1,4 @@ -package coinstore +package bank import ( "github.com/cosmos/cosmos-sdk/types" diff --git a/x/bank/store.go b/x/bank/store.go new file mode 100644 index 0000000000..c9e817188f --- /dev/null +++ b/x/bank/store.go @@ -0,0 +1,51 @@ +package bank + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/coin" + crypto "github.com/tendermint/go-crypto" +) + +// CoinStore manages transfers between accounts +type CoinStore struct { + store types.AccountStore +} + +// SubtractCoins subtracts amt from the coins at the addr. +func (cs CoinStore) SubtractCoins(ctx types.Context, addr crypto.Address, amt types.Coins) (types.Coins, error) { + acc, err := cs.store.GetAccount(ctx, addr) + if err != nil { + return amt, err + } else if acc == nil { + return amt, fmt.Errorf("Sending account (%s) does not exist", addr) + } + + coins := acc.GetCoins() + newCoins := coins.Minus(amt) + if !newCoins.IsNotNegative() { + return amt, ErrInsufficientCoins(fmt.Sprintf("%s < %s", coins, amt)) + } + + acc.SetCoins(newCoins) + cs.store.SetAccount(ctx, acc) + return newCoins, nil +} + +// AddCoins adds amt to the coins at the addr. +func (cs CoinStore) AddCoins(ctx types.Context, addr crypto.Address, amt types.Coins) (types.Coins, error) { + acc, err := cs.store.GetAccount(ctx, addr) + if err != nil { + return amt, err + } else if acc == nil { + acc = cs.store.NewAccountWithAddress(ctx, addr) + } + + coins := acc.GetCoins() + newCoins := coins.Plus(amt) + + acc.SetCoins(newCoins) + cs.store.SetAccount(ctx, acc) + return newCoins, nil +} diff --git a/x/bank/tx.go b/x/bank/tx.go new file mode 100644 index 0000000000..4639e585c9 --- /dev/null +++ b/x/bank/tx.go @@ -0,0 +1,165 @@ +package bank + +import ( + "encoding/json" + "fmt" + + crypto "github.com/tendermint/go-crypto" + + "github.com/cosmos/cosmos-sdk/types" +) + +// SendMsg - high level transaction of the coin module +type SendMsg struct { + Inputs []Input `json:"inputs"` + Outputs []Output `json:"outputs"` +} + +// NewSendMsg - construct arbitrary multi-in, multi-out send msg. +func NewSendMsg(in []Input, out []Output) types.Tx { + return SendMsg{Inputs: in, Outputs: out} +} + +// Implements Msg. +func (msg SendMsg) Type() string { return "bank" } // TODO: "bank/send" + +// Implements Msg. +func (msg SendMsg) ValidateBasic() error { + // this just makes sure all the inputs and outputs are properly formatted, + // not that they actually have the money inside + if len(msg.Inputs) == 0 { + return ErrNoInputs() + } + if len(msg.Outputs) == 0 { + return ErrNoOutputs() + } + // make sure all inputs and outputs are individually valid + var totalIn, totalOut types.Coins + for _, in := range msg.Inputs { + if err := in.ValidateBasic(); err != nil { + return err + } + totalIn = totalIn.Plus(in.Coins) + } + for _, out := range msg.Outputs { + if err := out.ValidateBasic(); err != nil { + return err + } + totalOut = totalOut.Plus(out.Coins) + } + // make sure inputs and outputs match + if !totalIn.IsEqual(totalOut) { + return ErrInvalidCoins(totalIn.String()) // TODO + } + return nil +} + +func (msg SendMsg) String() string { + return fmt.Sprintf("SendMsg{%v->%v}", msg.Inputs, msg.Outputs) +} + +// Implements Msg. +func (msg SendMsg) Get(key interface{}) (value interface{}) { + return nil +} + +// Implements Msg. +func (msg SendMsg) GetSignBytes() []byte { + b, err := json.Marshal(msg) // XXX: ensure some canonical form + if err != nil { + panic(err) + } + return b +} + +// Implements Msg. +func (msg SendMsg) GetSigners() []crypto.Address { + addrs := make([]crypto.Address, len(msg.Inputs)) + for i, in := range msg.Inputs { + addrs[i] = in.Address + } + return addrs +} + +//---------------------------------------- +// Input + +type Input struct { + Address crypto.Address `json:"address"` + Coins types.Coins `json:"coins"` + Sequence int64 `json:"sequence"` + + signature crypto.Signature +} + +// ValidateBasic - validate transaction input +func (in Input) ValidateBasic() error { + if len(in.Address) == 0 { + return ErrInvalidAddress(in.Address.String()) + } + if in.Sequence < 0 { + return ErrInvalidSequence(in.Sequence) + } + if !in.Coins.IsValid() { + return ErrInvalidCoins(in.Coins.String()) + } + if !in.Coins.IsPositive() { + return ErrInvalidCoins(in.Coins.String()) + } + return nil +} + +func (in Input) String() string { + return fmt.Sprintf("Input{%v,%v}", in.Address, in.Coins) +} + +// NewInput - create a transaction input, used with SendMsg +func NewInput(addr crypto.Address, coins types.Coins) Input { + input := Input{ + Address: addr, + Coins: coins, + } + return input +} + +// NewInputWithSequence - create a transaction input, used with SendMsg +func NewInputWithSequence(addr crypto.Address, coins types.Coins, seq int64) Input { + input := NewInput(addr, coins) + input.Sequence = seq + return input +} + +//---------------------------------------- +// Output + +type Output struct { + Address crypto.Address `json:"address"` + Coins types.Coins `json:"coins"` +} + +// ValidateBasic - validate transaction output +func (out Output) ValidateBasic() error { + if len(out.Address) == 0 { + return ErrInvalidAddress(out.Address.String()) + } + if !out.Coins.IsValid() { + return ErrInvalidCoins(out.Coins.String()) + } + if !out.Coins.IsPositive() { + return ErrInvalidCoins(out.Coins.String()) + } + return nil +} + +func (out Output) String() string { + return fmt.Sprintf("Output{%X,%v}", out.Address, out.Coins) +} + +// NewOutput - create a transaction output, used with SendMsg +func NewOutput(addr crypto.Address, coins types.Coins) Output { + output := Output{ + Address: addr, + Coins: coins, + } + return output +} diff --git a/x/coinstore/tx_test.go b/x/bank/tx_test.go similarity index 99% rename from x/coinstore/tx_test.go rename to x/bank/tx_test.go index c4627d487e..173fad8c97 100644 --- a/x/coinstore/tx_test.go +++ b/x/bank/tx_test.go @@ -1,4 +1,4 @@ -package coinstore +package bank import ( "testing" diff --git a/x/coinstore/store.go b/x/coinstore/store.go deleted file mode 100644 index 163e49041c..0000000000 --- a/x/coinstore/store.go +++ /dev/null @@ -1,74 +0,0 @@ -package coinstore - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/coin" - crypto "github.com/tendermint/go-crypto" -) - -type Coins = coin.Coins - -// Coinser can get and set coins -type Coinser interface { - GetCoins() Coins - SetCoins(Coins) -} - -// CoinStore manages transfers between accounts -type CoinStore struct { - types.AccountStore -} - -// SubtractCoins subtracts amt from the coins at the addr. -func (cs CoinStore) SubtractCoins(addr crypto.Address, amt Coins) (Coins, error) { - acc, err := cs.getCoinserAccount(addr) - if err != nil { - return amt, err - } else if acc == nil { - return amt, fmt.Errorf("Sending account (%s) does not exist", addr) - } - - coins := acc.GetCoins() - newCoins := coins.Minus(amt) - if !newCoins.IsNotNegative() { - return amt, ErrInsufficientCoins(fmt.Sprintf("%s < %s", coins, amt)) - } - - acc.SetCoins(newCoins) - cs.SetAccount(acc.(types.Account)) - return newCoins, nil -} - -// AddCoins adds amt to the coins at the addr. -func (cs CoinStore) AddCoins(addr crypto.Address, amt Coins) (Coins, error) { - acc, err := cs.getCoinserAccount(addr) - if err != nil { - return amt, err - } else if acc == nil { - acc = cs.AccountStore.NewAccountWithAddress(addr).(Coinser) - } - - coins := acc.GetCoins() - newCoins := coins.Plus(amt) - - acc.SetCoins(newCoins) - cs.SetAccount(acc.(types.Account)) - return newCoins, nil -} - -// get the account as a Coinser. if the account doesn't exist, return nil. -// if it's not a Coinser, return error. -func (cs CoinStore) getCoinserAccount(addr crypto.Address) (Coinser, error) { - _acc := cs.GetAccount(addr) - if _acc == nil { - return nil, nil - } - - acc, ok := _acc.(Coinser) - if !ok { - return nil, fmt.Errorf("Account %s is not a Coinser", addr) - } - return acc, nil -} diff --git a/x/coinstore/tx.go b/x/coinstore/tx.go deleted file mode 100644 index 399f2fc09f..0000000000 --- a/x/coinstore/tx.go +++ /dev/null @@ -1,222 +0,0 @@ -package coinstore - -import ( - "encoding/json" - "fmt" - - crypto "github.com/tendermint/go-crypto" - - "github.com/cosmos/cosmos-sdk/types" -) - -//----------------------------------------------------------------------------- - -// TxInput -type TxInput struct { - Address crypto.Address `json:"address"` - Coins types.Coins `json:"coins"` - Sequence int64 `json:"sequence"` - - signature crypto.Signature -} - -// ValidateBasic - validate transaction input -func (txIn TxInput) ValidateBasic() error { - if len(txIn.Address) == 0 { - return ErrInvalidAddress(txIn.Address.String()) - } - if txIn.Sequence < 0 { - return ErrInvalidSequence(txIn.Sequence) - } - if !txIn.Coins.IsValid() { - return ErrInvalidCoins(txIn.Coins.String()) - } - if !txIn.Coins.IsPositive() { - return ErrInvalidCoins(txIn.Coins.String()) - } - return nil -} - -func (txIn TxInput) String() string { - return fmt.Sprintf("TxInput{%v,%v}", txIn.Address, txIn.Coins) -} - -// NewTxInput - create a transaction input, used with SendTx -func NewTxInput(addr crypto.Address, coins types.Coins) TxInput { - input := TxInput{ - Address: addr, - Coins: coins, - } - return input -} - -// NewTxInputWithSequence - create a transaction input, used with SendTx -func NewTxInputWithSequence(addr crypto.Address, coins types.Coins, seq int64) TxInput { - input := NewTxInput(addr, coins) - input.Sequence = seq - return input -} - -//----------------------------------------------------------------------------- - -// TxOutput - expected coin movement output, used with SendTx -type TxOutput struct { - Address crypto.Address `json:"address"` - Coins types.Coins `json:"coins"` -} - -// ValidateBasic - validate transaction output -func (txOut TxOutput) ValidateBasic() error { - if len(txOut.Address) == 0 { - return ErrInvalidAddress(txOut.Address.String()) - } - if !txOut.Coins.IsValid() { - return ErrInvalidCoins(txOut.Coins.String()) - } - if !txOut.Coins.IsPositive() { - return ErrInvalidCoins(txOut.Coins.String()) - } - return nil -} - -func (txOut TxOutput) String() string { - return fmt.Sprintf("TxOutput{%X,%v}", txOut.Address, txOut.Coins) -} - -// NewTxOutput - create a transaction output, used with SendTx -func NewTxOutput(addr crypto.Address, coins types.Coins) TxOutput { - output := TxOutput{ - Address: addr, - Coins: coins, - } - return output -} - -//----------------------------------------------------------------------------- - -type CoinstoreTx interface { - Tx - AssertIsCoinstoreTx() -} - -var _ CoinstoreTx = (*SendTx)(nil) - -// SendTx - high level transaction of the coin module -type SendTx struct { - Inputs []TxInput `json:"inputs"` - Outputs []TxOutput `json:"outputs"` -} - -// Used to switch in the decorator to process all Coinstore txs. -func (tx SendTx) AssertIsCoinstoreTx() {} - -// ValidateBasic - validate the send transaction -func (tx SendTx) ValidateBasic() error { - // this just makes sure all the inputs and outputs are properly formatted, - // not that they actually have the money inside - if len(tx.Inputs) == 0 { - return ErrNoInputs() - } - if len(tx.Outputs) == 0 { - return ErrNoOutputs() - } - // make sure all inputs and outputs are individually valid - var totalIn, totalOut types.Coins - for _, in := range tx.Inputs { - if err := in.ValidateBasic(); err != nil { - return err - } - totalIn = totalIn.Plus(in.Coins) - } - for _, out := range tx.Outputs { - if err := out.ValidateBasic(); err != nil { - return err - } - totalOut = totalOut.Plus(out.Coins) - } - // make sure inputs and outputs match - if !totalIn.IsEqual(totalOut) { - return ErrInvalidCoins(totalIn.String()) // TODO - } - return nil -} - -func (tx SendTx) String() string { - return fmt.Sprintf("SendTx{%v->%v}", tx.Inputs, tx.Outputs) -} - -// NewSendTx - construct arbitrary multi-in, multi-out sendtx -func NewSendTx(in []TxInput, out []TxOutput) types.Tx { - return SendTx{Inputs: in, Outputs: out} -} - -// NewSendOneTx is a helper for the standard (?) case where there is exactly -// one sender and one recipient -func NewSendOneTx(sender, recipient crypto.Address, amount coin.Coins) types.Tx { - in := []TxInput{{Address: sender, Coins: amount}} - out := []TxOutput{{Address: recipient, Coins: amount}} - return SendTx{Inputs: in, Outputs: out} -} - -//------------------------ -// Implements types.Tx - -func (tx SendTx) Get(key interface{}) (value interface{}) { - switch k := key.(type) { - case string: - switch k { - case "key": - case "value": - } - } - return nil -} - -func (tx SendTx) SignBytes() []byte { - b, err := json.Marshal(tx) // XXX: ensure some canonical form - if err != nil { - panic(err) - } - return b -} - -func (tx SendTx) Signers() []crypto.Address { - addrs := make([]crypto.Address, len(tx.Inputs)) - for i, in := range tx.Inputs { - addrs[i] = in.Address - } - return addrs -} - -func (tx SendTx) TxBytes() []byte { - b, err := json.Marshal(struct { - Tx types.Tx `json:"tx"` - Signature []crypto.Signature `json:"signature"` - }{ - Tx: tx, - Signature: tx.signatures(), - }) - if err != nil { - panic(err) - } - return b -} - -func (tx SendTx) Signatures() []types.StdSignature { - stdSigs := make([]types.StdSignature, len(tx.Inputs)) - for i, in := range tx.Inputs { - stdSigs[i] = types.StdSignature{ - Signature: in.signature, - Sequence: in.Sequence, - } - } - return stdSigs -} - -func (tx SendTx) signatures() []crypto.Signature { - sigs := make([]crypto.Signature, len(tx.Inputs)) - for i, in := range tx.Inputs { - sigs[i] = in.signature - } - return sigs -}