initial sketch

This commit is contained in:
rhuairahrighairigh 2018-07-08 23:09:07 +01:00
parent b04556844f
commit afb7de58dd
9 changed files with 400 additions and 1 deletions

View File

@ -55,8 +55,9 @@ func main() {
)...)
rootCmd.AddCommand(
client.PostCommands(
client.PostCommands( // this just wraps the input cmds with common flags
bankcmd.SendTxCmd(cdc),
// paychan commands...
//ibccmd.IBCTransferCmd(cdc),
//ibccmd.IBCRelayCmd(cdc),
//stakecmd.GetCmdCreateValidator(cdc),

View File

@ -0,0 +1,15 @@
Paychan Sketch
Simplifications:
- unidirectional paychans
- no top ups or partial withdrawals (only opening and closing)
- no protection against fund lock up from dissapearing receiver
TODO
- fill in details
- add tests
- is errors.go needed?
- is wire.go needed?
- remove simplifications

View File

@ -0,0 +1,68 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
//"github.com/cosmos/cosmos-sdk/client/context"
//sdk "github.com/cosmos/cosmos-sdk/types"
//"github.com/cosmos/cosmos-sdk/wire"
//"github.com/cosmos/cosmos-sdk/x/auth"
)
// list of functions that return pointers to cobra commands
// No local storage needed for cli acting as a sender
// create paychan
// close paychan
// get paychan(s)
// send paychan payment
// get balance from receiver
// example from x/auth
/*
func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder) *cobra.Command {
return &cobra.Command{
Use: "account [address]",
Short: "Query account balance",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// find the key to look up the account
addr := args[0]
key, err := sdk.GetAccAddressBech32(addr)
if err != nil {
return err
}
// perform query
ctx := context.NewCoreContextFromViper()
res, err := ctx.Query(auth.AddressStoreKey(key), storeName)
if err != nil {
return err
}
// Check if account was found
if res == nil {
return sdk.ErrUnknownAddress("No account with address " + addr +
" was found in the state.\nAre you sure there has been a transaction involving it?")
}
// decode the value
account, err := decoder(res)
if err != nil {
return err
}
// print out whole account
output, err := wire.MarshalJSONIndent(cdc, account)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
},
}
}
*/

View File

@ -0,0 +1,7 @@
package lcd
import ()
// implement thing that polls blockchain and handles paychan disputes
// needs plugged into LCD - add a "background processes" slot in the LCD run function?
// eventually LCD evolves into paychan (network) daemon

View File

@ -0,0 +1,22 @@
package rest
import (
"github.com/gorilla/mux"
//"github.com/tendermint/go-crypto/keys"
//"github.com/cosmos/cosmos-sdk/client/context"
//"github.com/cosmos/cosmos-sdk/wire"
)
// RegisterRoutes registers paychan-related REST handlers to a router
func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
//r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, ctx)).Methods("POST")
}
// handler functions ...
// create paychan
// close paychan
// get paychan(s)
// send paychan payment
// get balance from receiver
// get balance from local storage
// handle incoming payment

View File

@ -0,0 +1,33 @@
package paychan
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"reflect"
)
// NewHandler returns a handler for "paychan" type messages.
func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgSend:
return handleMsgSend(ctx, k, msg)
case MsgIssue:
return handleMsgIssue(ctx, k, msg)
default:
errMsg := "Unrecognized paychan Msg type: " + reflect.TypeOf(msg).Name()
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
// Handle CreateMsg.
func handleMsgCreate(ctx sdk.Context, k Keeper, msg MsgSend) sdk.Result {
// k.CreatePaychan(args...)
// handle erros
}
// Handle CloseMsg.
func handleMsgClose(ctx sdk.Context, k Keeper, msg MsgIssue) sdk.Result {
// k.ClosePaychan(args...)
// handle errors
}

View File

@ -0,0 +1,62 @@
package paychan
// keeper of the paychan store
type Keeper struct {
storeKey sdk.StoreKey
cdc *wire.Codec // needed?
coinKeeper bank.Keeper
// codespace
codespace sdk.CodespaceType // ??
}
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper {
keeper := Keeper{
storeKey: key,
cdc: cdc,
coinKeeper: ck,
codespace: codespace,
}
return keeper
}
// bunch of business logic ...
func (keeper Keeper) GetPaychan(paychanID) Paychan {
// load from DB
// unmarshall
// return
}
func (keeper Keeper) setPaychan(pych Paychan) sdk.Error {
// marshal
// write to db
}
func (keeer Keeper) CreatePaychan(receiver sdkAddress, amt sdk.Coins) (Paychan, sdk.Error) {
// subtract coins from sender
// create new Paychan struct (create ID)
// save to db
// validation:
// sender has enough coins
// receiver address exists?
// paychan doesn't exist already
}
func (keeper Keeper) ClosePaychan() sdk.Error {
// add coins to sender
// add coins to receiver
// delete paychan from db
// validation:
// paychan exists
// output coins are less than paychan balance
// sender and receiver addresses exist?
}
func paychanKey(Paychan) {
// concat sender and receiver and integer ID
}
// maybe getAllPaychans(sender sdk.address) []Paychan

View File

@ -0,0 +1,93 @@
package paychan
import (
"testing"
"github.com/stretchr/testify/assert"
)
// example from x/bank
func TestKeeper(t *testing.T) {
// ms, authKey := setupMultiStore()
// cdc := wire.NewCodec()
// auth.RegisterBaseAccount(cdc)
// ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger())
// accountMapper := auth.NewAccountMapper(cdc, authKey, &auth.BaseAccount{})
// coinKeeper := NewKeeper(accountMapper)
// addr := sdk.Address([]byte("addr1"))
// addr2 := sdk.Address([]byte("addr2"))
// addr3 := sdk.Address([]byte("addr3"))
// acc := accountMapper.NewAccountWithAddress(ctx, addr)
// // Test GetCoins/SetCoins
// accountMapper.SetAccount(ctx, acc)
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{}))
// coinKeeper.SetCoins(ctx, addr, sdk.Coins{{"foocoin", 10}})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}}))
// // Test HasCoins
// assert.True(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 10}}))
// assert.True(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 5}}))
// assert.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"foocoin", 15}}))
// assert.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"barcoin", 5}}))
// // Test AddCoins
// coinKeeper.AddCoins(ctx, addr, sdk.Coins{{"foocoin", 15}})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 25}}))
// coinKeeper.AddCoins(ctx, addr, sdk.Coins{{"barcoin", 15}})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 15}, {"foocoin", 25}}))
// // Test SubtractCoins
// coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"foocoin", 10}})
// coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 5}})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
// coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
// coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 10}})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 15}}))
// assert.False(t, coinKeeper.HasCoins(ctx, addr, sdk.Coins{{"barcoin", 1}}))
// // Test SendCoins
// coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 5}})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}}))
// assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}}))
// _, err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}})
// assert.Implements(t, (*sdk.Error)(nil), err2)
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}}))
// assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}}))
// coinKeeper.AddCoins(ctx, addr, sdk.Coins{{"barcoin", 30}})
// coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"barcoin", 10}, {"foocoin", 5}})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 20}, {"foocoin", 5}}))
// assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 10}}))
// // Test InputOutputCoins
// input1 := NewInput(addr2, sdk.Coins{{"foocoin", 2}})
// output1 := NewOutput(addr, sdk.Coins{{"foocoin", 2}})
// coinKeeper.InputOutputCoins(ctx, []Input{input1}, []Output{output1})
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 20}, {"foocoin", 7}}))
// assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 8}}))
// inputs := []Input{
// NewInput(addr, sdk.Coins{{"foocoin", 3}}),
// NewInput(addr2, sdk.Coins{{"barcoin", 3}, {"foocoin", 2}}),
// }
// outputs := []Output{
// NewOutput(addr, sdk.Coins{{"barcoin", 1}}),
// NewOutput(addr3, sdk.Coins{{"barcoin", 2}, {"foocoin", 5}}),
// }
// coinKeeper.InputOutputCoins(ctx, inputs, outputs)
// assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 21}, {"foocoin", 4}}))
// assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"barcoin", 7}, {"foocoin", 6}}))
// assert.True(t, coinKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{{"barcoin", 2}, {"foocoin", 5}}))
}

View File

@ -0,0 +1,98 @@
package paychan
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
////////
// probably want to convert this to a general purpose "state"
struct Paychan {
balance sdk.Coins
sender sdk.Address
receiver sdk.Address
}
/////////////
// Message implement the sdk.Msg interface:
// type Msg interface {
// // Return the message type.
// // Must be alphanumeric or empty.
// Type() string
// // Get the canonical byte representation of the Msg.
// GetSignBytes() []byte
// // ValidateBasic does a simple validation check that
// // doesn't require access to any other information.
// ValidateBasic() Error
// // Signers returns the addrs of signers that must sign.
// // CONTRACT: All signatures must be present to be valid.
// // CONTRACT: Returns addrs in some deterministic order.
// GetSigners() []Address
// }
/////////////// CreatePayChan
// find a less confusing name
type CreateMsg struct {
// maybe just wrap a paychan struct
sender sdk.Address
receiver sdk.Address
amount sdk.Balance
}
func (msg CreatMsg) NewCreateMsg() CreateMsg {
return CreateMsg{ }
}
func (msg CreateMsg) Type() string { return "paychan" }
func (msg CreateMsg) GetSigners() []sdk.Address {
// sender
//return []sdk.Address{msg.sender}
}
func (msg CreateMsg) GetSignBytes() []byte {
}
func (msg CreateMsg) ValidateBasic() sdk.Error {
// verify msg as much as possible without using external information (such as account balance)
// are all fields present
// are all fields valid
// maybe check if sender and receiver is different
}
/////////////////
type CloseMsg struct {
// have to include sender and receiver in msg explicitly (rather than just universal paychanID)
// this gives ability to verify signatures with no external information
sender sdk.Address
receiver sdk.Address
id integer
receiverAmount sdk.Coins // amount the receiver should get - sender amount implicit with paychan balance
}
func (msg CloseMsg) NewCloseMsg( args... ) CloseMsg {
return CloseMsg{ args... }
}
func (msg CloseMsg) Type() string { return "paychan" }
func (msg CloseMsg) GetSigners() []sdk.Address {
// sender and receiver
}
func (msg CloseMsg) GetSignBytes() []byte {
}
func (msg CloseMsg) ValidateBasic() sdk.Error {
return msg.IBCPacket.ValidateBasic()
}