mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 16:25:21 +00:00
fill out rough implementation
This commit is contained in:
parent
3549c75474
commit
a4baa34ee0
@ -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?
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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.
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user