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 19:17:17 -08:00
parent 620bdf409f
commit ba2b4f0f21
28 changed files with 698 additions and 717 deletions
+22 -46
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
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...
}
}
+20 -11
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)
}
-59
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
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)
}
+1 -1
View File
@@ -1,5 +1,5 @@
//nolint
package coinstore
package bank
import (
"fmt"
+1 -1
View File
@@ -1,4 +1,4 @@
package coinstore
package bank
import (
"github.com/cosmos/cosmos-sdk/types"
+51
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
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
}
+1 -1
View File
@@ -1,4 +1,4 @@
package coinstore
package bank
import (
"testing"
-74
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
}
-222
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
}