0g-chain/internal/x/paychan/keeper.go

418 lines
13 KiB
Go
Raw Normal View History

2018-07-08 22:09:07 +00:00
package paychan
2018-07-09 18:46:51 +00:00
import (
"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.
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
}
// 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{
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
}
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
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
// 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 {
// No one has tried to update channel
2018-08-27 21:58:58 +00:00
submittedUpdate := SubmittedUpdate{
Update: update,
ExecutionTime: ctx.BlockHeight() + ChannelDisputeTime, //TODO check what exactly BlockHeight refers to
2018-08-27 21:58:58 +00:00
}
k.addToSubmittedUpdatesQueue(ctx, submittedUpdate)
2018-08-24 23:18:41 +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
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
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
k.removeFromSubmittedUpdatesQueue(ctx, update.ChannelID)
2018-08-27 21:58:58 +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
// 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
// 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
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
// 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)
if err != nil {
panic(err)
}
2018-08-27 21:58:58 +00:00
}
k.deleteChannel(ctx, update.ChannelID)
2018-08-27 21:58:58 +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
q, found := k.getSubmittedUpdatesQueue(ctx)
if !found {
panic("SubmittedUpdatesQueue not found.")
}
2018-08-27 21:58:58 +00:00
// append ID to queue
if !q.Contains(sUpdate.ChannelID) {
2018-08-27 21:58:58 +00:00
q = append(q, sUpdate.ChannelID)
}
// set queue
k.setSubmittedUpdatesQueue(ctx, q)
2018-08-27 21:58:58 +00:00
// store submittedUpdate
k.setSubmittedUpdate(ctx, sUpdate)
}
func (k Keeper) removeFromSubmittedUpdatesQueue(ctx sdk.Context, channelID ChannelID) {
2018-08-27 21:58:58 +00:00
// get current queue
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
k.setSubmittedUpdatesQueue(ctx, q)
2018-08-27 21:58:58 +00:00
// delete submittedUpdate
k.deleteSubmittedUpdate(ctx, channelID)
}
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())
var suq SubmittedUpdatesQueue
2018-08-27 21:58:58 +00:00
if bz == nil {
return suq, false // TODO maybe create custom error to pass up here
2018-08-27 21:58:58 +00:00
}
// unmarshal
k.cdc.MustUnmarshalBinary(bz, &suq)
2018-08-27 21:58:58 +00:00
// return
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-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
key := k.getSubmittedUpdateKey(sUpdate.ChannelID)
2018-08-27 21:58:58 +00:00
store.Set(key, bz) // panics if something goes wrong
}
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
key := k.getChannelKey(channel.ID)
2018-08-27 21:58:58 +00:00
store.Set(key, bz) // panics if something goes wrong
}
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?
}
func (k Keeper) getNewChannelID(ctx sdk.Context) ChannelID {
2018-08-27 21:58:58 +00:00
// get last channel ID
var lastID ChannelID
store := ctx.KVStore(k.storeKey)
2018-08-27 21:58:58 +00:00
bz := store.Get(k.getLastChannelIDKey())
if bz == nil {
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
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 {
return []byte(fmt.Sprintf("channel:%d", channelID))
2018-08-27 21:58:58 +00:00
}
func (k Keeper) getLastChannelIDKey() []byte {
return []byte("lastChannelID")
2018-08-24 23:18:41 +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
*/