2018-07-08 22:09:07 +00:00
|
|
|
package paychan
|
|
|
|
|
2018-07-09 18:46:51 +00:00
|
|
|
import (
|
|
|
|
"strconv"
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// bunch of business logic ...
|
2018-08-24 23:18:41 +00:00
|
|
|
/*
|
2018-07-09 18:46:51 +00:00
|
|
|
// Reteive a payment channel struct from the blockchain store.
|
|
|
|
// They are indexed by a concatenation of sender address, receiver address, and an integer.
|
2018-07-13 13:27:55 +00:00
|
|
|
func (k Keeper) GetPaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, id int64) (Paychan, bool) {
|
2018-07-09 18:46:51 +00:00
|
|
|
// Return error as second argument instead of bool?
|
|
|
|
var pych Paychan
|
2018-07-08 22:09:07 +00:00
|
|
|
// load from DB
|
2018-07-09 18:46:51 +00:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
bz := store.Get(paychanKey(sender, receiver, id))
|
|
|
|
if bz == nil {
|
|
|
|
return pych, false
|
|
|
|
}
|
|
|
|
// unmarshal
|
|
|
|
k.cdc.MustUnmarshalBinary(bz, &pych)
|
2018-07-08 22:09:07 +00:00
|
|
|
// return
|
2018-07-09 18:46:51 +00:00
|
|
|
return pych, true
|
2018-07-08 22:09:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:46:51 +00:00
|
|
|
// Store payment channel struct in blockchain store.
|
2018-07-13 13:27:55 +00:00
|
|
|
func (k Keeper) setPaychan(ctx sdk.Context, pych Paychan) {
|
2018-07-09 18:46:51 +00:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
2018-07-08 22:09:07 +00:00
|
|
|
// marshal
|
2018-07-13 13:27:55 +00:00
|
|
|
bz := k.cdc.MustMarshalBinary(pych) // panics if something goes wrong
|
2018-07-08 22:09:07 +00:00
|
|
|
// write to db
|
2018-07-15 11:41:55 +00:00
|
|
|
pychKey := paychanKey(pych.Sender, pych.Receiver, pych.Id)
|
2018-07-12 13:32:36 +00:00
|
|
|
store.Set(pychKey, bz) // panics if something goes wrong
|
2018-07-08 22:09:07 +00:00
|
|
|
}
|
2018-08-24 23:18:41 +00:00
|
|
|
*/
|
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-24 23:18:41 +00:00
|
|
|
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?
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Calculate next id
|
|
|
|
id := k.getNewChannelID(ctx)
|
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
|
|
|
|
}
|
|
|
|
// create new Paychan struct
|
2018-08-24 23:18:41 +00:00
|
|
|
channel := Channel{
|
|
|
|
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-07-12 13:32:36 +00:00
|
|
|
// TODO create tags
|
2018-07-13 13:27:55 +00:00
|
|
|
//tags := sdk.NewTags()
|
2018-07-10 13:56:04 +00:00
|
|
|
return tags, err
|
2018-07-08 22:09:07 +00:00
|
|
|
}
|
|
|
|
|
2018-08-24 23:18:41 +00:00
|
|
|
/* 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
|
|
|
|
}
|
|
|
|
/*
|
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
|
|
|
*/
|