WIP: refactor

Refactor

* No more decorators, but rather types.AntiHandler
* No more handlers, but rather types.MsgHandler
* Ability to pass "stores" in NewXYZHandler()
* Coins live in types, and Accounts have coins
* Coinstore -> bank
This commit is contained in:
Jae Kwon 2018-01-12 11:49:53 -08:00
parent 620bdf409f
commit ba2b4f0f21
28 changed files with 698 additions and 717 deletions

9
TODO Normal file
View 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

View File

@ -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
View 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
}

View File

@ -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

View File

@ -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}
}

View 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")
}

View File

@ -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
)

View File

@ -7,7 +7,8 @@ import (
// Account is a standard account using a sequence number for replay protection
// and a pubkey for authentication.
type Account interface {
Address() crypto.Address
GetAddress() crypto.Address
SetAddress(crypto.Address) error // errors if already set.
GetPubKey() crypto.PubKey // can return nil.
SetPubKey(crypto.PubKey) error
@ -15,13 +16,19 @@ type Account interface {
GetSequence() int64
SetSequence(int64) error
GetCoins() Coins
SetCoins(Coins)
Get(key interface{}) (value interface{}, err error)
Set(key interface{}, value interface{}) error
}
// AccountStore indexes accounts by address.
type AccountStore interface {
NewAccountWithAddress(addr crypto.Address) Account
GetAccount(addr crypto.Address) Account
SetAccount(acc Account)
NewAccountWithAddress(ctx Context, addr crypto.Address) Account
GetAccount(ctx Context, addr crypto.Address) Account
SetAccount(ctx Context, acc Account)
}
// new(AccountStoreKey) is a capabilities key.
type AccountStoreKey struct{}

14
types/codec.go Normal file
View File

@ -0,0 +1,14 @@
package types
// A generic codec for a fixed type.
type Codec interface {
// Returns a prototype (empty) object.
Prototype() interface{}
// Encodes the object.
Encode(o interface{}) ([]byte, error)
// Decodes an object.
Decode(bz []byte) (interface{}, error)
}

View File

@ -7,48 +7,21 @@ import (
abci "github.com/tendermint/abci/types"
)
// TODO: Add a default logger.
/*
A note on Context security:
The intent of Context is for it to be an immutable object that can be
cloned and updated cheaply with WithValue() and passed forward to the
next decorator or handler. For example,
```golang
func Decorator(ctx Context, tx Tx, next Handler) Result {
// Clone and update context with new kv pair.
ctx2 := ctx.WithValueSDK(key, value)
// Call the next decorator/handler.
res := next(ctx2, ms, tx)
func MsgHandler(ctx Context, tx Tx) Result {
...
ctx = ctx.WithValue(key, value)
...
}
```
While `ctx` and `ctx2`'s shallow values haven't changed, it's
possible that slices or addressable struct fields have been modified
by the call to `next(...)`.
This is generally undesirable because it prevents a decorator from
rolling back all side effects--which is the intent of immutable
Context's and store cache-wraps.
While well-written decorators wouldn't mutate any mutable context
values, a malicious or buggy plugin can create unwanted side-effects,
so it is highly advised for users of Context to only set immutable
values. To help enforce this contract, we require values to be
certain primitive types, a cloner, or a CacheWrapper.
If an outer (higher) decorator wants to know what an inner decorator
had set on the context, it can consult `context.GetOp(ver int64) Op`,
which retrieves the ver'th opertion to the context, globally since `NewContext()`.
TODO: Add a default logger.
*/
type Context struct {
context.Context
pst *thePast
@ -57,12 +30,13 @@ type Context struct {
// it's probably not what you want to do.
}
func NewContext(header abci.Header, isCheckTx bool, txBytes []byte) Context {
func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, txBytes []byte) Context {
c := Context{
Context: context.Background(),
pst: newThePast(),
gen: 0,
}
c = c.withMultiStore(ms)
c = c.withBlockHeader(header)
c = c.withBlockHeight(header.Height)
c = c.withChainID(header.ChainID)
@ -72,7 +46,7 @@ func NewContext(header abci.Header, isCheckTx bool, txBytes []byte) Context {
}
//----------------------------------------
// Get a value
// Getting a value
func (c Context) Value(key interface{}) interface{} {
value := c.Context.Value(key)
@ -85,10 +59,15 @@ func (c Context) Value(key interface{}) interface{} {
return value
}
//----------------------------------------
// Set a value
// KVStore fetches a KVStore from the MultiStore.
func (c Context) KVStore(key *KVStoreKey) KVStore {
return c.multiStore().GetKVStore(key)
}
func (c Context) WithValueUnsafe(key interface{}, value interface{}) Context {
//----------------------------------------
// With* (setting a value)
func (c Context) WithValue(key interface{}, value interface{}) Context {
return c.withValue(key, value)
}
@ -104,6 +83,10 @@ func (c Context) WithProtoMsg(key interface{}, value proto.Message) Context {
return c.withValue(key, value)
}
func (c Context) WithMultiStore(key *MultiStoreKey, ms MultiStore) Context {
return c.withValue(key, ms)
}
func (c Context) WithString(key interface{}, value string) Context {
return c.withValue(key, value)
}
@ -135,18 +118,24 @@ func (c Context) withValue(key interface{}, value interface{}) Context {
}
//----------------------------------------
// Our extensions
// Values that require no key.
type contextKey int // local to the context module
const (
contextKeyBlockHeader contextKey = iota
contextKeyMultiStore contextKey = iota
contextKeyBlockHeader
contextKeyBlockHeight
contextKeyChainID
contextKeyIsCheckTx
contextKeyTxBytes
)
// NOTE: Do not expose MultiStore, to require the store key.
func (c Context) multiStore() MultiStore {
return c.Value(contextKeyMultiStore).(MultiStore)
}
func (c Context) BlockHeader() abci.Header {
return c.Value(contextKeyBlockHeader).(abci.Header)
}
@ -167,8 +156,9 @@ func (c Context) TxBytes() []byte {
return c.Value(contextKeyTxBytes).([]byte)
}
func (c Context) KVStore(key interface{}) KVStore {
return c.Value(key).(KVStore)
// Unexposed to prevent overriding.
func (c Context) withMultiStore(ms MultiStore) Context {
return c.withValue(contextKeyMultiStore, ms)
}
// Unexposed to prevent overriding.

View File

@ -1,56 +0,0 @@
package types
// A Decorator executes before/during/after a handler to enhance functionality.
type Decorator func(ctx Context, tx Tx, next Handler) Result
// Return a decorated handler
func Decorate(dec Decorator, next Handler) Handler {
return func(ctx Context, tx Tx) Result {
return dec(ctx, tx, next)
}
}
//----------------------------------------
/*
Helper to construct a decorated Handler from a stack of Decorators
(first-decorator-first-call as in Python @decorators) , w/ Handler provided
last for syntactic sugar of ChainDecorators().WithHandler()
Usage:
handler := sdk.ChainDecorators(
decorator1,
decorator2,
...,
).WithHandler(myHandler)
*/
func ChainDecorators(decorators ...Decorator) stack {
return stack{
decs: decorators,
}
}
// No need to expose this.
type stack struct {
decs []Decorator
}
// WithHandler sets the final handler for the stack and
// returns the decoratored Handler.
func (s stack) WithHandler(handler Handler) Handler {
if handler == nil {
panic("WithHandler() requires a non-nil Handler")
}
return build(s.decs, handler)
}
// build wraps each decorator around the next, so that
// the last in the list is closest to the handler
func build(stack []Decorator, end Handler) Handler {
if len(stack) == 0 {
return end
}
return Decorate(stack[0], build(stack[1:], end))
}

View File

@ -1,38 +0,0 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDecorate(t *testing.T) {
var calledDec1, calledDec2, calledHandler bool
dec1 := func(ctx Context, ms MultiStore, tx Tx, next Handler) Result {
calledDec1 = true
next(ctx, ms, tx)
return Result{}
}
dec2 := func(ctx Context, ms MultiStore, tx Tx, next Handler) Result {
calledDec2 = true
next(ctx, ms, tx)
return Result{}
}
handler := func(ctx Context, ms MultiStore, tx Tx) Result {
calledHandler = true
return Result{}
}
decoratedHandler := ChainDecorators(dec1, dec2).WithHandler(handler)
var ctx Context
var ms MultiStore
var tx Tx
decoratedHandler(ctx, ms, tx)
assert.True(t, calledDec1)
assert.True(t, calledDec2)
assert.True(t, calledHandler)
}

View File

@ -1,5 +1,5 @@
package types
// Handler handles both ABCI DeliverTx and CheckTx requests.
// Iff ABCI.CheckTx, ctx.IsCheckTx() returns true.
type Handler func(ctx Context, tx Tx) Result
type AnteHandler func(ctx Context, tx Tx) (Result, abort bool)

View File

@ -1,15 +0,0 @@
package types
import crypto "github.com/tendermint/go-crypto"
type Model interface {
Address() crypto.Address
Get(key interface{}) interface{}
Set(key interface{}, value interface{})
}
type ModelStore interface {
Load(addr crypto.Address) Model
Store(m Model)
}

View File

@ -80,12 +80,6 @@ type CommitStoreLoader func(id CommitID) (CommitStore, error)
// KVStore is a simple interface to get/set data
type KVStore interface {
// TODO Not yet implemented.
// CreateSubKVStore(key *storeKey) (KVStore, error)
// TODO Not yet implemented.
// GetSubKVStore(key *storeKey) KVStore
// Get returns nil iff key doesn't exist. Panics on nil key.
Get(key []byte) []byte
@ -107,6 +101,13 @@ type KVStore interface {
// Start must be greater than end, or the Iterator is invalid.
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
ReverseIterator(start, end []byte) Iterator
// TODO Not yet implemented.
// CreateSubKVStore(key *storeKey) (KVStore, error)
// TODO Not yet implemented.
// GetSubKVStore(key *storeKey) KVStore
}
// dbm.DB implements KVStore so we can CacheKVStore it.
@ -162,3 +163,6 @@ func (cid CommitID) IsZero() bool {
func (cid CommitID) String() string {
return fmt.Sprintf("CommitID{%v:%X}", cid.Hash, cid.Version)
}
// new(KVStoreKey) is a capabilities key.
type KVStoreKey struct{}

View File

@ -4,6 +4,10 @@ import crypto "github.com/tendermint/go-crypto"
type Msg interface {
// Return the message type.
// Must be alphanumeric or empty.
Type() string
// Get some property of the Msg.
Get(key interface{}) (value interface{})
@ -17,15 +21,19 @@ type Msg interface {
// Signers returns the addrs of signers that must sign.
// CONTRACT: All signatures must be present to be valid.
// CONTRACT: Returns addrs in some deterministic order.
Signers() []crypto.Address
GetSigners() []crypto.Address
}
type Tx interface {
Msg
// The address that pays the base fee for this message. The fee is
// deducted before the Msg is processed.
GetFeePayer() crypto.Address
// Get the canonical byte representation of the Tx.
// Includes any signatures (or empty slots).
TxBytes() []byte
GetTxBytes() []byte
// Signatures returns the signature of signers who signed the Msg.
// CONTRACT: Length returned is same as length of
@ -34,5 +42,12 @@ type Tx interface {
// CONTRACT: If the signature is missing (ie the Msg is
// invalid), then the corresponding signature is
// .Empty().
Signatures() []StdSignature
GetSignatures() []StdSignature
}
type StdTx struct {
Msg
Signatures []StdSignature
}
type TxDecoder func(txBytes []byte) (Tx, error)

View File

@ -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
View 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...
}
}

View File

@ -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)
}

View File

@ -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
View 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)
}

View File

@ -1,5 +1,5 @@
//nolint
package coinstore
package bank
import (
"fmt"

View File

@ -1,4 +1,4 @@
package coinstore
package bank
import (
"github.com/cosmos/cosmos-sdk/types"

51
x/bank/store.go Normal file
View 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
View 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
}

View File

@ -1,4 +1,4 @@
package coinstore
package bank
import (
"testing"

View File

@ -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
}

View File

@ -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
}