2018-07-08 22:09:07 +00:00
package paychan
2018-07-09 18:46:51 +00:00
import (
2018-09-01 17:16:56 +00:00
"bytes"
2018-08-28 03:48:48 +00:00
"fmt"
2018-07-10 13:56:04 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
2018-07-13 13:27:55 +00:00
"github.com/cosmos/cosmos-sdk/x/bank"
2018-07-09 18:46:51 +00:00
)
2018-08-24 23:18:41 +00:00
// Keeper of the paychan store
2018-07-11 21:02:07 +00:00
// Handles validation internally. Does not rely on calling code to do validation.
2018-08-24 23:18:41 +00:00
// Aim to keep public methods safe, private ones not necessaily.
2018-09-02 02:57:42 +00:00
// Keepers contain main business logic of the module.
2018-07-08 22:09:07 +00:00
type Keeper struct {
2018-07-13 13:27:55 +00:00
storeKey sdk . StoreKey
2018-07-10 13:56:04 +00:00
cdc * wire . Codec // needed to serialize objects before putting them in the store
2018-07-08 22:09:07 +00:00
coinKeeper bank . Keeper
2018-09-02 02:57:42 +00:00
//codespace sdk.CodespaceType TODO custom errors
2018-07-08 22:09:07 +00:00
}
2018-07-09 18:46:51 +00:00
// Called when creating new app.
2018-07-10 13:56:04 +00:00
func NewKeeper ( cdc * wire . Codec , key sdk . StoreKey , ck bank . Keeper ) Keeper {
2018-07-08 22:09:07 +00:00
keeper := Keeper {
2018-07-13 13:27:55 +00:00
storeKey : key ,
2018-07-10 13:56:04 +00:00
cdc : cdc ,
2018-07-08 22:09:07 +00:00
coinKeeper : ck ,
2018-07-09 18:46:51 +00:00
//codespace: codespace,
2018-07-08 22:09:07 +00:00
}
return keeper
}
2018-07-09 18:46:51 +00:00
// Create a new payment channel and lock up sender funds.
2018-08-28 03:48:48 +00:00
func ( k Keeper ) CreateChannel ( ctx sdk . Context , sender sdk . AccAddress , receiver sdk . AccAddress , coins sdk . Coins ) ( sdk . Tags , sdk . Error ) {
2018-09-01 17:16:56 +00:00
2018-09-02 02:57:42 +00:00
// Check addresses valid (Technicaly don't need to check sender address is valid as SubtractCoins checks)
2018-09-01 17:16:56 +00:00
if len ( sender ) == 0 {
return nil , sdk . ErrInvalidAddress ( sender . String ( ) )
}
if len ( receiver ) == 0 {
return nil , sdk . ErrInvalidAddress ( receiver . String ( ) )
}
// check coins are sorted and positive (disallow channels with zero balance)
if ! coins . IsValid ( ) {
return nil , sdk . ErrInvalidCoins ( coins . String ( ) )
}
if ! coins . IsPositive ( ) {
return nil , sdk . ErrInvalidCoins ( coins . String ( ) )
}
2018-08-24 23:18:41 +00:00
2018-07-08 22:09:07 +00:00
// subtract coins from sender
2018-08-24 23:18:41 +00:00
_ , tags , err := k . coinKeeper . SubtractCoins ( ctx , sender , coins )
2018-07-12 13:32:36 +00:00
if err != nil {
return nil , err
}
2018-08-28 03:48:48 +00:00
// Calculate next id
id := k . getNewChannelID ( ctx )
2018-07-12 13:32:36 +00:00
// create new Paychan struct
2018-08-24 23:18:41 +00:00
channel := Channel {
2018-08-28 03:48:48 +00:00
ID : id ,
Participants : [ 2 ] sdk . AccAddress { sender , receiver } ,
Coins : coins ,
2018-07-13 13:27:55 +00:00
}
2018-07-08 22:09:07 +00:00
// save to db
2018-08-24 23:18:41 +00:00
k . setChannel ( ctx , channel )
2018-07-10 13:56:04 +00:00
2018-08-27 21:58:58 +00:00
// TODO add to tags
2018-07-10 13:56:04 +00:00
return tags , err
2018-07-08 22:09:07 +00:00
}
2018-09-02 02:57:42 +00:00
// Initiate the close of a payment channel, subject to dispute period.
2018-08-28 03:48:48 +00:00
func ( k Keeper ) InitCloseChannelBySender ( ctx sdk . Context , update Update ) ( sdk . Tags , sdk . Error ) {
2018-08-27 21:58:58 +00:00
// This is roughly the default path for non unidirectional channels
2018-09-01 20:41:40 +00:00
// get the channel
channel , found := k . getChannel ( ctx , update . ChannelID )
if ! found {
return nil , sdk . ErrInternal ( "Channel doesn't exist" )
}
2018-09-01 23:29:51 +00:00
err := VerifyUpdate ( channel , update )
2018-09-01 17:16:56 +00:00
if err != nil {
return nil , err
}
2018-08-27 21:58:58 +00:00
2018-09-01 23:29:51 +00:00
q := k . getSubmittedUpdatesQueue ( ctx )
2018-08-27 21:58:58 +00:00
if q . Contains ( update . ChannelID ) {
// Someone has previously tried to update channel
2018-09-02 02:57:42 +00:00
2018-08-30 17:43:15 +00:00
// In bidirectional channels the new update is compared against existing and replaces it if it has a higher sequence number.
// existingSUpdate, found := k.getSubmittedUpdate(ctx, update.ChannelID)
// if !found {
// panic("can't find element in queue that should exist")
// }
// k.addToSubmittedUpdatesQueue(ctx, k.applyNewUpdate(existingSUpdate, update))
// However in unidirectional case, only the sender can close a channel this way. No clear need for them to be able to submit an update replacing a previous one they sent, so don't allow it.
// TODO tags
2018-09-02 02:57:42 +00:00
// TODO custom errors
2018-09-01 17:16:56 +00:00
sdk . ErrInternal ( "Sender can't submit an update for channel if one has already been submitted." )
2018-08-27 21:58:58 +00:00
} else {
2018-08-28 03:48:48 +00:00
// No one has tried to update channel
2018-08-27 21:58:58 +00:00
submittedUpdate := SubmittedUpdate {
2018-08-28 03:48:48 +00:00
Update : update ,
ExecutionTime : ctx . BlockHeight ( ) + ChannelDisputeTime , //TODO check what exactly BlockHeight refers to
2018-08-27 21:58:58 +00:00
}
2018-08-28 03:48:48 +00:00
k . addToSubmittedUpdatesQueue ( ctx , submittedUpdate )
2018-08-24 23:18:41 +00:00
}
2018-08-28 03:48:48 +00:00
tags := sdk . EmptyTags ( ) // TODO tags
return tags , nil
2018-08-27 21:58:58 +00:00
}
2018-08-24 23:18:41 +00:00
2018-09-02 02:57:42 +00:00
// Immediately close a channel.
2018-08-28 03:48:48 +00:00
func ( k Keeper ) CloseChannelByReceiver ( ctx sdk . Context , update Update ) ( sdk . Tags , sdk . Error ) {
2018-09-01 17:16:56 +00:00
2018-09-01 20:41:40 +00:00
// get the channel
channel , found := k . getChannel ( ctx , update . ChannelID )
if ! found {
return nil , sdk . ErrInternal ( "Channel doesn't exist" )
}
2018-09-01 23:29:51 +00:00
err := VerifyUpdate ( channel , update )
2018-09-01 17:16:56 +00:00
if err != nil {
return nil , err
}
2018-08-27 21:58:58 +00:00
// Check if there is an update in the queue already
2018-09-01 23:29:51 +00:00
q := k . getSubmittedUpdatesQueue ( ctx )
2018-08-27 21:58:58 +00:00
if q . Contains ( update . ChannelID ) {
// Someone has previously tried to update channel but receiver has final say
2018-08-28 03:48:48 +00:00
k . removeFromSubmittedUpdatesQueue ( ctx , update . ChannelID )
2018-08-27 21:58:58 +00:00
}
2018-08-28 03:48:48 +00:00
tags , err := k . closeChannel ( ctx , update )
return tags , err
2018-08-24 23:18:41 +00:00
}
2018-08-27 21:58:58 +00:00
// Main function that compare updates against each other.
2018-09-02 02:57:42 +00:00
// Pure function, Not needed in unidirectional case.
2018-08-30 17:43:15 +00:00
// func (k Keeper) applyNewUpdate(existingSUpdate SubmittedUpdate, proposedUpdate Update) SubmittedUpdate {
// var returnUpdate SubmittedUpdate
// if existingSUpdate.Sequence > proposedUpdate.Sequence {
// // update accepted
// returnUpdate = SubmittedUpdate{
// Update: proposedUpdate,
// ExecutionTime: existingSUpdate.ExecutionTime, // FIXME any new update proposal should be subject to full dispute period from submission
// }
// } else {
// // update rejected
// returnUpdate = existingSUpdate
// }
// return returnUpdate
// }
2018-08-24 23:18:41 +00:00
2018-09-01 23:29:51 +00:00
func VerifyUpdate ( channel Channel , update Update ) sdk . Error {
2018-09-01 20:41:40 +00:00
2018-09-01 17:16:56 +00:00
// Check the num of payout participants match channel participants
if len ( update . Payout ) != len ( channel . Participants ) {
return sdk . ErrInternal ( "Payout doesn't match number of channel participants" )
}
// Check each coins are valid
for _ , coins := range update . Payout {
if ! coins . IsValid ( ) {
return sdk . ErrInternal ( "Payout coins aren't formatted correctly" )
}
}
// Check payout coins are each not negative (can be zero though)
if ! update . Payout . IsNotNegative ( ) {
return sdk . ErrInternal ( "Payout cannot be negative" )
}
// Check payout sums to match channel.Coins
if ! channel . Coins . IsEqual ( update . Payout . Sum ( ) ) {
return sdk . ErrInternal ( "Payout amount doesn't match channel amount" )
}
// Check sender signature is OK
2018-09-01 23:29:51 +00:00
if ! verifySignatures ( channel , update ) {
2018-09-01 17:16:56 +00:00
return sdk . ErrInternal ( "Signature on update not valid" )
}
return nil
}
2018-09-02 02:57:42 +00:00
// unsafe close channel - doesn't check if update matches existing channel
// TODO make safer?
2018-08-28 03:48:48 +00:00
func ( k Keeper ) closeChannel ( ctx sdk . Context , update Update ) ( sdk . Tags , sdk . Error ) {
2018-08-29 03:45:26 +00:00
var err sdk . Error
2018-08-28 03:48:48 +00:00
var tags sdk . Tags
2018-08-27 21:58:58 +00:00
2018-09-01 17:16:56 +00:00
channel , _ := k . getChannel ( ctx , update . ChannelID )
// TODO check channel exists and participants matches update payout length
2018-08-24 23:18:41 +00:00
// Add coins to sender and receiver
2018-08-28 03:48:48 +00:00
// TODO check for possible errors first to avoid coins being half paid out?
2018-09-01 17:16:56 +00:00
for i , coins := range update . Payout {
_ , tags , err = k . coinKeeper . AddCoins ( ctx , channel . Participants [ i ] , coins )
2018-08-28 03:48:48 +00:00
if err != nil {
panic ( err )
}
2018-08-27 21:58:58 +00:00
}
2018-08-28 03:48:48 +00:00
k . deleteChannel ( ctx , update . ChannelID )
2018-08-27 21:58:58 +00:00
2018-08-28 03:48:48 +00:00
return tags , nil
}
2018-08-27 21:58:58 +00:00
2018-09-01 23:29:51 +00:00
func verifySignatures ( channel Channel , update Update ) bool {
2018-09-01 17:16:56 +00:00
// In non unidirectional channels there will be more than one signature to check
signBytes := update . GetSignBytes ( )
address := channel . Participants [ 0 ]
pubKey := update . Sigs [ 0 ] . PubKey
cryptoSig := update . Sigs [ 0 ] . CryptoSignature
// Check public key submitted with update signature matches the account address
valid := bytes . Equal ( pubKey . Address ( ) , address ) &&
// Check the signature is correct
pubKey . VerifyBytes ( signBytes , cryptoSig )
return valid
}
2018-09-02 02:57:42 +00:00
// =========================================== SUBMITTED UPDATES QUEUE
2018-08-27 21:58:58 +00:00
func ( k Keeper ) addToSubmittedUpdatesQueue ( ctx sdk . Context , sUpdate SubmittedUpdate ) {
// always overwrite prexisting values - leave paychan logic to higher levels
// get current queue
2018-09-01 23:29:51 +00:00
q := k . getSubmittedUpdatesQueue ( ctx )
2018-08-27 21:58:58 +00:00
// append ID to queue
2018-08-28 03:48:48 +00:00
if ! q . Contains ( sUpdate . ChannelID ) {
2018-08-27 21:58:58 +00:00
q = append ( q , sUpdate . ChannelID )
}
// set queue
2018-08-28 03:48:48 +00:00
k . setSubmittedUpdatesQueue ( ctx , q )
2018-08-27 21:58:58 +00:00
// store submittedUpdate
k . setSubmittedUpdate ( ctx , sUpdate )
}
2018-08-28 03:48:48 +00:00
func ( k Keeper ) removeFromSubmittedUpdatesQueue ( ctx sdk . Context , channelID ChannelID ) {
2018-08-27 21:58:58 +00:00
// get current queue
2018-09-01 23:29:51 +00:00
q := k . getSubmittedUpdatesQueue ( ctx )
2018-08-27 21:58:58 +00:00
// remove id
q . RemoveMatchingElements ( channelID )
// set queue
2018-08-28 03:48:48 +00:00
k . setSubmittedUpdatesQueue ( ctx , q )
2018-08-27 21:58:58 +00:00
// delete submittedUpdate
k . deleteSubmittedUpdate ( ctx , channelID )
}
2018-09-01 23:29:51 +00:00
func ( k Keeper ) getSubmittedUpdatesQueue ( ctx sdk . Context ) SubmittedUpdatesQueue {
2018-08-27 21:58:58 +00:00
// load from DB
store := ctx . KVStore ( k . storeKey )
bz := store . Get ( k . getSubmittedUpdatesQueueKey ( ) )
2018-09-01 23:29:51 +00:00
var suq SubmittedUpdatesQueue // if the submittedUpdatesQueue not found then return an empty one
if bz != nil {
// unmarshal
k . cdc . MustUnmarshalBinary ( bz , & suq )
2018-08-27 21:58:58 +00:00
}
2018-09-01 23:29:51 +00:00
return suq
2018-08-27 21:58:58 +00:00
}
2018-08-29 03:45:26 +00:00
func ( k Keeper ) setSubmittedUpdatesQueue ( ctx sdk . Context , suq SubmittedUpdatesQueue ) {
2018-08-27 21:58:58 +00:00
store := ctx . KVStore ( k . storeKey )
// marshal
2018-08-29 03:45:26 +00:00
bz := k . cdc . MustMarshalBinary ( suq )
2018-08-27 21:58:58 +00:00
// write to db
key := k . getSubmittedUpdatesQueueKey ( )
store . Set ( key , bz )
}
func ( k Keeper ) getSubmittedUpdatesQueueKey ( ) [ ] byte {
return [ ] byte ( "submittedUpdatesQueue" )
}
// ============= SUBMITTED UPDATES
2018-09-01 17:16:56 +00:00
// These are keyed by the IDs of their associated Channels
2018-08-27 21:58:58 +00:00
// This section deals with only setting and getting
func ( k Keeper ) getSubmittedUpdate ( ctx sdk . Context , channelID ChannelID ) ( SubmittedUpdate , bool ) {
2018-08-28 03:48:48 +00:00
2018-08-27 21:58:58 +00:00
// load from DB
store := ctx . KVStore ( k . storeKey )
bz := store . Get ( k . getSubmittedUpdateKey ( channelID ) )
var sUpdate SubmittedUpdate
if bz == nil {
return sUpdate , false
}
// unmarshal
k . cdc . MustUnmarshalBinary ( bz , & sUpdate )
// return
return sUpdate , true
}
// Store payment channel struct in blockchain store.
func ( k Keeper ) setSubmittedUpdate ( ctx sdk . Context , sUpdate SubmittedUpdate ) {
store := ctx . KVStore ( k . storeKey )
// marshal
bz := k . cdc . MustMarshalBinary ( sUpdate ) // panics if something goes wrong
// write to db
2018-08-28 03:48:48 +00:00
key := k . getSubmittedUpdateKey ( sUpdate . ChannelID )
2018-08-27 21:58:58 +00:00
store . Set ( key , bz ) // panics if something goes wrong
}
2018-08-28 03:48:48 +00:00
func ( k Keeper ) deleteSubmittedUpdate ( ctx sdk . Context , channelID ChannelID ) {
2018-08-27 21:58:58 +00:00
store := ctx . KVStore ( k . storeKey )
store . Delete ( k . getSubmittedUpdateKey ( channelID ) )
// TODO does this have return values? What happens when key doesn't exist?
}
func ( k Keeper ) getSubmittedUpdateKey ( channelID ChannelID ) [ ] byte {
return [ ] byte ( fmt . Sprintf ( "submittedUpdate:%d" , channelID ) )
}
// ========================================== CHANNELS
// Reteive a payment channel struct from the blockchain store.
func ( k Keeper ) getChannel ( ctx sdk . Context , channelID ChannelID ) ( Channel , bool ) {
// load from DB
store := ctx . KVStore ( k . storeKey )
2018-09-01 23:29:51 +00:00
bz := store . Get ( GetChannelKey ( channelID ) )
2018-08-27 21:58:58 +00:00
var channel Channel
if bz == nil {
return channel , false
}
// unmarshal
k . cdc . MustUnmarshalBinary ( bz , & channel )
// return
return channel , true
}
// Store payment channel struct in blockchain store.
func ( k Keeper ) setChannel ( ctx sdk . Context , channel Channel ) {
store := ctx . KVStore ( k . storeKey )
// marshal
bz := k . cdc . MustMarshalBinary ( channel ) // panics if something goes wrong
// write to db
2018-09-01 23:29:51 +00:00
key := GetChannelKey ( channel . ID )
2018-08-27 21:58:58 +00:00
store . Set ( key , bz ) // panics if something goes wrong
}
2018-08-28 03:48:48 +00:00
func ( k Keeper ) deleteChannel ( ctx sdk . Context , channelID ChannelID ) {
2018-08-27 21:58:58 +00:00
store := ctx . KVStore ( k . storeKey )
2018-09-01 23:29:51 +00:00
store . Delete ( GetChannelKey ( channelID ) )
2018-08-27 21:58:58 +00:00
// TODO does this have return values? What happens when key doesn't exist?
}
2018-08-28 03:48:48 +00:00
func ( k Keeper ) getNewChannelID ( ctx sdk . Context ) ChannelID {
2018-08-27 21:58:58 +00:00
// get last channel ID
2018-08-28 03:48:48 +00:00
var lastID ChannelID
store := ctx . KVStore ( k . storeKey )
2018-09-01 23:29:51 +00:00
bz := store . Get ( getLastChannelIDKey ( ) )
2018-08-27 21:58:58 +00:00
if bz == nil {
2018-08-28 03:48:48 +00:00
lastID = - 1 // TODO is just setting to zero if uninitialized ok?
} else {
k . cdc . MustUnmarshalBinary ( bz , & lastID )
2018-08-27 21:58:58 +00:00
}
// increment to create new one
2018-08-28 03:48:48 +00:00
newID := lastID + 1
2018-08-27 21:58:58 +00:00
bz = k . cdc . MustMarshalBinary ( newID )
// set last channel id again
2018-09-01 23:29:51 +00:00
store . Set ( getLastChannelIDKey ( ) , bz )
2018-08-27 21:58:58 +00:00
// return
return newID
}
2018-09-01 23:29:51 +00:00
func GetChannelKey ( channelID ChannelID ) [ ] byte {
2018-08-28 03:48:48 +00:00
return [ ] byte ( fmt . Sprintf ( "channel:%d" , channelID ) )
2018-08-27 21:58:58 +00:00
}
2018-09-01 23:29:51 +00:00
func getLastChannelIDKey ( ) [ ] byte {
2018-08-28 03:48:48 +00:00
return [ ] byte ( "lastChannelID" )
2018-08-24 23:18:41 +00:00
}