commit
c5aeef3f2f
35
app/app.go
35
app/app.go
|
@ -35,6 +35,9 @@ type App struct {
|
||||||
// Current block header
|
// Current block header
|
||||||
header abci.Header
|
header abci.Header
|
||||||
|
|
||||||
|
// Unmarshal []byte into types.Tx
|
||||||
|
txParser TxParser
|
||||||
|
|
||||||
// Handler for CheckTx and DeliverTx.
|
// Handler for CheckTx and DeliverTx.
|
||||||
handler types.Handler
|
handler types.Handler
|
||||||
|
|
||||||
|
@ -59,6 +62,18 @@ func (app *App) SetCommitMultiStore(ms types.CommitMultiStore) {
|
||||||
app.ms = ms
|
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) {
|
func (app *App) SetHandler(handler types.Handler) {
|
||||||
app.handler = handler
|
app.handler = handler
|
||||||
}
|
}
|
||||||
|
@ -167,7 +182,15 @@ func (app *App) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
||||||
// Initialize arguments to Handler.
|
// Initialize arguments to Handler.
|
||||||
var isCheckTx = true
|
var isCheckTx = true
|
||||||
var ctx = types.NewContext(app.header, isCheckTx, txBytes)
|
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.
|
// Run the handler.
|
||||||
var result = app.handler(ctx, app.ms, tx)
|
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.
|
// Initialize arguments to Handler.
|
||||||
var isCheckTx = false
|
var isCheckTx = false
|
||||||
var ctx = types.NewContext(app.header, isCheckTx, txBytes)
|
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.
|
// Run the handler.
|
||||||
var result = app.handler(ctx, app.ms, tx)
|
var result = app.handler(ctx, app.ms, tx)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,10 +4,6 @@ import (
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type causer interface {
|
|
||||||
Cause() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func getABCIError(err error) (ABCIError, bool) {
|
func getABCIError(err error) (ABCIError, bool) {
|
||||||
if err, ok := err.(ABCIError); ok {
|
if err, ok := err.(ABCIError); ok {
|
||||||
return err, true
|
return err, true
|
||||||
|
@ -23,7 +19,7 @@ func getABCIError(err error) (ABCIError, bool) {
|
||||||
|
|
||||||
func ResponseDeliverTxFromErr(err error) *abci.ResponseDeliverTx {
|
func ResponseDeliverTxFromErr(err error) *abci.ResponseDeliverTx {
|
||||||
var code = CodeInternalError
|
var code = CodeInternalError
|
||||||
var log = codeToDefaultLog(code)
|
var log = CodeToDefaultLog(code)
|
||||||
|
|
||||||
abciErr, ok := getABCIError(err)
|
abciErr, ok := getABCIError(err)
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -41,7 +37,7 @@ func ResponseDeliverTxFromErr(err error) *abci.ResponseDeliverTx {
|
||||||
|
|
||||||
func ResponseCheckTxFromErr(err error) *abci.ResponseCheckTx {
|
func ResponseCheckTxFromErr(err error) *abci.ResponseCheckTx {
|
||||||
var code = CodeInternalError
|
var code = CodeInternalError
|
||||||
var log = codeToDefaultLog(code)
|
var log = CodeToDefaultLog(code)
|
||||||
|
|
||||||
abciErr, ok := getABCIError(err)
|
abciErr, ok := getABCIError(err)
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -53,7 +49,7 @@ func ResponseCheckTxFromErr(err error) *abci.ResponseCheckTx {
|
||||||
Code: code,
|
Code: code,
|
||||||
Data: nil,
|
Data: nil,
|
||||||
Log: log,
|
Log: log,
|
||||||
Gas: 0, // TODO
|
// Gas: 0, // TODO
|
||||||
Fee: 0, // TODO
|
// Fee: 0, // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -174,11 +172,11 @@ func ABCIErrorCause(err error) ABCIError {
|
||||||
}
|
}
|
||||||
errCause := cause.Cause()
|
errCause := cause.Cause()
|
||||||
if errCause == nil || errCause == err {
|
if errCause == nil || errCause == err {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
err = errCause
|
err = errCause
|
||||||
}
|
}
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identitical to pkg/errors.Cause, except handles .Cause()
|
// Identitical to pkg/errors.Cause, except handles .Cause()
|
||||||
|
|
|
@ -28,7 +28,7 @@ type rootMultiStore struct {
|
||||||
|
|
||||||
var _ CommitMultiStore = (*rootMultiStore)(nil)
|
var _ CommitMultiStore = (*rootMultiStore)(nil)
|
||||||
|
|
||||||
func NewMultiStore(db dbm.DB) *rootMultiStore {
|
func NewCommitMultiStore(db dbm.DB) *rootMultiStore {
|
||||||
return &rootMultiStore{
|
return &rootMultiStore{
|
||||||
db: db,
|
db: db,
|
||||||
nextVersion: 0,
|
nextVersion: 0,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -136,7 +136,7 @@ func (coins Coins) IsGTE(coinsB Coins) bool {
|
||||||
if len(diff) == 0 {
|
if len(diff) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return diff.IsNonnegative()
|
return diff.IsNotNegative()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsZero returns true if there are no coins
|
// IsZero returns true if there are no coins
|
||||||
|
@ -171,9 +171,9 @@ func (coins Coins) IsPositive() bool {
|
||||||
return true
|
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)
|
// (even no coins is true here)
|
||||||
func (coins Coins) IsNonnegative() bool {
|
func (coins Coins) IsNotNegative() bool {
|
||||||
if len(coins) == 0 {
|
if len(coins) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package types
|
||||||
import crypto "github.com/tendermint/go-crypto"
|
import crypto "github.com/tendermint/go-crypto"
|
||||||
|
|
||||||
type StdSignature struct {
|
type StdSignature struct {
|
||||||
|
crypto.PubKey // optional
|
||||||
crypto.Signature
|
crypto.Signature
|
||||||
Sequence int64
|
Sequence int64
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Msg interface {
|
||||||
// Signers returns the addrs of signers that must sign.
|
// Signers returns the addrs of signers that must sign.
|
||||||
// CONTRACT: All signatures must be present to be valid.
|
// CONTRACT: All signatures must be present to be valid.
|
||||||
// CONTRACT: Returns addrs in some deterministic order.
|
// CONTRACT: Returns addrs in some deterministic order.
|
||||||
Signers() [][]byte
|
Signers() []Address
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tx interface {
|
type Tx interface {
|
||||||
|
|
|
@ -24,10 +24,10 @@ const (
|
||||||
contextKeyAccount contextKey = iota
|
contextKeyAccount contextKey = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetAccount(ctx types.Context, account Account) types.Context {
|
func SetAccount(ctx types.Context, account types.Account) types.Context {
|
||||||
return ctx.WithValueSDK(contextKeyAccount, account)
|
return ctx.WithValueUnsafe(contextKeyAccount, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAccount(ctx types.Context) Account {
|
func GetAccount(ctx types.Context) types.Account {
|
||||||
return ctx.Value(contextKeyAccount).(Account)
|
return ctx.Value(contextKeyAccount).(types.Account)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,15 +1,9 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import crypto "github.com/tendermint/go-crypto"
|
import (
|
||||||
|
crypto "github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
type Account interface {
|
type SetPubKeyer interface {
|
||||||
Get(key interface{}) (value interface{})
|
SetPubKey(crypto.PubKey)
|
||||||
|
|
||||||
Address() []byte
|
|
||||||
PubKey() crypto.PubKey
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountStore interface {
|
|
||||||
GetAccount(addr []byte) Account
|
|
||||||
SetAccount(acc Account)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,6 @@
|
||||||
package coin
|
package coin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/errors"
|
"github.com/cosmos/cosmos-sdk/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,7 +10,9 @@ const (
|
||||||
CodeInvalidInput uint32 = 101
|
CodeInvalidInput uint32 = 101
|
||||||
CodeInvalidOutput uint32 = 102
|
CodeInvalidOutput uint32 = 102
|
||||||
CodeInvalidAddress uint32 = 103
|
CodeInvalidAddress uint32 = 103
|
||||||
CodeUnknownAddress uint32 = 103
|
CodeUnknownAddress uint32 = 104
|
||||||
|
CodeInsufficientCoins uint32 = 105
|
||||||
|
CodeInvalidCoins uint32 = 106
|
||||||
CodeUnknownRequest uint32 = errors.CodeUnknownRequest
|
CodeUnknownRequest uint32 = errors.CodeUnknownRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +27,10 @@ func codeToDefaultLog(code uint32) string {
|
||||||
return "Invalid address"
|
return "Invalid address"
|
||||||
case CodeUnknownAddress:
|
case CodeUnknownAddress:
|
||||||
return "Unknown address"
|
return "Unknown address"
|
||||||
|
case CodeInsufficientCoins:
|
||||||
|
return "Insufficient coins"
|
||||||
|
case CodeInvalidCoins:
|
||||||
|
return "Invalid coins"
|
||||||
case CodeUnknownRequest:
|
case CodeUnknownRequest:
|
||||||
return "Unknown request"
|
return "Unknown request"
|
||||||
default:
|
default:
|
||||||
|
@ -53,6 +57,14 @@ func ErrUnknownAddress(log string) error {
|
||||||
return newError(CodeUnknownAddress, log)
|
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 {
|
func ErrUnknownRequest(log string) error {
|
||||||
return newError(CodeUnknownRequest, log)
|
return newError(CodeUnknownRequest, log)
|
||||||
}
|
}
|
||||||
|
@ -60,11 +72,11 @@ func ErrUnknownRequest(log string) error {
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
// Misc
|
// Misc
|
||||||
|
|
||||||
func logOrDefault(log string, code uint32) string {
|
func logOrDefaultLog(log string, code uint32) string {
|
||||||
if log != "" {
|
if log != "" {
|
||||||
return log
|
return log
|
||||||
} else {
|
} else {
|
||||||
return codeToDefaultLog
|
return codeToDefaultLog(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}*/
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue