2018-07-08 22:09:07 +00:00
package paychan
2018-07-09 18:46:51 +00:00
import (
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-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-08-24 23:18:41 +00:00
// TODO investigate codespace
//codespace sdk.CodespaceType
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-08-27 21:58:58 +00:00
// ============================================== Main Business Logic
2018-07-08 22:09:07 +00:00
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-08-24 23:18:41 +00:00
// 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?
* /
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-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
// TODO Validate update - e.g. check signed by sender
2018-08-28 03:48:48 +00:00
q , found := k . getSubmittedUpdatesQueue ( ctx )
if ! found {
panic ( "SubmittedUpdatesQueue not found." ) // TODO nicer custom errors
}
2018-08-27 21:58:58 +00:00
if q . Contains ( update . ChannelID ) {
// Someone has previously tried to update channel
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
// TODO custom errors return sdk.EmptyTags(), sdk.NewError("Sender can't submit an update for channel if one has already been submitted.")
panic ( "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-08-28 03:48:48 +00:00
func ( k Keeper ) CloseChannelByReceiver ( ctx sdk . Context , update Update ) ( sdk . Tags , sdk . Error ) {
2018-08-27 21:58:58 +00:00
// TODO Validate update
// Check if there is an update in the queue already
2018-08-28 03:48:48 +00:00
q , found := k . getSubmittedUpdatesQueue ( ctx )
if ! found {
panic ( "SubmittedUpdatesQueue not found." ) // TODO nicer custom errors
}
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.
// Pure function
2018-08-30 17:43:15 +00:00
// Not needed in unidirectional case.
// 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-08-28 03:48:48 +00:00
// unsafe close channel - doesn't check if update matches existing channel TODO make safer?
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-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-08-29 03:45:26 +00:00
for _ , payout := range update . Payouts {
// TODO check somewhere if coins are not negative?
_ , tags , err = k . coinKeeper . AddCoins ( ctx , payout . Address , payout . 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
// =========================================== QUEUE
func ( k Keeper ) addToSubmittedUpdatesQueue ( ctx sdk . Context , sUpdate SubmittedUpdate ) {
// always overwrite prexisting values - leave paychan logic to higher levels
// get current queue
2018-08-28 03:48:48 +00:00
q , found := k . getSubmittedUpdatesQueue ( ctx )
if ! found {
panic ( "SubmittedUpdatesQueue not found." )
}
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-08-28 03:48:48 +00:00
q , found := k . getSubmittedUpdatesQueue ( ctx )
if ! found {
panic ( "SubmittedUpdatesQueue not found." )
}
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-08-28 03:48:48 +00:00
func ( k Keeper ) getSubmittedUpdatesQueue ( ctx sdk . Context ) ( SubmittedUpdatesQueue , bool ) {
2018-08-27 21:58:58 +00:00
// load from DB
store := ctx . KVStore ( k . storeKey )
bz := store . Get ( k . getSubmittedUpdatesQueueKey ( ) )
2018-08-28 03:48:48 +00:00
var suq SubmittedUpdatesQueue
2018-08-27 21:58:58 +00:00
if bz == nil {
2018-08-28 03:48:48 +00:00
return suq , false // TODO maybe create custom error to pass up here
2018-08-27 21:58:58 +00:00
}
// unmarshal
2018-08-28 03:48:48 +00:00
k . cdc . MustUnmarshalBinary ( bz , & suq )
2018-08-27 21:58:58 +00:00
// return
2018-08-28 03:48:48 +00:00
return suq , true
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
// These are keyed by the IDs of thei associated Channels
// 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 )
bz := store . Get ( k . getChannelKey ( channelID ) )
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-08-28 03:48:48 +00:00
key := k . 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 )
store . Delete ( k . getChannelKey ( channelID ) )
// 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-08-27 21:58:58 +00:00
bz := store . Get ( k . getLastChannelIDKey ( ) )
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
store . Set ( k . getLastChannelIDKey ( ) , bz )
// return
return newID
}
func ( k Keeper ) 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
}
func ( k Keeper ) getLastChannelIDKey ( ) [ ] byte {
2018-08-28 03:48:48 +00:00
return [ ] byte ( "lastChannelID" )
2018-08-24 23:18:41 +00:00
}
2018-08-28 03:48:48 +00:00
2018-08-24 23:18:41 +00:00
/ *
2018-07-09 18:46:51 +00:00
// Close a payment channel and distribute funds to participants.
2018-07-13 13:27:55 +00:00
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 {
return nil , sdk . ErrInvalidAddress ( sender . String ( ) )
2018-07-12 13:32:36 +00:00
}
2018-07-13 13:27:55 +00:00
if len ( receiver ) == 0 {
return nil , sdk . ErrInvalidAddress ( receiver . String ( ) )
2018-07-12 13:32:36 +00:00
}
2018-07-13 13:27:55 +00:00
if len ( receiverAmount ) == 0 {
return nil , sdk . ErrInvalidCoins ( receiverAmount . String ( ) )
2018-07-12 13:32:36 +00:00
}
// check id ≥ 0
2018-07-13 13:27:55 +00:00
if id < 0 {
return nil , sdk . ErrInvalidAddress ( strconv . Itoa ( int ( id ) ) ) // TODO implement custom errors
2018-07-12 13:32:36 +00:00
}
// Check if coins are sorted, non zero, non negative
2018-07-13 13:27:55 +00:00
if ! receiverAmount . IsValid ( ) {
return nil , sdk . ErrInvalidCoins ( receiverAmount . String ( ) )
2018-07-12 13:32:36 +00:00
}
2018-07-13 13:27:55 +00:00
if ! receiverAmount . IsPositive ( ) {
return nil , sdk . ErrInvalidCoins ( receiverAmount . String ( ) )
2018-07-12 13:32:36 +00:00
}
store := ctx . KVStore ( k . storeKey )
2018-07-13 13:27:55 +00:00
pych , exists := k . GetPaychan ( ctx , sender , receiver , id )
2018-07-12 13:32:36 +00:00
if ! exists {
2018-07-13 13:27:55 +00:00
return nil , sdk . ErrUnknownAddress ( "paychan not found" ) // TODO implement custom errors
2018-07-12 13:32:36 +00:00
}
2018-07-09 18:46:51 +00:00
// compute coin distribution
2018-07-15 11:41:55 +00:00
senderAmount := pych . Balance . Minus ( receiverAmount ) // Minus sdk.Coins method
2018-07-12 13:32:36 +00:00
// check that receiverAmt not greater than paychan balance
2018-07-13 13:27:55 +00:00
if ! senderAmount . IsNotNegative ( ) {
2018-07-15 11:41:55 +00:00
return nil , sdk . ErrInsufficientFunds ( pych . Balance . String ( ) )
2018-07-12 13:32:36 +00:00
}
2018-07-08 22:09:07 +00:00
// add coins to sender
2018-07-12 13:32:36 +00:00
// creating account if it doesn't exist
2018-07-13 13:27:55 +00:00
k . coinKeeper . AddCoins ( ctx , sender , senderAmount )
2018-07-08 22:09:07 +00:00
// add coins to receiver
2018-07-13 13:27:55 +00:00
k . coinKeeper . AddCoins ( ctx , receiver , receiverAmount )
2018-07-12 13:32:36 +00:00
2018-07-08 22:09:07 +00:00
// delete paychan from db
2018-07-15 11:41:55 +00:00
pychKey := paychanKey ( pych . Sender , pych . Receiver , pych . Id )
2018-07-09 18:46:51 +00:00
store . Delete ( pychKey )
2018-07-12 13:32:36 +00:00
// TODO create tags
2018-07-10 13:56:04 +00:00
//sdk.NewTags(
// "action", []byte("channel closure"),
// "receiver", receiver.Bytes(),
// "sender", sender.Bytes(),
// "id", ??)
tags := sdk . NewTags ( )
return tags , nil
2018-07-08 22:09:07 +00:00
}
2018-07-09 18:46:51 +00:00
// Creates a key to reference a paychan in the blockchain store.
2018-07-13 13:27:55 +00:00
func paychanKey ( sender sdk . Address , receiver sdk . Address , id int64 ) [ ] byte {
2018-07-09 18:46:51 +00:00
//sdk.Address is just a slice of bytes under a different name
//convert id to string then to byte slice
2018-07-13 13:27:55 +00:00
idAsBytes := [ ] byte ( strconv . Itoa ( int ( id ) ) )
2018-07-08 22:09:07 +00:00
// concat sender and receiver and integer ID
2018-07-13 13:27:55 +00:00
key := append ( sender . Bytes ( ) , receiver . Bytes ( ) ... )
key = append ( key , idAsBytes ... )
return key
2018-07-08 22:09:07 +00:00
}
2018-07-10 13:56:04 +00:00
// Get all paychans between a given sender and receiver.
2018-07-13 13:27:55 +00:00
func ( k Keeper ) GetPaychans ( sender sdk . Address , receiver sdk . Address ) [ ] Paychan {
2018-07-10 13:56:04 +00:00
var paychans [ ] Paychan
// TODO Implement this
return paychans
}
2018-07-13 13:27:55 +00:00
2018-07-08 22:09:07 +00:00
// maybe getAllPaychans(sender sdk.address) []Paychan
2018-08-24 23:18:41 +00:00
* /