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

193 lines
5.9 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 (
"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-07-08 22:09:07 +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.
// Aim to keep public methids 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
// codespace
2018-07-09 18:46:51 +00:00
//codespace sdk.CodespaceType // ??
2018-07-08 22:09:07 +00:00
}
2018-07-09 18:46:51 +00:00
// Called when creating new app.
//func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper {
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-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-09 18:46:51 +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-07-09 18:46:51 +00:00
// Create a new payment channel and lock up sender funds.
2018-07-13 13:27:55 +00:00
func (k Keeper) CreatePaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, amount sdk.Coins) (sdk.Tags, sdk.Error) {
2018-07-12 13:32:36 +00:00
// TODO move validation somewhere nicer
// args present
if len(sender) == 0 {
2018-07-13 13:27:55 +00:00
return nil, sdk.ErrInvalidAddress(sender.String())
2018-07-12 13:32:36 +00:00
}
if len(receiver) == 0 {
2018-07-13 13:27:55 +00:00
return nil, sdk.ErrInvalidAddress(receiver.String())
2018-07-12 13:32:36 +00:00
}
if len(amount) == 0 {
2018-07-13 13:27:55 +00:00
return nil, sdk.ErrInvalidCoins(amount.String())
2018-07-12 13:32:36 +00:00
}
// Check if coins are sorted, non zero, positive
if !amount.IsValid() {
2018-07-13 13:27:55 +00:00
return nil, sdk.ErrInvalidCoins(amount.String())
2018-07-12 13:32:36 +00:00
}
if !amount.IsPositive() {
2018-07-13 13:27:55 +00:00
return nil, sdk.ErrInvalidCoins(amount.String())
2018-07-12 13:32:36 +00:00
}
// 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?
2018-07-13 13:27:55 +00:00
// TODO remove as account mapper not available to this pkg
//if k.coinKeeper.am.GetAccount(ctx, receiver) == nil {
// return nil, sdk.ErrUnknownAddress(receiver.String())
//}
2018-07-12 13:32:36 +00:00
// sender has enough coins - done in Subtract method
// TODO check if sender and receiver different?
2018-07-10 13:56:04 +00:00
// Calculate next id (num existing paychans plus 1)
2018-07-13 13:27:55 +00:00
id := int64(len(k.GetPaychans(sender, receiver)) + 1) // TODO check for overflow?
2018-07-08 22:09:07 +00:00
// subtract coins from sender
2018-07-13 13:27:55 +00:00
_, tags, err := k.coinKeeper.SubtractCoins(ctx, sender, amount)
2018-07-12 13:32:36 +00:00
if err != nil {
return nil, err
}
// create new Paychan struct
2018-07-13 13:27:55 +00:00
pych := Paychan{
sender: sender,
receiver: receiver,
id: id,
balance: amount,
}
2018-07-08 22:09:07 +00:00
// save to db
2018-07-13 13:27:55 +00:00
k.setPaychan(ctx, pych)
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-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-13 13:27: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-12 13:32:36 +00:00
return nil, sdk.ErrInsufficientFunds(pych.balance.String())
}
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-09 18:46:51 +00:00
pychKey := paychanKey(pych.sender, pych.receiver, pych.id)
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