initial refactor sketch

This commit is contained in:
rhuairahrighairigh 2018-08-24 19:18:41 -04:00
parent 3b325798c2
commit 3549c75474
8 changed files with 186 additions and 139 deletions

View File

@ -4,12 +4,7 @@ Simplifications:
- unidirectional paychans - unidirectional paychans
- no top ups or partial withdrawals (only opening and closing) - no top ups or partial withdrawals (only opening and closing)
- no protection against fund lock up from dissapearing receiver
TODO TODO
- fix issue with multisig accounts and sequence numbers - chnge module name to "channel"?
- create a nicer paychan store key for querying (and implement query)
- expand client code
- tidy up - add return tags
- start removing simplifications, refactor

View File

@ -33,6 +33,7 @@ import (
// send paychan payment // send paychan payment
// get balance from receiver // get balance from receiver
/*
func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command { func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command {
flagTo := "to" flagTo := "to"
flagAmount := "amount" flagAmount := "amount"
@ -297,3 +298,4 @@ func EnsureSignBuild(ctx context.CoreContext, name string, msg sdk.Msg, cdc *wir
} }
return txBytes, nil return txBytes, nil
} }
*/

View File

@ -7,6 +7,7 @@ import (
//"github.com/cosmos/cosmos-sdk/wire" //"github.com/cosmos/cosmos-sdk/wire"
) )
/*
// RegisterRoutes registers paychan-related REST handlers to a router // RegisterRoutes registers paychan-related REST handlers to a router
func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
//r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, ctx)).Methods("POST") //r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, ctx)).Methods("POST")
@ -20,3 +21,4 @@ func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb
// get balance from receiver // get balance from receiver
// get balance from local storage // get balance from local storage
// handle incoming payment // handle incoming payment
*/

View File

@ -0,0 +1,13 @@
package paychan
import ()
func EndBlocker(ctx sdk.Context k Keeper) sdk.Tags {
// Iterate through submittedUpdates and for each
// if current block height >= executionDate
// k.CloseChannel(...)
tags := sdk.NewTags()
return tags
}

View File

@ -12,8 +12,8 @@ func NewHandler(k Keeper) sdk.Handler {
switch msg := msg.(type) { switch msg := msg.(type) {
case MsgCreate: case MsgCreate:
return handleMsgCreate(ctx, k, msg) return handleMsgCreate(ctx, k, msg)
case MsgClose: case MsgSubmitUpdate:
return handleMsgClose(ctx, k, msg) return handleMsgSubmitUpdate(ctx, k, msg)
default: default:
errMsg := "Unrecognized paychan Msg type: " + reflect.TypeOf(msg).Name() errMsg := "Unrecognized paychan Msg type: " + reflect.TypeOf(msg).Name()
return sdk.ErrUnknownRequest(errMsg).Result() return sdk.ErrUnknownRequest(errMsg).Result()
@ -21,11 +21,10 @@ func NewHandler(k Keeper) sdk.Handler {
} }
} }
// Handle CreateMsg. // Handle MsgCreate
// Leaves validation to the keeper methods. // Leaves validation to the keeper methods.
func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgCreate) sdk.Result { func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgCreate) sdk.Result {
// TODO maybe remove tags for first version tags, err := k.CreateChannel(ctx, msg.Participants[0], msg.Participants[len(msg.Participants)-1], msg.Coins)
tags, err := k.CreatePaychan(ctx, msg.Sender, msg.Receiver, msg.Amount)
if err != nil { if err != nil {
return err.Result() return err.Result()
} }
@ -35,15 +34,19 @@ func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgCreate) sdk.Result {
} }
} }
// Handle CloseMsg. // Handle MsgSubmitUpdate
// Leaves validation to the keeper methods. // Leaves validation to the keeper methods.
func handleMsgClose(ctx sdk.Context, k Keeper, msg MsgClose) sdk.Result { func handleMsgSubmitUpdate(ctx sdk.Context, k Keeper, msg MsgSubmitUpdate) sdk.Result {
// TODO maybe remove tags for first version
tags, err := k.ClosePaychan(ctx, msg.Sender, msg.Receiver, msg.Id, msg.ReceiverAmount) // if only sender sig then
tags, err := k.InitChannelCloseBySender()
// else (if there are both)
tags, err := k.ChannelCloseByReceiver()
if err != nil { if err != nil {
return err.Result() return err.Result()
} }
// These tags can be used to subscribe to channel closures // These tags can be used by clients to subscribe to channel close attempts
return sdk.Result{ return sdk.Result{
Tags: tags, Tags: tags,
} }

View File

@ -8,20 +8,19 @@ import (
"github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/bank"
) )
// keeper of the paychan store // Keeper of the paychan store
// Handles validation internally. Does not rely on calling code to do validation. // Handles validation internally. Does not rely on calling code to do validation.
// Aim to keep public methids safe, private ones not necessaily. // Aim to keep public methods safe, private ones not necessaily.
type Keeper struct { type Keeper struct {
storeKey sdk.StoreKey storeKey sdk.StoreKey
cdc *wire.Codec // needed to serialize objects before putting them in the store cdc *wire.Codec // needed to serialize objects before putting them in the store
coinKeeper bank.Keeper coinKeeper bank.Keeper
// codespace // TODO investigate codespace
//codespace sdk.CodespaceType // ?? //codespace sdk.CodespaceType
} }
// Called when creating new app. // Called when creating new app.
//func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper {
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper) Keeper { func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper) Keeper {
keeper := Keeper{ keeper := Keeper{
storeKey: key, storeKey: key,
@ -33,7 +32,7 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper) Keeper {
} }
// bunch of business logic ... // bunch of business logic ...
/*
// Reteive a payment channel struct from the blockchain store. // Reteive a payment channel struct from the blockchain store.
// They are indexed by a concatenation of sender address, receiver address, and an integer. // They are indexed by a concatenation of sender address, receiver address, and an integer.
func (k Keeper) GetPaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, id int64) (Paychan, bool) { func (k Keeper) GetPaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, id int64) (Paychan, bool) {
@ -60,60 +59,91 @@ func (k Keeper) setPaychan(ctx sdk.Context, pych Paychan) {
pychKey := paychanKey(pych.Sender, pych.Receiver, pych.Id) pychKey := paychanKey(pych.Sender, pych.Receiver, pych.Id)
store.Set(pychKey, bz) // panics if something goes wrong store.Set(pychKey, bz) // panics if something goes wrong
} }
*/
// Create a new payment channel and lock up sender funds. // Create a new payment channel and lock up sender funds.
func (k Keeper) CreatePaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, amount sdk.Coins) (sdk.Tags, sdk.Error) { func (k Keeper) CreatePaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, coins sdk.Coins) (sdk.Tags, sdk.Error) {
// TODO move validation somewhere nicer // TODO do validation and maybe move somewhere nicer
// args present /*
if len(sender) == 0 { // args present
return nil, sdk.ErrInvalidAddress(sender.String()) if len(sender) == 0 {
} return nil, sdk.ErrInvalidAddress(sender.String())
if len(receiver) == 0 { }
return nil, sdk.ErrInvalidAddress(receiver.String()) if len(receiver) == 0 {
} return nil, sdk.ErrInvalidAddress(receiver.String())
if len(amount) == 0 { }
return nil, sdk.ErrInvalidCoins(amount.String()) if len(amount) == 0 {
} return nil, sdk.ErrInvalidCoins(amount.String())
// Check if coins are sorted, non zero, positive }
if !amount.IsValid() { // Check if coins are sorted, non zero, positive
return nil, sdk.ErrInvalidCoins(amount.String()) if !amount.IsValid() {
} return nil, sdk.ErrInvalidCoins(amount.String())
if !amount.IsPositive() { }
return nil, sdk.ErrInvalidCoins(amount.String()) if !amount.IsPositive() {
} return nil, sdk.ErrInvalidCoins(amount.String())
// sender should exist already as they had to sign. }
// receiver address exists. am is the account mapper in the coin keeper. // sender should exist already as they had to sign.
// TODO automatically create account if not present? // receiver address exists. am is the account mapper in the coin keeper.
// TODO remove as account mapper not available to this pkg // TODO automatically create account if not present?
//if k.coinKeeper.am.GetAccount(ctx, receiver) == nil { // TODO remove as account mapper not available to this pkg
// return nil, sdk.ErrUnknownAddress(receiver.String()) //if k.coinKeeper.am.GetAccount(ctx, receiver) == nil {
//} // return nil, sdk.ErrUnknownAddress(receiver.String())
//}
// sender has enough coins - done in Subtract method // sender has enough coins - done in Subtract method
// TODO check if sender and receiver different? // TODO check if sender and receiver different?
*/
// Calculate next id (just num of existing paychans - zero indexed) // Calculate next id
id := int64(len(k.GetPaychans(sender, receiver))) id := k.getNewChannelID(ctx)
// subtract coins from sender // subtract coins from sender
_, tags, err := k.coinKeeper.SubtractCoins(ctx, sender, amount) _, tags, err := k.coinKeeper.SubtractCoins(ctx, sender, coins)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// create new Paychan struct // create new Paychan struct
pych := Paychan{ channel := Channel{
Sender: sender, ID: id
Receiver: receiver, Participants: [2]sdk.AccAddress{sender, receiver},
Id: id, Coins: coins,
Balance: amount,
} }
// save to db // save to db
k.setPaychan(ctx, pych) k.setChannel(ctx, channel)
// TODO create tags // TODO create tags
//tags := sdk.NewTags() //tags := sdk.NewTags()
return tags, err return tags, err
} }
/* This is how gov manages creating unique IDs. Needs to be deterministic - can't use UUID
func (keeper Keeper) getNewChannelID(ctx sdk.Context) (channelID int64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
}
keeper.cdc.MustUnmarshalBinary(bz, &proposalID)
bz = keeper.cdc.MustMarshalBinary(proposalID + 1)
store.Set(KeyNextProposalID, bz)
return proposalID, nil
*/
func (k Keeper) ChannelCloseByReceiver() () {
// Validate inputs
// k.closeChannel
}
func (k Keeper) InitChannelCloseBySender() () {
// Validate inputs
// Create SubmittedUpdate from Update and add to queue
}
func (k Keeper) closeChannel() () {
// Remove corresponding SubmittedUpdate from queue (if it exist)
// Add coins to sender and receiver
// Delete Channel
}
/*
// Close a payment channel and distribute funds to participants. // Close a payment channel and distribute funds to participants.
func (k Keeper) ClosePaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, id int64, receiverAmount sdk.Coins) (sdk.Tags, sdk.Error) { func (k Keeper) ClosePaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, id int64, receiverAmount sdk.Coins) (sdk.Tags, sdk.Error) {
if len(sender) == 0 { if len(sender) == 0 {
@ -190,3 +220,4 @@ func (k Keeper) GetPaychans(sender sdk.Address, receiver sdk.Address) []Paychan
} }
// maybe getAllPaychans(sender sdk.address) []Paychan // maybe getAllPaychans(sender sdk.address) []Paychan
*/

View File

@ -5,81 +5,91 @@ import (
"strconv" "strconv"
) )
// Paychan Type /* CHANNEL TYPES */
// Used to represent paychan in keeper module and to serialize.
// probably want to convert this to a general purpose "state" // Used to represent a channel in the keeper module.
type Paychan struct { // Participants is limited to two as currently these are unidirectional channels.
Sender sdk.Address // Last participant is designated as receiver.
Receiver sdk.Address type Channel struct {
Id int64 ID int64
Balance sdk.Coins Participants [2]sdk.AccAddress
Coins sdk.Coins
} }
// Message Types // The data that is passed between participants as payments, and submitted to the blockchain to close a channel.
type Update struct {
ChannelID int64
CoinsUpdate //TODO type
Sequence int64
sig // TODO type, only sender needs to sign
}
// Message implement the sdk.Msg interface: // An update that has been submitted to the blockchain, but not yet acted on.
type SubmittedUpdate {
Update
executionDate int64 // BlockHeight
}
// type Msg interface { /* MESSAGE TYPES */
/*
Message implement the sdk.Msg interface:
type Msg interface {
// // Return the message type. // Return the message type.
// // Must be alphanumeric or empty. // Must be alphanumeric or empty.
// Type() string Type() string
// // Get the canonical byte representation of the Msg. // Get the canonical byte representation of the Msg.
// GetSignBytes() []byte GetSignBytes() []byte
// // ValidateBasic does a simple validation check that // ValidateBasic does a simple validation check that
// // doesn't require access to any other information. // doesn't require access to any other information.
// ValidateBasic() Error ValidateBasic() Error
// // 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.
// GetSigners() []Address GetSigners() []Address
// } }
*/
// A message to create a payment channel. // A message to create a payment channel.
type MsgCreate struct { type MsgCreate struct {
// maybe just wrap a paychan struct Participants [2]sdk.AccAddress
Sender sdk.Address Coins sdk.Coins
Receiver sdk.Address
Amount sdk.Coins
} }
// Create a new message. //Create a new message.
// Called in client code when constructing transaction from cli args to send to the network. /*
// maybe just a placeholder for more advanced future functionality? Called in client code when constructing transaction from cli args to send to the network.
// func (msg CreatMsg) NewMsgCreate(sender sdk.Address, receiver sdk.Address, amount sdk.Coins) MsgCreate { maybe just a placeholder for more advanced future functionality?
// return MsgCreate{ func (msg CreatMsg) NewMsgCreate(sender sdk.Address, receiver sdk.Address, amount sdk.Coins) MsgCreate {
// sender return MsgCreate{
// receiver sender
// amount receiver
// } amount
// } }
}
*/
func (msg MsgCreate) Type() string { return "paychan" } func (msg MsgCreate) Type() string { return "paychan" }
func (msg MsgCreate) GetSignBytes() []byte { func (msg MsgCreate) GetSignBytes() []byte {
// TODO create msgCdc in wire.go // TODO create msgCdc in wire.go
b, err := msgCdc.MarshalJSON(struct { bz, err := msgCdc.Marshal(msg)
SenderAddr string `json:"sender_addr"`
ReceiverAddr string `json:"receiver_addr"`
Amount sdk.Coins `json:"amount"`
}{
SenderAddr: sdk.MustBech32ifyAcc(msg.Sender),
ReceiverAddr: sdk.MustBech32ifyAcc(msg.Receiver),
Amount: msg.Amount,
})
if err != nil { if err != nil {
panic(err) panic(err)
} }
return b return MustSortJSON(bz)
} }
func (msg MsgCreate) ValidateBasic() sdk.Error { func (msg MsgCreate) ValidateBasic() sdk.Error {
// Validate msg as an optimisation to avoid all validation going to keeper. It's run before the sigs are checked by the auth module. // Validate msg as an optimisation to avoid all validation going to keeper. It's run before the sigs are checked by the auth module.
// Validate without external information (such as account balance) // Validate without external information (such as account balance)
//TODO implement
/*
// check if all fields present / not 0 valued // check if all fields present / not 0 valued
if len(msg.Sender) == 0 { if len(msg.Sender) == 0 {
return sdk.ErrInvalidAddress(msg.Sender.String()) return sdk.ErrInvalidAddress(msg.Sender.String())
@ -98,55 +108,42 @@ func (msg MsgCreate) ValidateBasic() sdk.Error {
return sdk.ErrInvalidCoins(msg.Amount.String()) return sdk.ErrInvalidCoins(msg.Amount.String())
} }
// TODO check if Address valid? // TODO check if Address valid?
*/
return nil return nil
} }
func (msg MsgCreate) GetSigners() []sdk.Address { func (msg MsgCreate) GetSigners() []sdk.Address {
// Only sender must sign to create a paychan // Only sender must sign to create a paychan
return []sdk.Address{msg.Sender} return []sdk.AccAddress{msg.Participants[0]} // select sender address
} }
// A message to close a payment channel. // A message to close a payment channel.
type MsgClose struct { type MsgSubmitUpdate struct {
// have to include sender and receiver in msg explicitly (rather than just universal paychanID) Update
// this gives ability to verify signatures with no external information // might need a "signer" to be able to say who is signing this as either can or not
Sender sdk.Address
Receiver sdk.Address
Id int64 // TODO is another int type better?
ReceiverAmount sdk.Coins // amount the receiver should get - sender amount implicit with paychan balance
} }
// func (msg MsgClose) NewMsgClose(sender sdk.Address, receiver sdk.Address, id integer, receiverAmount sdk.Coins) MsgClose { // func (msg MsgSubmitUpdate) NewMsgSubmitUpdate(update Update) MsgSubmitUpdate {
// return MsgClose{ // return MsgSubmitUpdate{
// sender // update
// receiver
// id
// receiverAmount
// } // }
// } // }
func (msg MsgClose) Type() string { return "paychan" } func (msg MsgSubmitUpdate) Type() string { return "paychan" }
func (msg MsgClose) GetSignBytes() []byte { func (msg MsgSubmitUpdate) GetSignBytes() []byte {
// TODO create msgCdc in wire.go // TODO create msgCdc in wire.go
b, err := msgCdc.MarshalJSON(struct { bz, err := msgCdc.Marshal(msg)
SenderAddr string `json:"sender_addr"`
ReceiverAddr string `json:"receiver_addr"`
Id int64 `json:"id"`
ReceiverAmount sdk.Coins `json:"receiver_amount"`
}{
SenderAddr: sdk.MustBech32ifyAcc(msg.Sender),
ReceiverAddr: sdk.MustBech32ifyAcc(msg.Receiver),
Id: msg.Id,
ReceiverAmount: msg.ReceiverAmount,
})
if err != nil { if err != nil {
panic(err) panic(err)
} }
return b return MustSortJSON(bz)
} }
func (msg MsgClose) ValidateBasic() sdk.Error { func (msg MsgSubmitUpdate) ValidateBasic() sdk.Error {
// TODO implement
/*
// check if all fields present / not 0 valued // check if all fields present / not 0 valued
if len(msg.Sender) == 0 { if len(msg.Sender) == 0 {
return sdk.ErrInvalidAddress(msg.Sender.String()) return sdk.ErrInvalidAddress(msg.Sender.String())
@ -169,10 +166,12 @@ func (msg MsgClose) ValidateBasic() sdk.Error {
return sdk.ErrInvalidCoins(msg.ReceiverAmount.String()) return sdk.ErrInvalidCoins(msg.ReceiverAmount.String())
} }
// TODO check if Address valid? // TODO check if Address valid?
*/
return nil return nil
} }
func (msg MsgClose) GetSigners() []sdk.Address { func (msg MsgSubmitUpdate) GetSigners() []sdk.Address {
// Both sender and receiver must sign in order to close a channel // Signing not strictly necessary as signatures contained within the channel update.
return []sdk.Address{msg.Sender, msg.Receiver} // TODO add signature by submitting address
return []sdk.Address{}
} }

View File

@ -4,6 +4,7 @@ import (
"github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/wire"
) )
/*
func RegisterWire(cdc *wire.Codec) { func RegisterWire(cdc *wire.Codec) {
cdc.RegisterConcrete(MsgCreate{}, "paychan/MsgCreate", nil) cdc.RegisterConcrete(MsgCreate{}, "paychan/MsgCreate", nil)
cdc.RegisterConcrete(MsgClose{}, "paychan/MsgClose", nil) cdc.RegisterConcrete(MsgClose{}, "paychan/MsgClose", nil)
@ -16,3 +17,4 @@ func init() {
// TODO is this needed? // TODO is this needed?
//wire.RegisterCrypto(msgCdc) //wire.RegisterCrypto(msgCdc)
} }
*/