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

376 lines
11 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 (
"bytes"
"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.
func (k Keeper) CreateChannel(ctx sdk.Context, sender sdk.AccAddress, receiver sdk.AccAddress, coins sdk.Coins) (sdk.Tags, sdk.Error) {
2018-09-02 02:57:42 +00:00
// Check addresses valid (Technicaly don't need to check sender address is valid as SubtractCoins checks)
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
}
// 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
}
2018-09-02 02:57:42 +00:00
// Initiate the close of a payment channel, subject to dispute period.
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)
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
// 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
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 {
// 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
2018-09-02 02:57:42 +00:00
// Immediately close a channel.
func (k Keeper) CloseChannelByReceiver(ctx sdk.Context, update Update) (sdk.Tags, sdk.Error) {
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)
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
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.
2018-09-02 02:57:42 +00:00
// 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
2018-09-01 23:29:51 +00:00
func VerifyUpdate(channel Channel, update Update) sdk.Error {
2018-09-01 20:41:40 +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) {
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?
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
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
// TODO check for possible errors first to avoid coins being half paid out?
for i, coins := range update.Payout {
_, tags, err = k.coinKeeper.AddCoins(ctx, channel.Participants[i], 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
2018-09-01 23:29:51 +00:00
func verifySignatures(channel Channel, update Update) bool {
// 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
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
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
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
// 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-27 21:58:58 +00:00
// load from DB
store := ctx.KVStore(k.storeKey)
bz := store.Get(GetSubmittedUpdateKey(channelID))
2018-08-27 21:58:58 +00:00
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 := 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(GetSubmittedUpdateKey(channelID))
2018-08-27 21:58:58 +00:00
// TODO does this have return values? What happens when key doesn't exist?
}
func GetSubmittedUpdateKey(channelID ChannelID) []byte {
2018-08-27 21:58:58 +00:00
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
}
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?
}
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-09-01 23:29:51 +00:00
bz := store.Get(getLastChannelIDKey())
2018-08-27 21:58:58 +00:00
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
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 {
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 {
return []byte("lastChannelID")
2018-08-24 23:18:41 +00:00
}