mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +00:00
initial sketch
This commit is contained in:
parent
b04556844f
commit
afb7de58dd
@ -55,8 +55,9 @@ func main() {
|
|||||||
)...)
|
)...)
|
||||||
|
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
client.PostCommands(
|
client.PostCommands( // this just wraps the input cmds with common flags
|
||||||
bankcmd.SendTxCmd(cdc),
|
bankcmd.SendTxCmd(cdc),
|
||||||
|
// paychan commands...
|
||||||
//ibccmd.IBCTransferCmd(cdc),
|
//ibccmd.IBCTransferCmd(cdc),
|
||||||
//ibccmd.IBCRelayCmd(cdc),
|
//ibccmd.IBCRelayCmd(cdc),
|
||||||
//stakecmd.GetCmdCreateValidator(cdc),
|
//stakecmd.GetCmdCreateValidator(cdc),
|
||||||
|
15
internal/x/paychan/README.md
Normal file
15
internal/x/paychan/README.md
Normal 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
|
68
internal/x/paychan/client/cmd/cmd.go
Normal file
68
internal/x/paychan/client/cmd/cmd.go
Normal 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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
7
internal/x/paychan/client/rest/channel-watcher.go
Normal file
7
internal/x/paychan/client/rest/channel-watcher.go
Normal 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
|
22
internal/x/paychan/client/rest/rest.go
Normal file
22
internal/x/paychan/client/rest/rest.go
Normal 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
|
33
internal/x/paychan/handler.go
Normal file
33
internal/x/paychan/handler.go
Normal 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
|
||||||
|
}
|
62
internal/x/paychan/keeper.go
Normal file
62
internal/x/paychan/keeper.go
Normal 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
|
93
internal/x/paychan/keeper_test.go
Normal file
93
internal/x/paychan/keeper_test.go
Normal 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}}))
|
||||||
|
|
||||||
|
}
|
98
internal/x/paychan/types.go
Normal file
98
internal/x/paychan/types.go
Normal 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()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user