fill out rough implementation

This commit is contained in:
rhuairahrighairigh 2018-08-27 17:58:58 -04:00
parent 3549c75474
commit a4baa34ee0
5 changed files with 274 additions and 65 deletions

View File

@ -7,4 +7,7 @@ Simplifications:
TODO TODO
- error handling (getter setter return values? and what happens in failures)
- chnge module name to "channel"? - chnge module name to "channel"?
- Find a better name for Queue - clarify distinction between int slice and abstract queue concept
- Do all the small functions need to be methods on the keeper or can they just be floating around?

View File

@ -2,11 +2,20 @@ package paychan
import () import ()
func EndBlocker(ctx sdk.Context k Keeper) sdk.Tags { func EndBlocker(ctx sdk.Context, k Keeper) sdk.Tags {
// Iterate through submittedUpdates and for each // Iterate through submittedUpdatesQueue
// if current block height >= executionDate // TODO optimise so it doesn't pull every update from DB every block
// k.CloseChannel(...) var sUpdate SubmittedUpdate
q := k.getSubmittedUpdatesQueue(ctx)
for _, id := range q {
// close the channel if the update has reached its execution time.
// Using >= in case some are somehow missed.
sUpdate = k.getSubmittedUpdate(ctx, id)
if ctx.BlockHeight() >= sUpdate.ExecutionTime {
k.closeChannel(ctx, sUpdate.Update)
}
}
tags := sdk.NewTags() tags := sdk.NewTags()
return tags return tags

View File

@ -38,10 +38,15 @@ func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgCreate) sdk.Result {
// Leaves validation to the keeper methods. // Leaves validation to the keeper methods.
func handleMsgSubmitUpdate(ctx sdk.Context, k Keeper, msg MsgSubmitUpdate) sdk.Result { func handleMsgSubmitUpdate(ctx sdk.Context, k Keeper, msg MsgSubmitUpdate) sdk.Result {
// if only sender sig then participants := k.getChannel(ctx, msg.Update.ChannelID).Participants
tags, err := k.InitChannelCloseBySender()
// else (if there are both) // if only sender signed
tags, err := k.ChannelCloseByReceiver() if msg.submitter == participants[0] {
tags, err := k.InitCloseChannelBySender()
// else if receiver signed
} else if msg.submitter == participants[len(participants)-1] {
tags, err := k.CloseChannelByReceiver()
}
if err != nil { if err != nil {
return err.Result() return err.Result()

View File

@ -31,38 +31,11 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper) Keeper {
return keeper return keeper
} }
// bunch of business logic ... // ============================================== Main Business Logic
/*
// Reteive a payment channel struct from the blockchain store.
// They are indexed by a concatenation of sender address, receiver address, and an integer.
func (k Keeper) GetPaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, id int64) (Paychan, bool) {
// Return error as second argument instead of bool?
var pych Paychan
// load from DB
store := ctx.KVStore(k.storeKey)
bz := store.Get(paychanKey(sender, receiver, id))
if bz == nil {
return pych, false
}
// unmarshal
k.cdc.MustUnmarshalBinary(bz, &pych)
// return
return pych, true
}
// Store payment channel struct in blockchain store.
func (k Keeper) setPaychan(ctx sdk.Context, pych Paychan) {
store := ctx.KVStore(k.storeKey)
// marshal
bz := k.cdc.MustMarshalBinary(pych) // panics if something goes wrong
// write to db
pychKey := paychanKey(pych.Sender, pych.Receiver, pych.Id)
store.Set(pychKey, bz) // panics if something goes wrong
}
*/
// Create a new payment channel and lock up sender funds. // Create a new payment channel and lock up sender funds.
func (k Keeper) CreatePaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, coins sdk.Coins) (sdk.Tags, sdk.Error) { func (k Keeper) CreateChannel(ctx sdk.Context, sender sdk.Address, receiver sdk.Address, coins sdk.Coins) (sdk.Tags, sdk.Error) {
// TODO do validation and maybe move somewhere nicer // TODO do validation and maybe move somewhere nicer
/* /*
// args present // args present
@ -110,38 +83,229 @@ func (k Keeper) CreatePaychan(ctx sdk.Context, sender sdk.Address, receiver sdk.
// save to db // save to db
k.setChannel(ctx, channel) k.setChannel(ctx, channel)
// TODO create tags // TODO add to tags
//tags := sdk.NewTags()
return tags, err return tags, err
} }
/* 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) func (k Keeper) InitCloseChannelBySender(update Update) {
bz := store.Get(KeyNextProposalID) // This is roughly the default path for non unidirectional channels
if bz == nil {
return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") // TODO Validate update - e.g. check signed by sender
q := k.getSubmittedUpdateQueue(ctx)
if q.Contains(update.ChannelID) {
// Someone has previously tried to update channel
existingSUpdate := k.getSubmittedUpdate(ctx, update.ChannelID)
k.addToSubmittedUpdateQueue(ctx, k.applyNewUpdate(existingSUpdate, update))
} else {
// No one has tried to update channel.
submittedUpdate := SubmittedUpdate{
Update: update
executionTime: ctx.BlockHeight()+ChannelDisputeTime //TODO check what exactly BlockHeight refers to
}
k.addToSubmittedUpdateQueue(ctx, submittedUpdate)
} }
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() () { func (k Keeper) CloseChannelByReceiver(update Update) () {
// Validate inputs // TODO Validate update
// Create SubmittedUpdate from Update and add to queue
// Check if there is an update in the queue already
q := k.getSubmittedUpdateQueue(ctx)
if q.Contains(update.ChannelID) {
// Someone has previously tried to update channel but receiver has final say
k.removeFromSubmittedUpdateQueue(ctx, update.ChannelID)
}
k.closeChannel(ctx, update)
} }
func (k Keeper) closeChannel() () { // Main function that compare updates against each other.
// Remove corresponding SubmittedUpdate from queue (if it exist) // Pure function
func (k Keeper) applyNewUpdate(existingSUpdate, proposedUpdate) SubmittedUpdate {
var returnUpdate SubmittedUpdate
if existingSUpdate.sequence > proposedUpdate.sequence {
// update accepted
returnUpdate = SubmittedUpdate{
Update: proposedUpdate
ExecutionTime: existingSUpdate.ExecutionTime
}
} else {
// update rejected
returnUpdate = existingSUpdate
}
return returnUpdate
}
func (k Keeper) closeChannel(ctx sdk.Context, update Update) {
channel := k.getChannel(ctx, update.ChannelID)
// Add coins to sender and receiver // Add coins to sender and receiver
// Delete Channel for address, coins := range update.CoinsUpdate {
// TODO check somewhere if coins are not negative?
k.ck.AddCoins(ctx, address, coins)
}
k.deleteChannel(ctx, update.ChannelID)
}
// =========================================== QUEUE
func (k Keeper) addToSubmittedUpdatesQueue(ctx sdk.Context, sUpdate SubmittedUpdate) {
// always overwrite prexisting values - leave paychan logic to higher levels
// get current queue
q := k.getSubmittedUpdateQueue(ctx)
// append ID to queue
if q.Contains(sUpdate.ChannelID)! {
q = append(q, sUpdate.ChannelID)
}
// set queue
k.setSubmittedUpdateQueue(ctx, q)
// store submittedUpdate
k.setSubmittedUpdate(ctx, sUpdate)
}
func (k Keeper) removeFromSubmittdUpdatesQueue(ctx sdk.Context, channelID) {
// get current queue
q := k.getSubmittedUpdateQueue(ctx)
// remove id
q.RemoveMatchingElements(channelID)
// set queue
k.setSubmittedUpdateQueue(ctx, q)
// delete submittedUpdate
k.deleteSubmittedUpdate(ctx, channelID)
}
func (k Keeper) getSubmittedUpdatesQueue(ctx sdk.Context) (Queue, bool) {
// load from DB
store := ctx.KVStore(k.storeKey)
bz := store.Get(k.getSubmittedUpdatesQueueKey())
var q Queue
if bz == nil {
return q, false
}
// unmarshal
k.cdc.MustUnmarshalBinary(bz, &q)
// return
return q, true
}
func (k Keeper) setSubmittedUpdatesQueue(ctx sdk.Context, q Queue) {
store := ctx.KVStore(k.storeKey)
// marshal
bz := k.cdc.MustMarshalBinary(q)
// 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) {
// 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)
store.Set(key, bz) // panics if something goes wrong
}
func (k Keeper) deleteSubmittedUpdate(ctx sdk.Context, channelID ) {
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 := sdk.getChannelKey(channel.ID)
store.Set(key, bz) // panics if something goes wrong
}
func (k Keeper) deleteChannel(ctx sdk.Context, channelID ) {
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) (int64, error) {
// get last channel ID
store := k.KVStore(k.storeKey)
bz := store.Get(k.getLastChannelIDKey())
if bz == nil {
return nil, // TODO throw some error (assumes this has been initialized elsewhere) or just set to zero here
}
var lastID ChannelID
k.cdc.MustUnmarshalBinary(bz, &lastID)
// increment to create new one
newID := lastID+1
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 []bytes(fmt.Sprintf("channel:%d", channelID))
}
func (k Keeper) getLastChannelIDKey() []byte {
return []bytes("lastChannelID")
} }
/* /*
// Close a payment channel and distribute funds to participants. // Close a payment channel and distribute funds to participants.

View File

@ -11,15 +11,17 @@ import (
// Participants is limited to two as currently these are unidirectional channels. // Participants is limited to two as currently these are unidirectional channels.
// Last participant is designated as receiver. // Last participant is designated as receiver.
type Channel struct { type Channel struct {
ID int64 ID ChannelID
Participants [2]sdk.AccAddress Participants [2]sdk.AccAddress
Coins sdk.Coins Coins sdk.Coins
} }
type ChannelID int64 // TODO should this be positive only
// The data that is passed between participants as payments, and submitted to the blockchain to close a channel. // The data that is passed between participants as payments, and submitted to the blockchain to close a channel.
type Update struct { type Update struct {
ChannelID int64 ChannelID int64
CoinsUpdate //TODO type CoinsUpdate map[sdk.AccAddress]sdk.Coins
Sequence int64 Sequence int64
sig // TODO type, only sender needs to sign sig // TODO type, only sender needs to sign
} }
@ -27,9 +29,35 @@ type Update struct {
// An update that has been submitted to the blockchain, but not yet acted on. // An update that has been submitted to the blockchain, but not yet acted on.
type SubmittedUpdate { type SubmittedUpdate {
Update Update
executionDate int64 // BlockHeight executionTime int64 // BlockHeight
} }
type SubmittedUpdateQueue []ChannelID
// Check if value is in queue
func (suq SubmittedChannelID) Contains(channelID ChannelID) bool {
found := false
for _, id := range(suq) {
if id == channelID {
found = true
break
}
}
return found
}
// Remove all values from queue that match argument
func (suq SubmittedUpdateQueue) RemoveMatchingElements(channelID ChannelID) {
newSUQ := SubmittedUpdateQueue{}
for _, id := range(suq) {
if id != channelID {
newSUQ = append(newSUQ, id)
}
}
suq = newSUQ
}
var ChannelDisputeTime = 2000 // measured in blocks
/* MESSAGE TYPES */ /* MESSAGE TYPES */
/* /*
Message implement the sdk.Msg interface: Message implement the sdk.Msg interface:
@ -120,7 +148,7 @@ func (msg MsgCreate) GetSigners() []sdk.Address {
// A message to close a payment channel. // A message to close a payment channel.
type MsgSubmitUpdate struct { type MsgSubmitUpdate struct {
Update Update
// might need a "signer" to be able to say who is signing this as either can or not submitter sdk.AccAddress
} }
// func (msg MsgSubmitUpdate) NewMsgSubmitUpdate(update Update) MsgSubmitUpdate { // func (msg MsgSubmitUpdate) NewMsgSubmitUpdate(update Update) MsgSubmitUpdate {