diff --git a/app/app.go b/app/app.go index 12fea597f..a082e93f4 100644 --- a/app/app.go +++ b/app/app.go @@ -35,6 +35,9 @@ type App struct { // Current block header header abci.Header + // Unmarshal []byte into types.Tx + txParser TxParser + // Handler for CheckTx and DeliverTx. handler types.Handler @@ -59,6 +62,18 @@ func (app *App) SetCommitMultiStore(ms types.CommitMultiStore) { app.ms = ms } +/* +SetBeginBlocker +SetEndBlocker +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 } @@ -167,7 +182,15 @@ 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 = nil // nil until a decorator parses one. + var tx types.Tx + + 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.ms, tx) @@ -192,7 +215,15 @@ 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 = nil // nil until a decorator parses one. + 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.ms, tx) diff --git a/app/example/basecoin/account.go b/app/example/basecoin/account.go new file mode 100644 index 000000000..0668c86d4 --- /dev/null +++ b/app/example/basecoin/account.go @@ -0,0 +1,71 @@ +package main + +import ( + "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +// AppAccount - coin account structure +type AppAccount struct { + Address_ types.Address `json:"address"` + Coins types.Coins `json:"coins"` + PubKey_ crypto.PubKey `json:"public_key"` // can't conflict with PubKey() + Sequence int64 `json:"sequence"` +} + +// Implements auth.Account +func (a *AppAccount) Get(key interface{}) (value interface{}, err error) { + switch key.(type) { + case string: + // + default: + panic("HURAH!") + } + return nil, nil +} + +// Implements auth.Account +func (a *AppAccount) Set(key interface{}, value interface{}) error { + switch key.(type) { + case string: + // + default: + panic("HURAH!") + } + return nil +} + +// Implements auth.Account +func (a *AppAccount) Address() types.Address { + return a.PubKey_.Address() +} + +// Implements auth.Account +func (a *AppAccount) PubKey() crypto.PubKey { + return a.PubKey_ +} + +func (a *AppAccount) SetPubKey(pubKey crypto.PubKey) error { + a.PubKey_ = pubKey + return nil +} + +// Implements coinstore.Coinser +func (a *AppAccount) GetCoins() types.Coins { + return a.Coins +} + +// Implements coinstore.Coinser +func (a *AppAccount) SetCoins(coins types.Coins) error { + a.Coins = coins + return nil +} + +func (a *AppAccount) GetSequence() int64 { + return a.Sequence +} + +func (a *AppAccount) SetSequence(seq int64) error { + a.Sequence = seq + return nil +} diff --git a/app/example/basecoin/account_store.go b/app/example/basecoin/account_store.go new file mode 100644 index 000000000..53078ff6b --- /dev/null +++ b/app/example/basecoin/account_store.go @@ -0,0 +1,55 @@ +package main + +import ( + "encoding/json" + "path" + + "github.com/cosmos/cosmos-sdk/types" +) + +type AppAccountStore struct { + kvStore types.KVStore +} + +func newAccountStore(kvStore types.KVStore) types.AccountStore { + return AppAccountStore{kvStore} +} + +func (accStore AppAccountStore) NewAccountWithAddress(addr types.Address) types.Account { + return &AppAccount{ + Address_: addr, + } +} + +func (accStore AppAccountStore) GetAccount(addr types.Address) types.Account { + v := accStore.kvStore.Get(keyAccount(addr)) + + if len(v) == 0 { + return nil + } + + acc := new(AppAccount) + if err := json.Unmarshal(v, acc); err != nil { + panic(err) + } + + return acc +} + +func (accStore AppAccountStore) SetAccount(acc types.Account) { + b, err := json.Marshal(acc) + if err != nil { + panic(err) + } + + appAcc, ok := acc.(*AppAccount) + if !ok { + panic("acc is not *AppAccount") // XXX + } + + accStore.kvStore.Set(keyAccount(appAcc.Address_), b) +} + +func keyAccount(addr types.Address) []byte { + return []byte(path.Join("account", string(addr))) +} diff --git a/app/example/basecoin/main.go b/app/example/basecoin/main.go new file mode 100644 index 000000000..96e1bd43c --- /dev/null +++ b/app/example/basecoin/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/tendermint/abci/server" + 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" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/sendtx" +) + +func main() { + + app := app.NewApp("basecoin") + + db, err := dbm.NewGoLevelDB("basecoin", "basecoin-data") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // create CommitStoreLoader + cacheSize := 10000 + numHistory := int64(100) + loader := store.NewIAVLStoreLoader(db, cacheSize, numHistory) + + // Create MultiStore + multiStore := store.NewCommitMultiStore(db) + multiStore.SetSubstoreLoader("main", loader) + + // Create Handler + handler := types.ChainDecorators( + // recover.Decorator(), + // logger.Decorator(), + auth.DecoratorFn(newAccountStore), + ).WithHandler( + sendtx.TransferHandlerFn(newAccountStore), + ) + + // TODO: load genesis + // TODO: InitChain with validators + // accounts := 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) + if err := app.LoadLatestVersion(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Start the ABCI server + srv, err := server.NewServer("0.0.0.0:46658", "socket", app) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + srv.Start() + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + srv.Stop() + }) + return +} + +// create ctx in begin block to be used as background for txs ... + +func txParser(txBytes []byte) (types.Tx, error) { + var tx sendtx.SendTx + err := json.Unmarshal(txBytes, &tx) + return tx, err +} diff --git a/app/example/dummy/main.go b/app/example/dummy/main.go new file mode 100644 index 000000000..3ccb49ccb --- /dev/null +++ b/app/example/dummy/main.go @@ -0,0 +1,136 @@ +package main + +import ( + "bytes" + "fmt" + "os" + + "github.com/tendermint/abci/server" + 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" +) + +func main() { + + app := app.NewApp("dummy") + + db, err := dbm.NewGoLevelDB("dummy", "dummy-data") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // create CommitStoreLoader + cacheSize := 10000 + numHistory := int64(100) + loader := store.NewIAVLStoreLoader(db, cacheSize, numHistory) + + // Create MultiStore + multiStore := store.NewMultiStore(db) + multiStore.SetSubstoreLoader("main", loader) + + // Create Handler + handler := types.Decorate(unmarshalDecorator, dummyHandler) + + // Set everything on the app and load latest + app.SetCommitMultiStore(multiStore) + app.SetHandler(handler) + if err := app.LoadLatestVersion(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Start the ABCI server + srv, err := server.NewServer("0.0.0.0:46658", "socket", app) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + srv.Start() + + // Wait forever + cmn.TrapSignal(func() { + // Cleanup + srv.Stop() + }) + return +} + +type dummyTx struct { + key []byte + value []byte + + bytes []byte +} + +func (tx dummyTx) Get(key interface{}) (value interface{}) { + switch k := key.(type) { + case string: + switch k { + case "key": + return tx.key + case "value": + return tx.value + } + } + return nil +} + +func (tx dummyTx) SignBytes() []byte { + return tx.bytes +} + +// Should the app be calling this? Or only handlers? +func (tx dummyTx) ValidateBasic() error { + return nil +} + +func (tx dummyTx) Signers() [][]byte { + return nil +} + +func (tx dummyTx) TxBytes() []byte { + return tx.bytes +} + +func (tx dummyTx) Signatures() []types.StdSignature { + return nil +} + +func unmarshalDecorator(ctx types.Context, ms types.MultiStore, tx types.Tx, next types.Handler) types.Result { + txBytes := ctx.TxBytes() + + split := bytes.Split(txBytes, []byte("=")) + if len(split) == 1 { + k := split[0] + tx = dummyTx{k, k, txBytes} + } else if len(split) == 2 { + k, v := split[0], split[1] + tx = dummyTx{k, v, txBytes} + } else { + return types.Result{ + Code: 1, + Log: "too many =", + } + } + + return next(ctx, ms, tx) +} + +func dummyHandler(ctx types.Context, ms types.MultiStore, tx types.Tx) types.Result { + // tx is already unmarshalled + key := tx.Get("key").([]byte) + value := tx.Get("value").([]byte) + + main := ms.GetKVStore("main") + main.Set(key, value) + + return types.Result{ + Code: 0, + Log: fmt.Sprintf("set %s=%s", key, value), + } +} diff --git a/errors/abci.go b/errors/abci.go index 6c5cebf11..4341eaef0 100644 --- a/errors/abci.go +++ b/errors/abci.go @@ -4,10 +4,6 @@ import ( abci "github.com/tendermint/abci/types" ) -type causer interface { - Cause() error -} - func getABCIError(err error) (ABCIError, bool) { if err, ok := err.(ABCIError); ok { return err, true @@ -23,7 +19,7 @@ func getABCIError(err error) (ABCIError, bool) { func ResponseDeliverTxFromErr(err error) *abci.ResponseDeliverTx { var code = CodeInternalError - var log = codeToDefaultLog(code) + var log = CodeToDefaultLog(code) abciErr, ok := getABCIError(err) if ok { @@ -41,7 +37,7 @@ func ResponseDeliverTxFromErr(err error) *abci.ResponseDeliverTx { func ResponseCheckTxFromErr(err error) *abci.ResponseCheckTx { var code = CodeInternalError - var log = codeToDefaultLog(code) + var log = CodeToDefaultLog(code) abciErr, ok := getABCIError(err) if ok { @@ -53,7 +49,7 @@ func ResponseCheckTxFromErr(err error) *abci.ResponseCheckTx { Code: code, Data: nil, Log: log, - Gas: 0, // TODO - Fee: 0, // TODO + // Gas: 0, // TODO + // Fee: 0, // TODO } } diff --git a/errors/errors.go b/errors/errors.go index f2d6f5e20..3bee90113 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -2,8 +2,6 @@ package errors import ( "fmt" - - "github.com/pkg/errors" ) const ( @@ -174,11 +172,11 @@ func ABCIErrorCause(err error) ABCIError { } errCause := cause.Cause() if errCause == nil || errCause == err { - return err + return nil } err = errCause } - return err + return nil } // Identitical to pkg/errors.Cause, except handles .Cause() diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 7508d3c05..495df1839 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -28,7 +28,7 @@ type rootMultiStore struct { var _ CommitMultiStore = (*rootMultiStore)(nil) -func NewMultiStore(db dbm.DB) *rootMultiStore { +func NewCommitMultiStore(db dbm.DB) *rootMultiStore { return &rootMultiStore{ db: db, nextVersion: 0, diff --git a/types/account.go b/types/account.go new file mode 100644 index 000000000..ebd17f82d --- /dev/null +++ b/types/account.go @@ -0,0 +1,35 @@ +package types + +import ( + "encoding/hex" + + crypto "github.com/tendermint/go-crypto" +) + +type Address []byte // TODO: cmn.HexBytes + +func (a Address) String() string { + return hex.EncodeToString(a) +} + +type Account interface { + Address() Address + + PubKey() crypto.PubKey + SetPubKey(crypto.PubKey) error + + GetCoins() Coins + SetCoins(Coins) error + + GetSequence() int64 + SetSequence(int64) error + + Get(key interface{}) (value interface{}, err error) + Set(key interface{}, value interface{}) error +} + +type AccountStore interface { + NewAccountWithAddress(addr Address) Account + GetAccount(addr Address) Account + SetAccount(acc Account) +} diff --git a/types/coin.go b/types/coin.go index 30e96b295..fae79647d 100644 --- a/types/coin.go +++ b/types/coin.go @@ -136,7 +136,7 @@ func (coins Coins) IsGTE(coinsB Coins) bool { if len(diff) == 0 { return true } - return diff.IsNonnegative() + return diff.IsNotNegative() } // IsZero returns true if there are no coins @@ -171,9 +171,9 @@ func (coins Coins) IsPositive() bool { return true } -// IsNonnegative returns true if there is no currency with a negative value +// IsNotNegative returns true if there is no currency with a negative value // (even no coins is true here) -func (coins Coins) IsNonnegative() bool { +func (coins Coins) IsNotNegative() bool { if len(coins) == 0 { return true } diff --git a/types/signature.go b/types/signature.go index 02c059e58..e22d0c78c 100644 --- a/types/signature.go +++ b/types/signature.go @@ -3,6 +3,7 @@ package types import crypto "github.com/tendermint/go-crypto" type StdSignature struct { + crypto.PubKey // optional crypto.Signature Sequence int64 } diff --git a/types/tx_msg.go b/types/tx_msg.go index c704cc41f..2d432f845 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -15,7 +15,7 @@ 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() [][]byte + Signers() []Address } type Tx interface { diff --git a/x/auth/context.go b/x/auth/context.go index dd585f205..07e86b7d6 100644 --- a/x/auth/context.go +++ b/x/auth/context.go @@ -24,10 +24,10 @@ const ( contextKeyAccount contextKey = iota ) -func SetAccount(ctx types.Context, account Account) types.Context { - return ctx.WithValueSDK(contextKeyAccount, account) +func SetAccount(ctx types.Context, account types.Account) types.Context { + return ctx.WithValueUnsafe(contextKeyAccount, account) } -func GetAccount(ctx types.Context) Account { - return ctx.Value(contextKeyAccount).(Account) +func GetAccount(ctx types.Context) types.Account { + return ctx.Value(contextKeyAccount).(types.Account) } diff --git a/x/auth/decorator.go b/x/auth/decorator.go new file mode 100644 index 000000000..38311563f --- /dev/null +++ b/x/auth/decorator.go @@ -0,0 +1,78 @@ +package auth + +import ( + "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +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")) + + // NOTE: we actually dont need Signers() since we have pubkeys in Signatures() + 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]) + + // assert it has the right methods + acc, ok := _acc.(Auther) + if !ok { + return types.Result{ + Code: 1, // TODO + } + } + + // if no pubkey, set pubkey + if acc.GetPubKey().Empty() { + err := acc.SetPubKey(sig.PubKey) + if err != nil { + return types.Result{ + Code: 1, // TODO + } + } + } + + // check sequence number + seq := acc.GetSequence() + if seq != sig.Sequence { + return types.Result{ + Code: 1, // TODO + } + } + + // check sig + if !sig.PubKey.VerifyBytes(tx.SignBytes(), sig.Signature) { + return types.Result{ + Code: 1, // TODO + } + } + } + return next(ctx, ms, tx) + } +} + +type Auther interface { + GetPubKey() crypto.PubKey + SetPubKey(crypto.PubKey) error + + GetSequence() int64 + SetSequence() (int64, error) +} diff --git a/x/auth/types.go b/x/auth/types.go index 4f914bdbe..5f62fa5d3 100644 --- a/x/auth/types.go +++ b/x/auth/types.go @@ -1,15 +1,9 @@ package auth -import crypto "github.com/tendermint/go-crypto" +import ( + crypto "github.com/tendermint/go-crypto" +) -type Account interface { - Get(key interface{}) (value interface{}) - - Address() []byte - PubKey() crypto.PubKey -} - -type AccountStore interface { - GetAccount(addr []byte) Account - SetAccount(acc Account) +type SetPubKeyer interface { + SetPubKey(crypto.PubKey) } diff --git a/x/coinstore/genesis.go b/x/coinstore/_attic/genesis.go similarity index 100% rename from x/coinstore/genesis.go rename to x/coinstore/_attic/genesis.go diff --git a/x/coinstore/utils.go b/x/coinstore/_attic/utils.go similarity index 100% rename from x/coinstore/utils.go rename to x/coinstore/_attic/utils.go diff --git a/x/coinstore/decorator.go b/x/coinstore/decorator.go deleted file mode 100644 index 2ef0df008..000000000 --- a/x/coinstore/decorator.go +++ /dev/null @@ -1,13 +0,0 @@ -package coin - -import ( - sdk "github.com/cosmos/cosmos-sdk" -) - -func Decorator(ctx sdk.Context, store sdk.MultiStore, tx sdk.Tx, next sdk.Handler) sdk.Result { - if msg, ok := tx.(CoinsMsg); ok { - handleCoinsMsg(ctx, store, msg) - } else { - next(ctx, store, tx) - } -} diff --git a/x/coinstore/errors.go b/x/coinstore/errors.go index f5419d9c9..a8fce42ac 100644 --- a/x/coinstore/errors.go +++ b/x/coinstore/errors.go @@ -2,18 +2,18 @@ package coin import ( - "fmt" - "github.com/cosmos/cosmos-sdk/errors" ) const ( // Coin errors reserve 100 ~ 199. - CodeInvalidInput uint32 = 101 - CodeInvalidOutput uint32 = 102 - CodeInvalidAddress uint32 = 103 - CodeUnknownAddress uint32 = 103 - CodeUnknownRequest uint32 = errors.CodeUnknownRequest + CodeInvalidInput uint32 = 101 + CodeInvalidOutput uint32 = 102 + CodeInvalidAddress uint32 = 103 + CodeUnknownAddress uint32 = 104 + CodeInsufficientCoins uint32 = 105 + CodeInvalidCoins uint32 = 106 + CodeUnknownRequest uint32 = errors.CodeUnknownRequest ) // NOTE: Don't stringer this, we'll put better messages in later. @@ -27,6 +27,10 @@ func codeToDefaultLog(code uint32) string { return "Invalid address" case CodeUnknownAddress: return "Unknown address" + case CodeInsufficientCoins: + return "Insufficient coins" + case CodeInvalidCoins: + return "Invalid coins" case CodeUnknownRequest: return "Unknown request" default: @@ -53,6 +57,14 @@ func ErrUnknownAddress(log string) error { return newError(CodeUnknownAddress, log) } +func ErrInsufficientCoins(log string) error { + return newError(CodeInsufficientCoins, log) +} + +func ErrInvalidCoins(log string) error { + return newError(CodeInvalidCoins, log) +} + func ErrUnknownRequest(log string) error { return newError(CodeUnknownRequest, log) } @@ -60,11 +72,11 @@ func ErrUnknownRequest(log string) error { //---------------------------------------- // Misc -func logOrDefault(log string, code uint32) string { +func logOrDefaultLog(log string, code uint32) string { if log != "" { return log } else { - return codeToDefaultLog + return codeToDefaultLog(code) } } diff --git a/x/coinstore/store.go b/x/coinstore/store.go new file mode 100644 index 000000000..7ee4467c3 --- /dev/null +++ b/x/coinstore/store.go @@ -0,0 +1,117 @@ +package coin + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/types" +) + +type Coins = types.Coins + +type Coinser interface { + GetCoins() Coins + SetCoins(Coins) +} + +// CoinStore manages transfers between accounts +type CoinStore struct { + types.AccountStore +} + +// 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 types.Address) (types.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 +} + +func (cs CoinStore) SubtractCoins(addr types.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 +} + +func (cs CoinStore) AddCoins(addr types.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 +} + +/* +// TransferCoins transfers coins from fromAddr to toAddr. +// It returns an error if the from account doesn't exist, +// if the accounts doin't implement Coinser, +// or if the from account does not have enough coins. +func (cs CoinStore) TransferCoins(fromAddr, toAddr types.Address, amt Coins) error { + var fromAcc, toAcc types.Account + + // Get the accounts + _fromAcc := cs.GetAccount(fromAddr) + if _fromAcc == nil { + return ErrUnknownAccount(fromAddr) + } + + _toAcc := cs.GetAccount(to) + if _toAcc == nil { + toAcc = cs.AccountStore.NewAccountWithAddress(to) + } + + // Ensure they are Coinser + fromAcc, ok := _fromAcc.(Coinser) + if !ok { + return ErrAccountNotCoinser(from) + } + + toAcc, ok = _toAcc.(Coinser) + if !ok { + return ErrAccountNotCoinser(from) + } + + // Coin math + fromCoins := fromAcc.GetCoins() + newFromCoins := fromCoins.Minus(amt) + if newFromCoins.Negative() { + return ErrInsufficientCoins(fromCoins, amt) + } + toCoins := toAcc.GetCoins() + newToCoins := toCoins.Plus(amt) + + // Set everything! + fromAcc.SetCoins(newFromCoins) + toAcc.SetCoins(newToCoins) + cs.SetAccount(fromAcc) + cs.SetAccount(toAcc) + + return nil +}*/ diff --git a/x/coinstore/tx.go b/x/coinstore/tx.go deleted file mode 100644 index ab1289374..000000000 --- a/x/coinstore/tx.go +++ /dev/null @@ -1,168 +0,0 @@ -package coin - -// TODO rename this to msg.go - -import ( - "fmt" - "github.com/cosmos/cosmos-sdk/types" - cmn "github.com/tendermint/tmlibs/common" -) - -type CoinMsg interface { - AssertIsCoinMsg() - Type() string // "send", "credit" -} - -//----------------------------------------------------------------------------- - -// Input is a source of coins in a transaction. -type Input struct { - Address cmn.Bytes - Coins types.Coins -} - -func (in Input) ValidateBasic() error { - if !auth.IsValidAddress(in.Address) { - return ErrInvalidAddress() - } - if !in.Coins.IsValid() { - return ErrInvalidInput() - } - if !in.Coins.IsPositive() { - return ErrInvalidInput() - } - 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 Actor, coins types.Coins) TxInput { - input := TxInput{ - Address: addr, - Coins: coins, - } - return input -} - -//----------------------------------------------------------------------------- - -// TxOutput - expected coin movement output, used with SendTx -type TxOutput struct { - Address Actor `json:"address"` - Coins types.Coins `json:"coins"` -} - -// ValidateBasic - validate transaction output -func (txOut TxOutput) ValidateBasic() error { - if txOut.Address.App == "" { - return ErrInvalidAddress() - } - // TODO: knowledge of app-specific codings? - if len(txOut.Address.Address) == 0 { - return ErrInvalidAddress() - } - if !txOut.Coins.IsValid() { - return ErrInvalidCoins() - } - if !txOut.Coins.IsPositive() { - return ErrInvalidCoins() - } - 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 Actor, coins types.Coins) TxOutput { - output := TxOutput{ - Address: addr, - Coins: coins, - } - return output -} - -//----------------------------------------------------------------------------- - -// SendTx - high level transaction of the coin module -// Satisfies: TxInner -type SendTx struct { - Inputs []TxInput `json:"inputs"` - Outputs []TxOutput `json:"outputs"` -} - -// var _ types.Tx = NewSendTx(nil, nil) - -// NewSendTx - construct arbitrary multi-in, multi-out sendtx -func NewSendTx(in []TxInput, out []TxOutput) SendTx { // 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 Actor, amount types.Coins) SendTx { - in := []TxInput{{Address: sender, Coins: amount}} - out := []TxOutput{{Address: recipient, Coins: amount}} - return SendTx{Inputs: in, Outputs: out} -} - -// 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() - } - return nil -} - -func (tx SendTx) String() string { - return fmt.Sprintf("SendTx{%v->%v}", tx.Inputs, tx.Outputs) -} - -//----------------------------------------------------------------------------- - -// CreditTx - this allows a special issuer to give an account credit -// Satisfies: TxInner -type CreditTx struct { - Debitor Actor `json:"debitor"` - // Credit is the amount to change the credit... - // This may be negative to remove some over-issued credit, - // but can never bring the credit or the balance to negative - Credit types.Coins `json:"credit"` -} - -// NewCreditTx - modify the credit granted to a given account -func NewCreditTx(debitor Actor, credit types.Coins) CreditTx { - return CreditTx{Debitor: debitor, Credit: credit} -} - -// ValidateBasic - used to satisfy TxInner -func (tx CreditTx) ValidateBasic() error { - return nil -} diff --git a/x/coinstore/tx_test.go b/x/coinstore/tx_test.go deleted file mode 100644 index bff2f98da..000000000 --- a/x/coinstore/tx_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package coin - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/tendermint/go-wire/data" - - sdk "github.com/cosmos/cosmos-sdk" -) - -// these are some constructs for the test cases -var actors = []struct { - actor sdk.Actor - valid bool -}{ - {sdk.Actor{}, false}, - {sdk.Actor{App: "fooz"}, false}, - {sdk.Actor{Address: []byte{1, 2, 3, 4}}, false}, - {sdk.Actor{App: "fooz", Address: []byte{1, 2, 3, 4}}, true}, - {sdk.Actor{ChainID: "dings", App: "fooz", Address: []byte{1, 2, 3, 4}}, true}, - {sdk.Actor{ChainID: "dat", App: "fooz"}, false}, -} - -var ( - zeroCoin = Coin{"zeros", 0} - plusCoin = Coin{"plus", 23} - negCoin = Coin{"neg", -42} -) - -var coins = []struct { - coins Coins - valid bool -}{ - {Coins{}, false}, - {Coins{zeroCoin}, false}, - {Coins{plusCoin}, true}, - {Coins{negCoin}, false}, - {Coins{plusCoin, plusCoin}, false}, - {Coins{plusCoin, zeroCoin}, false}, - {Coins{negCoin, plusCoin}, false}, -} - -func TestTxValidateInput(t *testing.T) { - assert := assert.New(t) - - for i, act := range actors { - for j, coin := range coins { - input := NewTxInput(act.actor, coin.coins) - err := input.ValidateBasic() - if act.valid && coin.valid { - assert.Nil(err, "%d,%d: %+v", i, j, err) - } else { - assert.NotNil(err, "%d,%d", i, j) - } - } - } -} - -func TestTxValidateOutput(t *testing.T) { - assert := assert.New(t) - - for i, act := range actors { - for j, coin := range coins { - input := NewTxOutput(act.actor, coin.coins) - err := input.ValidateBasic() - if act.valid && coin.valid { - assert.Nil(err, "%d,%d: %+v", i, j, err) - } else { - assert.NotNil(err, "%d,%d", i, j) - } - } - } -} - -func TestTxValidateTx(t *testing.T) { - assert := assert.New(t) - - addr1 := sdk.Actor{App: "coin", Address: []byte{1, 2}} - addr2 := sdk.Actor{App: "coin", Address: []byte{3, 4}, ChainID: "over-there"} - addr3 := sdk.Actor{App: "role", Address: []byte{7, 8}} - noAddr := sdk.Actor{} - - noCoins := Coins{} - someCoins := Coins{{"atom", 123}} - moreCoins := Coins{{"atom", 124}} - otherCoins := Coins{{"btc", 15}} - bothCoins := someCoins.Plus(otherCoins) - minusCoins := Coins{{"eth", -34}} - - // cases: all valid (one), all valid (multi) - // no input, no outputs, invalid inputs, invalid outputs - // totals don't match - cases := []struct { - valid bool - tx sdk.Tx - }{ - // 0-2. valid cases - {true, NewSendTx( - []TxInput{NewTxInput(addr1, someCoins)}, - []TxOutput{NewTxOutput(addr2, someCoins)}, - )}, - {true, NewSendTx( - []TxInput{NewTxInput(addr1, someCoins), NewTxInput(addr2, otherCoins)}, - []TxOutput{NewTxOutput(addr3, bothCoins)}, - )}, - {true, NewSendTx( - []TxInput{NewTxInput(addr1, bothCoins)}, - []TxOutput{NewTxOutput(addr2, someCoins), NewTxOutput(addr3, otherCoins)}, - )}, - - // 3-4. missing cases - {false, NewSendTx( - nil, - []TxOutput{NewTxOutput(addr2, someCoins)}, - )}, - {false, NewSendTx( - []TxInput{NewTxInput(addr1, someCoins)}, - nil, - )}, - - // 5-7. invalid inputs - {false, NewSendTx( - []TxInput{NewTxInput(noAddr, someCoins)}, - []TxOutput{NewTxOutput(addr2, someCoins)}, - )}, - {false, NewSendTx( - []TxInput{NewTxInput(addr1, noCoins)}, - []TxOutput{NewTxOutput(addr2, noCoins)}, - )}, - {false, NewSendTx( - []TxInput{NewTxInput(addr1, minusCoins)}, - []TxOutput{NewTxOutput(addr2, minusCoins)}, - )}, - - // 8-10. totals don't match - {false, NewSendTx( - []TxInput{NewTxInput(addr1, someCoins)}, - []TxOutput{NewTxOutput(addr2, moreCoins)}, - )}, - {false, NewSendTx( - []TxInput{NewTxInput(addr1, someCoins), NewTxInput(addr2, minusCoins)}, - []TxOutput{NewTxOutput(addr3, someCoins)}, - )}, - {false, NewSendTx( - []TxInput{NewTxInput(addr1, someCoins), NewTxInput(addr2, moreCoins)}, - []TxOutput{NewTxOutput(addr3, bothCoins)}, - )}, - } - - for i, tc := range cases { - err := tc.tx.ValidateBasic() - if tc.valid { - assert.Nil(err, "%d: %+v", i, err) - } else { - assert.NotNil(err, "%d", i) - } - } -} - -func TestTxSerializeTx(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - addr1 := sdk.Actor{App: "coin", Address: []byte{1, 2}} - addr2 := sdk.Actor{App: "coin", Address: []byte{3, 4}} - someCoins := Coins{{"atom", 123}} - - send := NewSendTx( - []TxInput{NewTxInput(addr1, someCoins)}, - []TxOutput{NewTxOutput(addr2, someCoins)}, - ) - - js, err := data.ToJSON(send) - require.Nil(err) - var tx sdk.Tx - err = data.FromJSON(js, &tx) - require.Nil(err) - assert.Equal(send, tx) - - bin, err := data.ToWire(send) - require.Nil(err) - var tx2 sdk.Tx - err = data.FromWire(bin, &tx2) - require.Nil(err) - assert.Equal(send, tx2) - -} diff --git a/x/sendtx/errors.go b/x/sendtx/errors.go new file mode 100644 index 000000000..ffcf426e8 --- /dev/null +++ b/x/sendtx/errors.go @@ -0,0 +1,17 @@ +package sendtx + +import "fmt" + +// TODO! Deal coherently with this and coinstore/errors.go + +func ErrNoInputs() error { + return fmt.Errorf("No inputs") +} + +func ErrNoOutputs() error { + return fmt.Errorf("No outputs") +} + +func ErrInvalidSequence(seq int64) error { + return fmt.Errorf("Bad sequence %d", seq) +} diff --git a/x/sendtx/handler.go b/x/sendtx/handler.go new file mode 100644 index 000000000..fcbb66a00 --- /dev/null +++ b/x/sendtx/handler.go @@ -0,0 +1,41 @@ +package sendtx + +import ( + "github.com/cosmos/cosmos-sdk/types" + coinstore "github.com/cosmos/cosmos-sdk/x/coinstore" +) + +func TransferHandlerFn(newAccStore func(types.KVStore) types.AccountStore) types.Handler { + return func(ctx types.Context, ms types.MultiStore, tx types.Tx) types.Result { + + accStore := newAccStore(ms.GetKVStore("main")) + cs := coinstore.CoinStore{accStore} + + sendTx, ok := tx.(SendTx) + if !ok { + panic("tx is not SendTx") // ? + } + + // NOTE: totalIn == totalOut should already have been checked + + for _, in := range sendTx.Inputs { + _, err := cs.SubtractCoins(in.Address, in.Coins) + if err != nil { + return types.Result{ + Code: 1, // TODO + } + } + } + + for _, out := range sendTx.Outputs { + _, err := cs.AddCoins(out.Address, out.Coins) + if err != nil { + return types.Result{ + Code: 1, // TODO + } + } + } + + return types.Result{} // TODO + } +} diff --git a/x/sendtx/tx.go b/x/sendtx/tx.go new file mode 100644 index 000000000..839eaa3be --- /dev/null +++ b/x/sendtx/tx.go @@ -0,0 +1,212 @@ +package sendtx + +import ( + "encoding/json" + "fmt" + + "github.com/cosmos/cosmos-sdk/types" + coinstore "github.com/cosmos/cosmos-sdk/x/coinstore" + crypto "github.com/tendermint/go-crypto" +) + +type ( + Address = types.Address + Coins = types.Coins +) + +//----------------------------------------------------------------------------- + +// TxInput +type TxInput struct { + Address Address `json:"address"` + Coins 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 coinstore.ErrInvalidAddress(txIn.Address.String()) + } + if txIn.Sequence < 0 { + return ErrInvalidSequence(txIn.Sequence) + } + if !txIn.Coins.IsValid() { + return coinstore.ErrInvalidCoins(txIn.Coins.String()) + } + if !txIn.Coins.IsPositive() { + return coinstore.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 Address, coins Coins) TxInput { + input := TxInput{ + Address: addr, + Coins: coins, + } + return input +} + +//----------------------------------------------------------------------------- + +// TxOutput - expected coin movement output, used with SendTx +type TxOutput struct { + Address Address `json:"address"` + Coins Coins `json:"coins"` +} + +// ValidateBasic - validate transaction output +func (txOut TxOutput) ValidateBasic() error { + if len(txOut.Address) == 0 { + return coinstore.ErrInvalidAddress(txOut.Address.String()) + } + if !txOut.Coins.IsValid() { + return coinstore.ErrInvalidCoins(txOut.Coins.String()) + } + if !txOut.Coins.IsPositive() { + return coinstore.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 Address, coins Coins) TxOutput { + output := TxOutput{ + Address: addr, + Coins: coins, + } + return output +} + +//----------------------------------------------------------------------------- + +// SendTx - high level transaction of the coin module +// Satisfies: TxInner +type SendTx struct { + Inputs []TxInput `json:"inputs"` + Outputs []TxOutput `json:"outputs"` +} + +var _ types.Tx = (*SendTx)(nil) + +// 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 types.Address, amount types.Coins) types.Tx { + in := []TxInput{{Address: sender, Coins: amount}} + out := []TxOutput{{Address: recipient, Coins: amount}} + return SendTx{Inputs: in, Outputs: out} +} + +// 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 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 coinstore.ErrInvalidCoins(totalIn.String()) // TODO + } + return nil +} + +func (tx SendTx) String() string { + return fmt.Sprintf("SendTx{%v->%v}", tx.Inputs, tx.Outputs) +} + +// TODO +//------------------------ +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() []types.Address { + addrs := make([]types.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 +}