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:
parent
620bdf409f
commit
ba2b4f0f21
9
TODO
Normal file
9
TODO
Normal file
@ -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
|
||||
145
app/app.go
145
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
|
||||
|
||||
40
app/router.go
Normal file
40
app/router.go
Normal file
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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}
|
||||
}
|
||||
|
||||
36
examples/basecoin/types/account.go
Normal file
36
examples/basecoin/types/account.go
Normal file
@ -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")
|
||||
}
|
||||
18
exports.go
18
exports.go
@ -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
|
||||
)
|
||||
@ -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
types/codec.go
Normal file
14
types/codec.go
Normal 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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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{}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
81
x/auth/ante.go
Normal file
81
x/auth/ante.go
Normal file
@ -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...
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
50
x/auth/store.go
Normal file
50
x/auth/store.go
Normal file
@ -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)
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
//nolint
|
||||
package coinstore
|
||||
package bank
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -1,4 +1,4 @@
|
||||
package coinstore
|
||||
package bank
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
51
x/bank/store.go
Normal file
51
x/bank/store.go
Normal file
@ -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
|
||||
}
|
||||
165
x/bank/tx.go
Normal file
165
x/bank/tx.go
Normal file
@ -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
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package coinstore
|
||||
package bank
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user