diff --git a/internal/x/paychan/README.md b/internal/x/paychan/README.md index 68ec7016..c3812ab3 100644 --- a/internal/x/paychan/README.md +++ b/internal/x/paychan/README.md @@ -4,12 +4,7 @@ Simplifications: - unidirectional paychans - no top ups or partial withdrawals (only opening and closing) - - no protection against fund lock up from dissapearing receiver TODO - - fix issue with multisig accounts and sequence numbers - - create a nicer paychan store key for querying (and implement query) - - expand client code - - tidy up - add return tags - - start removing simplifications, refactor + - chnge module name to "channel"? diff --git a/internal/x/paychan/client/cmd/cmd.go b/internal/x/paychan/client/cmd/cmd.go index dd4ccbfc..f262cbf8 100644 --- a/internal/x/paychan/client/cmd/cmd.go +++ b/internal/x/paychan/client/cmd/cmd.go @@ -33,6 +33,7 @@ import ( // send paychan payment // get balance from receiver +/* func CreatePaychanCmd(cdc *wire.Codec) *cobra.Command { flagTo := "to" flagAmount := "amount" @@ -297,3 +298,4 @@ func EnsureSignBuild(ctx context.CoreContext, name string, msg sdk.Msg, cdc *wir } return txBytes, nil } +*/ diff --git a/internal/x/paychan/client/rest/rest.go b/internal/x/paychan/client/rest/rest.go index 3565d70a..d25e01cb 100644 --- a/internal/x/paychan/client/rest/rest.go +++ b/internal/x/paychan/client/rest/rest.go @@ -7,6 +7,7 @@ import ( //"github.com/cosmos/cosmos-sdk/wire" ) +/* // RegisterRoutes registers paychan-related REST handlers to a router 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") @@ -20,3 +21,4 @@ func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb // get balance from receiver // get balance from local storage // handle incoming payment +*/ diff --git a/internal/x/paychan/endblocker.go b/internal/x/paychan/endblocker.go new file mode 100644 index 00000000..5a753a59 --- /dev/null +++ b/internal/x/paychan/endblocker.go @@ -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 +} \ No newline at end of file diff --git a/internal/x/paychan/handler.go b/internal/x/paychan/handler.go index cf649cbb..d639f99a 100644 --- a/internal/x/paychan/handler.go +++ b/internal/x/paychan/handler.go @@ -12,8 +12,8 @@ func NewHandler(k Keeper) sdk.Handler { switch msg := msg.(type) { case MsgCreate: return handleMsgCreate(ctx, k, msg) - case MsgClose: - return handleMsgClose(ctx, k, msg) + case MsgSubmitUpdate: + return handleMsgSubmitUpdate(ctx, k, msg) default: errMsg := "Unrecognized paychan Msg type: " + reflect.TypeOf(msg).Name() 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. func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgCreate) sdk.Result { - // TODO maybe remove tags for first version - tags, err := k.CreatePaychan(ctx, msg.Sender, msg.Receiver, msg.Amount) + tags, err := k.CreateChannel(ctx, msg.Participants[0], msg.Participants[len(msg.Participants)-1], msg.Coins) if err != nil { 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. -func handleMsgClose(ctx sdk.Context, k Keeper, msg MsgClose) sdk.Result { - // TODO maybe remove tags for first version - tags, err := k.ClosePaychan(ctx, msg.Sender, msg.Receiver, msg.Id, msg.ReceiverAmount) +func handleMsgSubmitUpdate(ctx sdk.Context, k Keeper, msg MsgSubmitUpdate) sdk.Result { + + // if only sender sig then + tags, err := k.InitChannelCloseBySender() + // else (if there are both) + tags, err := k.ChannelCloseByReceiver() + if err != nil { 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{ Tags: tags, } diff --git a/internal/x/paychan/keeper.go b/internal/x/paychan/keeper.go index 11a50db2..d3648884 100644 --- a/internal/x/paychan/keeper.go +++ b/internal/x/paychan/keeper.go @@ -8,20 +8,19 @@ import ( "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. -// Aim to keep public methids safe, private ones not necessaily. +// Aim to keep public methods safe, private ones not necessaily. type Keeper struct { storeKey sdk.StoreKey cdc *wire.Codec // needed to serialize objects before putting them in the store coinKeeper bank.Keeper - // codespace - //codespace sdk.CodespaceType // ?? + // TODO investigate codespace + //codespace sdk.CodespaceType } // 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 { keeper := Keeper{ storeKey: key, @@ -33,7 +32,7 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper) Keeper { } // bunch of business logic ... - +/* // Reteive a payment channel struct from the blockchain store. // 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) { @@ -60,60 +59,91 @@ func (k Keeper) setPaychan(ctx sdk.Context, pych Paychan) { pychKey := paychanKey(pych.Sender, pych.Receiver, pych.Id) store.Set(pychKey, bz) // panics if something goes wrong } +*/ // 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) { - // TODO move validation somewhere nicer - // args present - if len(sender) == 0 { - return nil, sdk.ErrInvalidAddress(sender.String()) - } - if len(receiver) == 0 { - return nil, sdk.ErrInvalidAddress(receiver.String()) - } - if len(amount) == 0 { - return nil, sdk.ErrInvalidCoins(amount.String()) - } - // Check if coins are sorted, non zero, positive - if !amount.IsValid() { - 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. - // TODO automatically create account if not present? - // TODO remove as account mapper not available to this pkg - //if k.coinKeeper.am.GetAccount(ctx, receiver) == nil { - // return nil, sdk.ErrUnknownAddress(receiver.String()) - //} +func (k Keeper) CreatePaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, coins sdk.Coins) (sdk.Tags, sdk.Error) { + // TODO do validation and maybe move somewhere nicer + /* + // args present + if len(sender) == 0 { + return nil, sdk.ErrInvalidAddress(sender.String()) + } + if len(receiver) == 0 { + return nil, sdk.ErrInvalidAddress(receiver.String()) + } + if len(amount) == 0 { + return nil, sdk.ErrInvalidCoins(amount.String()) + } + // Check if coins are sorted, non zero, positive + if !amount.IsValid() { + 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. + // TODO automatically create account if not present? + // TODO remove as account mapper not available to this pkg + //if k.coinKeeper.am.GetAccount(ctx, receiver) == nil { + // return nil, sdk.ErrUnknownAddress(receiver.String()) + //} - // sender has enough coins - done in Subtract method - // TODO check if sender and receiver different? + // sender has enough coins - done in Subtract method + // TODO check if sender and receiver different? + */ - // Calculate next id (just num of existing paychans - zero indexed) - id := int64(len(k.GetPaychans(sender, receiver))) + // Calculate next id + id := k.getNewChannelID(ctx) // subtract coins from sender - _, tags, err := k.coinKeeper.SubtractCoins(ctx, sender, amount) + _, tags, err := k.coinKeeper.SubtractCoins(ctx, sender, coins) if err != nil { return nil, err } // create new Paychan struct - pych := Paychan{ - Sender: sender, - Receiver: receiver, - Id: id, - Balance: amount, + channel := Channel{ + ID: id + Participants: [2]sdk.AccAddress{sender, receiver}, + Coins: coins, } // save to db - k.setPaychan(ctx, pych) + k.setChannel(ctx, channel) // TODO create tags //tags := sdk.NewTags() 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. 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 { @@ -190,3 +220,4 @@ func (k Keeper) GetPaychans(sender sdk.Address, receiver sdk.Address) []Paychan } // maybe getAllPaychans(sender sdk.address) []Paychan +*/ diff --git a/internal/x/paychan/types.go b/internal/x/paychan/types.go index 832495d3..bbe6738b 100644 --- a/internal/x/paychan/types.go +++ b/internal/x/paychan/types.go @@ -5,81 +5,91 @@ import ( "strconv" ) -// Paychan Type -// Used to represent paychan in keeper module and to serialize. -// probably want to convert this to a general purpose "state" -type Paychan struct { - Sender sdk.Address - Receiver sdk.Address - Id int64 - Balance sdk.Coins +/* CHANNEL TYPES */ + +// Used to represent a channel in the keeper module. +// Participants is limited to two as currently these are unidirectional channels. +// Last participant is designated as receiver. +type Channel struct { + ID int64 + 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. -// // Must be alphanumeric or empty. -// Type() string + // Return the message type. + // Must be alphanumeric or empty. + Type() string -// // Get the canonical byte representation of the Msg. -// GetSignBytes() []byte + // Get the canonical byte representation of the Msg. + GetSignBytes() []byte -// // ValidateBasic does a simple validation check that -// // doesn't require access to any other information. -// ValidateBasic() Error + // ValidateBasic does a simple validation check that + // doesn't require access to any other information. + ValidateBasic() Error -// // 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. -// GetSigners() []Address -// } + // 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. + GetSigners() []Address +} +*/ // A message to create a payment channel. type MsgCreate struct { - // maybe just wrap a paychan struct - Sender sdk.Address - Receiver sdk.Address - Amount sdk.Coins + Participants [2]sdk.AccAddress + Coins sdk.Coins } -// 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? -// func (msg CreatMsg) NewMsgCreate(sender sdk.Address, receiver sdk.Address, amount sdk.Coins) MsgCreate { -// return MsgCreate{ -// sender -// receiver -// amount -// } -// } +//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? +func (msg CreatMsg) NewMsgCreate(sender sdk.Address, receiver sdk.Address, amount sdk.Coins) MsgCreate { + return MsgCreate{ + sender + receiver + amount + } +} +*/ func (msg MsgCreate) Type() string { return "paychan" } func (msg MsgCreate) GetSignBytes() []byte { // TODO create msgCdc in wire.go - b, err := msgCdc.MarshalJSON(struct { - 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, - }) + bz, err := msgCdc.Marshal(msg) if err != nil { panic(err) } - return b + return MustSortJSON(bz) } 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 without external information (such as account balance) + //TODO implement + + /* // check if all fields present / not 0 valued if len(msg.Sender) == 0 { return sdk.ErrInvalidAddress(msg.Sender.String()) @@ -98,55 +108,42 @@ func (msg MsgCreate) ValidateBasic() sdk.Error { return sdk.ErrInvalidCoins(msg.Amount.String()) } // TODO check if Address valid? + */ return nil } func (msg MsgCreate) GetSigners() []sdk.Address { // 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. -type MsgClose struct { - // have to include sender and receiver in msg explicitly (rather than just universal paychanID) - // this gives ability to verify signatures with no external information - 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 +type MsgSubmitUpdate struct { + Update + // might need a "signer" to be able to say who is signing this as either can or not } -// func (msg MsgClose) NewMsgClose(sender sdk.Address, receiver sdk.Address, id integer, receiverAmount sdk.Coins) MsgClose { -// return MsgClose{ -// sender -// receiver -// id -// receiverAmount +// func (msg MsgSubmitUpdate) NewMsgSubmitUpdate(update Update) MsgSubmitUpdate { +// return MsgSubmitUpdate{ +// update // } // } -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 - b, err := msgCdc.MarshalJSON(struct { - 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, - }) + bz, err := msgCdc.Marshal(msg) if err != nil { 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 if len(msg.Sender) == 0 { return sdk.ErrInvalidAddress(msg.Sender.String()) @@ -169,10 +166,12 @@ func (msg MsgClose) ValidateBasic() sdk.Error { return sdk.ErrInvalidCoins(msg.ReceiverAmount.String()) } // TODO check if Address valid? + */ return nil } -func (msg MsgClose) GetSigners() []sdk.Address { - // Both sender and receiver must sign in order to close a channel - return []sdk.Address{msg.Sender, msg.Receiver} +func (msg MsgSubmitUpdate) GetSigners() []sdk.Address { + // Signing not strictly necessary as signatures contained within the channel update. + // TODO add signature by submitting address + return []sdk.Address{} } diff --git a/internal/x/paychan/wire.go b/internal/x/paychan/wire.go index dfb2d468..9d9e5eb3 100644 --- a/internal/x/paychan/wire.go +++ b/internal/x/paychan/wire.go @@ -4,6 +4,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" ) +/* func RegisterWire(cdc *wire.Codec) { cdc.RegisterConcrete(MsgCreate{}, "paychan/MsgCreate", nil) cdc.RegisterConcrete(MsgClose{}, "paychan/MsgClose", nil) @@ -16,3 +17,4 @@ func init() { // TODO is this needed? //wire.RegisterCrypto(msgCdc) } +*/