diff --git a/app/app.go b/app/app.go index cba1fe69d..910d7b9b2 100644 --- a/app/app.go +++ b/app/app.go @@ -242,11 +242,11 @@ func NewInitApp( ) // Engine's module keepers - app.ownershipKeeper = ownership.NewKeeper(app.cdc, keys[ownership.StoreKey]) + app.ownershipKeeper = ownership.NewKeeper(app.cdc, keys[ownership.StoreKey], app.bankKeeper) app.instanceKeeper = instance.NewKeeper(app.cdc, keys[instance.StoreKey]) app.processKeeper = process.NewKeeper(app.cdc, keys[process.StoreKey], app.instanceKeeper, app.ownershipKeeper) app.serviceKeeper = service.NewKeeper(app.cdc, keys[service.StoreKey], app.ownershipKeeper) - app.runnerKeeper = runner.NewKeeper(app.cdc, keys[runner.StoreKey], app.instanceKeeper) + app.runnerKeeper = runner.NewKeeper(app.cdc, keys[runner.StoreKey], app.instanceKeeper, app.ownershipKeeper) app.executionKeeper = execution.NewKeeper( app.cdc, keys[execution.StoreKey], diff --git a/core/main.go b/core/main.go index d958ec5b8..ecdfc2050 100644 --- a/core/main.go +++ b/core/main.go @@ -30,6 +30,7 @@ import ( "github.com/mesg-foundation/engine/server/grpc" "github.com/mesg-foundation/engine/version" "github.com/sirupsen/logrus" + rpcclient "github.com/tendermint/tendermint/rpc/client" rpcserver "github.com/tendermint/tendermint/rpc/lib/server" tmtypes "github.com/tendermint/tendermint/types" db "github.com/tendermint/tm-db" @@ -186,7 +187,7 @@ func main() { }() // create cosmos client - client, err := cosmos.NewClient(node, cdc, kb, genesis.ChainID, cfg.Account.Name, cfg.Account.Password, cfg.Cosmos.MinGasPrices) + client, err := cosmos.NewClient(rpcclient.NewLocal(node), cdc, kb, genesis.ChainID, cfg.Account.Name, cfg.Account.Password, cfg.Cosmos.MinGasPrices) if err != nil { logrus.WithField("module", "main").Fatalln(err) } diff --git a/cosmos/client.go b/cosmos/client.go index e040852db..8310f7dde 100644 --- a/cosmos/client.go +++ b/cosmos/client.go @@ -19,14 +19,13 @@ import ( "github.com/mesg-foundation/engine/ext/xstrings" "github.com/mesg-foundation/engine/hash" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/node" rpcclient "github.com/tendermint/tendermint/rpc/client" tenderminttypes "github.com/tendermint/tendermint/types" ) // Client is a tendermint client with helper functions. type Client struct { - *rpcclient.Local + rpcclient.Client cdc *codec.Codec kb keys.Keybase chainID string @@ -41,13 +40,13 @@ type Client struct { } // NewClient returns a rpc tendermint client. -func NewClient(node *node.Node, cdc *codec.Codec, kb keys.Keybase, chainID, accName, accPassword, minGasPrices string) (*Client, error) { +func NewClient(client rpcclient.Client, cdc *codec.Codec, kb keys.Keybase, chainID, accName, accPassword, minGasPrices string) (*Client, error) { minGasPricesDecoded, err := sdktypes.ParseDecCoins(minGasPrices) if err != nil { return nil, err } return &Client{ - Local: rpcclient.NewLocal(node), + Client: client, cdc: cdc, kb: kb, chainID: chainID, @@ -93,7 +92,7 @@ func (c *Client) QueryWithData(path string, data []byte) ([]byte, int64, error) // BuildAndBroadcastMsg builds and signs message and broadcast it to node. func (c *Client) BuildAndBroadcastMsg(msg sdktypes.Msg) (*abci.ResponseDeliverTx, error) { c.broadcastMutex.Lock() // Lock the whole signature + broadcast of the transaction - signedTx, err := c.createAndSignTx([]sdktypes.Msg{msg}) + signedTx, err := c.CreateAndSignTx([]sdktypes.Msg{msg}) if err != nil { c.broadcastMutex.Unlock() return nil, err @@ -204,7 +203,8 @@ func (c *Client) GetAccount() (authExported.Account, error) { return c.acc, nil } -func (c *Client) createAndSignTx(msgs []sdktypes.Msg) (tenderminttypes.Tx, error) { +// CreateAndSignTx build and sign a msg with client account. +func (c *Client) CreateAndSignTx(msgs []sdktypes.Msg) (tenderminttypes.Tx, error) { // retrieve account accR, err := c.GetAccount() if err != nil { diff --git a/e2e/api_test.go b/e2e/api_test.go index 5dcb2db13..2cbddd0f1 100644 --- a/e2e/api_test.go +++ b/e2e/api_test.go @@ -5,14 +5,18 @@ import ( "context" "io/ioutil" "net/http" + "os" + "path/filepath" "testing" + "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/mesg-foundation/engine/app" "github.com/mesg-foundation/engine/config" "github.com/mesg-foundation/engine/cosmos" pb "github.com/mesg-foundation/engine/protobuf/api" "github.com/stretchr/testify/require" + rpcclient "github.com/tendermint/tendermint/rpc/client" "google.golang.org/grpc" ) @@ -27,8 +31,9 @@ type apiclient struct { } var ( - client apiclient - cdc = app.MakeCodec() + client apiclient + cclient *cosmos.Client + cdc = app.MakeCodec() ) const ( @@ -70,14 +75,32 @@ func TestAPI(t *testing.T) { } cfg, err := config.New() - if err != nil { - panic(err) - } + require.NoError(t, err) + cosmos.CustomizeConfig(cfg) conn, err := grpc.DialContext(context.Background(), "localhost:50052", grpc.WithInsecure()) require.NoError(t, err) + // change and recreate cosmos relative path because CI dir permissions + cfg.Cosmos.RelativePath = "e2e.cosmos" + err = os.MkdirAll(filepath.Join(cfg.Path, cfg.Cosmos.RelativePath), os.FileMode(0755)) + require.NoError(t, err) + + kb, err := cosmos.NewKeybase(filepath.Join(cfg.Path, cfg.Cosmos.RelativePath)) + require.NoError(t, err) + if cfg.Account.Mnemonic != "" { + _, err = kb.CreateAccount(cfg.Account.Name, cfg.Account.Mnemonic, "", cfg.Account.Password, keys.CreateHDPath(cfg.Account.Number, cfg.Account.Index).String(), cosmos.DefaultAlgo) + require.NoError(t, err) + } + + httpclient, err := rpcclient.NewHTTP("http://localhost:26657", "/websocket") + require.NoError(t, err) + require.NoError(t, httpclient.Start()) + defer httpclient.Stop() + cclient, err = cosmos.NewClient(httpclient, cdc, kb, cfg.DevGenesis.ChainID, cfg.Account.Name, cfg.Account.Password, cfg.Cosmos.MinGasPrices) + require.NoError(t, err) + client = apiclient{ pb.NewServiceClient(conn), pb.NewEventClient(conn), diff --git a/e2e/execution_test.go b/e2e/execution_test.go index d1eeb73ab..4674b1916 100644 --- a/e2e/execution_test.go +++ b/e2e/execution_test.go @@ -6,11 +6,13 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" "github.com/mesg-foundation/engine/execution" "github.com/mesg-foundation/engine/hash" "github.com/mesg-foundation/engine/protobuf/acknowledgement" pb "github.com/mesg-foundation/engine/protobuf/api" "github.com/mesg-foundation/engine/protobuf/types" + "github.com/mesg-foundation/engine/x/ownership" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" ) @@ -254,6 +256,32 @@ func testExecution(t *testing.T) { lcdGet(t, "bank/balances/"+execAddress.String(), &coins) require.True(t, coins.AmountOf("atto").Equal(sdk.NewInt(0))) }) + + t.Run("withdraw from service", func(t *testing.T) { + acc, err := cclient.GetAccount() + require.NoError(t, err) + coins := sdk.NewCoins(sdk.NewCoin("atto", sdk.NewInt(7000))) + msg := ownership.NewMsgWithdrawCoins(testServiceHash, coins, acc.GetAddress()) + _, err = cclient.BuildAndBroadcastMsg(msg) + require.NoError(t, err) + + param := bank.NewQueryBalanceParams(sdk.AccAddress(crypto.AddressHash(testServiceHash))) + require.NoError(t, cclient.QueryJSON("custom/bank/balances", param, &coins)) + require.True(t, coins.IsZero(), coins) + }) + + t.Run("withdraw from runner", func(t *testing.T) { + acc, err := cclient.GetAccount() + require.NoError(t, err) + coins := sdk.NewCoins(sdk.NewCoin("atto", sdk.NewInt(63000))) + msg := ownership.NewMsgWithdrawCoins(testRunnerHash, coins, acc.GetAddress()) + _, err = cclient.BuildAndBroadcastMsg(msg) + require.NoError(t, err) + + param := bank.NewQueryBalanceParams(sdk.AccAddress(crypto.AddressHash(testRunnerHash))) + require.NoError(t, cclient.QueryJSON("custom/bank/balances", param, &coins)) + require.True(t, coins.IsZero(), coins) + }) }) t.Run("many executions in parallel", func(t *testing.T) { diff --git a/e2e/process_test.go b/e2e/process_test.go index 9856955db..0b1d89aff 100644 --- a/e2e/process_test.go +++ b/e2e/process_test.go @@ -124,12 +124,12 @@ func testProcess(t *testing.T) { t.Run("lcd", func(t *testing.T) { ownerships := make([]*ownership.Ownership, 0) lcdGet(t, "ownership/list", &ownerships) - require.Len(t, ownerships, 1) + require.Len(t, ownerships, 2) }) t.Run("grpc", func(t *testing.T) { ownerships, err := client.OwnershipClient.List(context.Background(), &pb.ListOwnershipRequest{}) require.NoError(t, err) - require.Len(t, ownerships.Ownerships, 1) + require.Len(t, ownerships.Ownerships, 2) }) }) } diff --git a/e2e/runner_test.go b/e2e/runner_test.go index bfd6650b4..02642eed4 100644 --- a/e2e/runner_test.go +++ b/e2e/runner_test.go @@ -35,6 +35,17 @@ func testRunner(t *testing.T) { require.NoError(t, err) }) + t.Run("recreate", func(t *testing.T) { + _, err := client.RunnerClient.Delete(context.Background(), &pb.DeleteRunnerRequest{Hash: testRunnerHash}) + require.NoError(t, err) + resp, err := client.RunnerClient.Create(context.Background(), &pb.CreateRunnerRequest{ + ServiceHash: testServiceHash, + Env: []string{"BAR=3", "REQUIRED=4"}, + }) + require.NoError(t, err) + testRunnerHash = resp.Hash + }) + t.Run("get", func(t *testing.T) { t.Run("grpc", func(t *testing.T) { resp, err := client.RunnerClient.Get(context.Background(), &pb.GetRunnerRequest{Hash: testRunnerHash}) diff --git a/ownership/ownership.pb.go b/ownership/ownership.pb.go index 86a57b02f..b35c324db 100644 --- a/ownership/ownership.pb.go +++ b/ownership/ownership.pb.go @@ -29,18 +29,21 @@ const ( Ownership_None Ownership_Resource = 0 Ownership_Service Ownership_Resource = 1 Ownership_Process Ownership_Resource = 2 + Ownership_Runner Ownership_Resource = 3 ) var Ownership_Resource_name = map[int32]string{ 0: "None", 1: "Service", 2: "Process", + 3: "Runner", } var Ownership_Resource_value = map[string]int32{ "None": 0, "Service": 1, "Process": 2, + "Runner": 3, } func (x Ownership_Resource) String() string { @@ -98,28 +101,28 @@ func init() { func init() { proto.RegisterFile("ownership.proto", fileDescriptor_21ae26e0dccf9d04) } var fileDescriptor_21ae26e0dccf9d04 = []byte{ - // 326 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x51, 0x4f, 0x4e, 0x32, 0x31, - 0x14, 0x67, 0x80, 0xef, 0x13, 0x2a, 0x2a, 0xe9, 0x6a, 0x62, 0x0c, 0x33, 0x36, 0x31, 0x21, 0x31, - 0x74, 0x12, 0x88, 0x9b, 0x71, 0x47, 0x5c, 0xb8, 0x52, 0x33, 0xec, 0xdc, 0x0d, 0xc3, 0xa3, 0x6d, - 0x22, 0x2d, 0xb6, 0x33, 0x18, 0x4f, 0xe0, 0x55, 0x3c, 0x0a, 0x67, 0x70, 0x31, 0x89, 0x1e, 0x81, - 0x13, 0x98, 0x16, 0x41, 0x4d, 0x5c, 0x18, 0x77, 0xfd, 0xbd, 0xfc, 0xfe, 0xf5, 0x3d, 0x74, 0xa0, - 0x1e, 0x24, 0x68, 0xc3, 0xc5, 0x9c, 0xce, 0xb5, 0xca, 0x15, 0x46, 0x33, 0x30, 0x8c, 0xe6, 0x8f, - 0x73, 0x30, 0x87, 0x84, 0x29, 0xa6, 0x22, 0x37, 0x1f, 0x17, 0xd3, 0xc8, 0x22, 0x07, 0xdc, 0x6b, - 0xcd, 0x27, 0x4f, 0x35, 0xd4, 0xbc, 0xde, 0x78, 0x60, 0x86, 0xea, 0x3c, 0x35, 0xdc, 0xf7, 0x42, - 0xaf, 0xdb, 0x1a, 0x8e, 0x96, 0x65, 0x50, 0x79, 0x29, 0x83, 0x53, 0x26, 0x72, 0x5e, 0x8c, 0x69, - 0xa6, 0x66, 0x91, 0xb5, 0xef, 0x4d, 0x55, 0x21, 0x27, 0x69, 0x2e, 0x94, 0x8c, 0x40, 0x32, 0x21, - 0x21, 0xb2, 0x2a, 0x7a, 0x99, 0x1a, 0xbe, 0x2a, 0x83, 0x23, 0x0b, 0x62, 0xd2, 0x23, 0xe1, 0x22, - 0xbd, 0x13, 0x93, 0x34, 0x87, 0x98, 0x68, 0xb8, 0x2f, 0x84, 0x86, 0x09, 0x49, 0x5c, 0x00, 0x3e, - 0x47, 0xff, 0x5c, 0x73, 0xbf, 0x1a, 0x7a, 0xdd, 0xe6, 0xf0, 0x64, 0x55, 0x06, 0xc7, 0x6b, 0x99, - 0x4c, 0x67, 0x10, 0xf7, 0x7f, 0xd6, 0xae, 0x35, 0x98, 0xa3, 0x96, 0x06, 0xa3, 0x0a, 0x9d, 0x81, - 0x8d, 0xf4, 0x6b, 0xae, 0xed, 0xc5, 0xdf, 0xda, 0xee, 0x7d, 0x89, 0x1d, 0x90, 0xe4, 0x9b, 0x33, - 0x8e, 0x51, 0x63, 0x83, 0xfd, 0x7a, 0xe8, 0x75, 0xf7, 0xfb, 0x1d, 0xfa, 0xb9, 0x60, 0xba, 0x5d, - 0x1c, 0x4d, 0x3e, 0x58, 0xc9, 0x96, 0x4f, 0x28, 0x6a, 0x6c, 0xa6, 0xb8, 0x81, 0xea, 0x57, 0x4a, - 0x42, 0xbb, 0x82, 0x77, 0xd1, 0xce, 0x08, 0xf4, 0x42, 0x64, 0xd0, 0xf6, 0x2c, 0xb8, 0xd1, 0x2a, - 0x03, 0x63, 0xda, 0xd5, 0xe1, 0xd9, 0xf2, 0xb5, 0x53, 0x79, 0x7e, 0xeb, 0x78, 0xb7, 0xbf, 0xf8, - 0xc1, 0xf6, 0xec, 0xe3, 0xff, 0xee, 0x8e, 0x83, 0xf7, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2d, 0x23, - 0xcb, 0x4c, 0x0a, 0x02, 0x00, 0x00, + // 334 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x52, 0xcf, 0x4e, 0xc2, 0x30, + 0x18, 0x67, 0x80, 0x08, 0x9f, 0xa8, 0x4b, 0x4f, 0x8b, 0x31, 0x6c, 0x36, 0x31, 0x21, 0x31, 0x74, + 0x09, 0xc4, 0xcb, 0xbc, 0x11, 0x0f, 0x9e, 0xd4, 0x8c, 0x9b, 0xb7, 0x31, 0x3e, 0xb6, 0x26, 0xd2, + 0x62, 0xbb, 0x61, 0x7c, 0x0e, 0x5f, 0xc2, 0x47, 0xe1, 0x19, 0x3c, 0x2c, 0xd1, 0x47, 0xe0, 0x09, + 0xcc, 0x8a, 0xa0, 0x26, 0x1e, 0x8c, 0xb7, 0xfe, 0xbe, 0xfc, 0xfe, 0xf5, 0x6b, 0xe1, 0x50, 0x3e, + 0x0a, 0x54, 0x3a, 0xe5, 0x73, 0x36, 0x57, 0x32, 0x93, 0x04, 0x66, 0xa8, 0x13, 0x96, 0x3d, 0xcd, + 0x51, 0x1f, 0xd1, 0x44, 0x26, 0xd2, 0x37, 0xf3, 0x71, 0x3e, 0xf5, 0x4b, 0x64, 0x80, 0x39, 0xad, + 0xf9, 0xf4, 0xb9, 0x06, 0xad, 0x9b, 0x8d, 0x07, 0x49, 0xa0, 0x9e, 0x46, 0x3a, 0x75, 0x2c, 0xcf, + 0xea, 0xb6, 0x87, 0xa3, 0x65, 0xe1, 0x56, 0x5e, 0x0b, 0xf7, 0x2c, 0xe1, 0x59, 0x9a, 0x8f, 0x59, + 0x2c, 0x67, 0x7e, 0x69, 0xdf, 0x9b, 0xca, 0x5c, 0x4c, 0xa2, 0x8c, 0x4b, 0xe1, 0xa3, 0x48, 0xb8, + 0x40, 0xbf, 0x54, 0xb1, 0xab, 0x48, 0xa7, 0xab, 0xc2, 0x3d, 0x2e, 0x41, 0x40, 0x7b, 0xd4, 0x5b, + 0x44, 0xf7, 0x7c, 0x12, 0x65, 0x18, 0x50, 0x85, 0x0f, 0x39, 0x57, 0x38, 0xa1, 0xa1, 0x09, 0x20, + 0x17, 0xb0, 0x63, 0x9a, 0x3b, 0x55, 0xcf, 0xea, 0xb6, 0x86, 0xa7, 0xab, 0xc2, 0x3d, 0x59, 0xcb, + 0x44, 0x34, 0xc3, 0xa0, 0xff, 0xbb, 0x76, 0xad, 0x21, 0x29, 0xb4, 0x15, 0x6a, 0x99, 0xab, 0x18, + 0xcb, 0x48, 0xa7, 0x66, 0xda, 0x5e, 0xfe, 0xaf, 0xed, 0xfe, 0xb7, 0xd8, 0x01, 0x0d, 0x7f, 0x38, + 0x93, 0x00, 0x9a, 0x1b, 0xec, 0xd4, 0x3d, 0xab, 0x7b, 0xd0, 0xef, 0xb0, 0xaf, 0x05, 0xb3, 0xed, + 0xe2, 0x58, 0xf8, 0xc9, 0x0a, 0xb7, 0x7c, 0x1a, 0x40, 0x73, 0x33, 0x25, 0x4d, 0xa8, 0x5f, 0x4b, + 0x81, 0x76, 0x85, 0xec, 0xc1, 0xee, 0x08, 0xd5, 0x82, 0xc7, 0x68, 0x5b, 0x25, 0xb8, 0x55, 0x32, + 0x46, 0xad, 0xed, 0x2a, 0x01, 0x68, 0x84, 0xb9, 0x10, 0xa8, 0xec, 0xda, 0xf0, 0x7c, 0xf9, 0xd6, + 0xa9, 0xbc, 0xbc, 0x77, 0xac, 0xbb, 0x3f, 0xdc, 0x66, 0xfb, 0x05, 0xc6, 0x0d, 0xf3, 0xa6, 0x83, + 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0xb7, 0x4e, 0x6e, 0x16, 0x02, 0x00, 0x00, } func (this *Ownership) Equal(that interface{}) bool { diff --git a/protobuf/types/ownership.proto b/protobuf/types/ownership.proto index 6f8f8da95..9cfce7ecb 100644 --- a/protobuf/types/ownership.proto +++ b/protobuf/types/ownership.proto @@ -35,6 +35,7 @@ message Ownership { None = 0; Service = 1; Process = 2; + Runner = 3; } // Resource's type. diff --git a/x/execution/handler.go b/x/execution/handler.go index b90aa3dd9..e49ac70d2 100644 --- a/x/execution/handler.go +++ b/x/execution/handler.go @@ -24,7 +24,7 @@ func NewHandler(k Keeper) sdk.Handler { } } -// handleMsgCreateExecution creates a new process. +// handleMsgCreateExecution creates a new execution. func handleMsgCreateExecution(ctx sdk.Context, k Keeper, msg MsgCreateExecution) (*sdk.Result, error) { s, err := k.Create(ctx, msg) if err != nil { diff --git a/x/ownership/alias.go b/x/ownership/alias.go index c4f00e3a2..617409936 100644 --- a/x/ownership/alias.go +++ b/x/ownership/alias.go @@ -25,6 +25,8 @@ var ( ModuleCdc = types.ModuleCdc QueryListOwnerships = types.QueryListOwnerships + + NewMsgWithdrawCoins = types.NewMsgWithdrawCoins ) // module types @@ -32,4 +34,6 @@ type ( Keeper = keeper.Keeper GenesisState = types.GenesisState Params = types.Params + + MsgWithdrawCoins = types.MsgWithdrawCoins ) diff --git a/x/ownership/client/cli/tx.go b/x/ownership/client/cli/tx.go index 67ad67c77..c3df07e36 100644 --- a/x/ownership/client/cli/tx.go +++ b/x/ownership/client/cli/tx.go @@ -1,11 +1,17 @@ package cli import ( + "bufio" "fmt" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/mesg-foundation/engine/hash" "github.com/mesg-foundation/engine/x/ownership/internal/types" "github.com/spf13/cobra" ) @@ -20,7 +26,40 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { RunE: client.ValidateCmd, } - ownershipTxCmd.AddCommand(flags.PostCommands()...) - + ownershipTxCmd.AddCommand(flags.PostCommands( + GetCmdWithdrawCoins(cdc), + )...) return ownershipTxCmd } + +// GetCmdWithdrawCoins is the CLI command for sending a WithdrawCoins transaction +func GetCmdWithdrawCoins(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "withdraw-coins [resource] [amount]", + Short: "withdraw amount from resource address", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) + + h, err := hash.Decode(args[0]) + if err != nil { + return err + } + + coins, err := sdk.ParseCoins(args[1]) + if err != nil { + return err + } + + msg := types.NewMsgWithdrawCoins(h, coins, cliCtx.GetFromAddress()) + if err = msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} diff --git a/x/ownership/client/rest/tx.go b/x/ownership/client/rest/tx.go index ae173af7c..ef371501b 100644 --- a/x/ownership/client/rest/tx.go +++ b/x/ownership/client/rest/tx.go @@ -1,9 +1,62 @@ package rest import ( - "github.com/gorilla/mux" + "net/http" "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/gorilla/mux" + "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/x/ownership/internal/types" ) -func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {} +type withdrawCoinsReq struct { + BaseReq rest.BaseReq `json:"base_req"` + Amount string `json:"amount"` + Hash hash.Hash `json:"hash"` +} + +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc( + "/ownership/withdraw-coins", + txWithdrawCoinsHandlerFn(cliCtx), + ).Methods(http.MethodPost) +} + +func txWithdrawCoinsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req withdrawCoinsReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + owner, err := sdk.AccAddressFromBech32(baseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + amt, err := sdk.ParseCoins(req.Amount) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + msg := types.NewMsgWithdrawCoins(req.Hash, amt, owner) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) + } +} diff --git a/x/ownership/handler.go b/x/ownership/handler.go index 322eb7fc0..e6fae32af 100644 --- a/x/ownership/handler.go +++ b/x/ownership/handler.go @@ -11,7 +11,33 @@ import ( // NewHandler creates an sdk.Handler for all the instance type messages func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg) - return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case MsgWithdrawCoins: + return handleMsgWithdrawCoins(ctx, k, msg) + default: + errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg) + return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) + } } } + +// handleMsgWithdrawCoins withdraws coins from resource to the owner. +func handleMsgWithdrawCoins(ctx sdk.Context, k Keeper, msg MsgWithdrawCoins) (*sdk.Result, error) { + if err := k.WithdrawCoins(ctx, msg); err != nil { + return nil, err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, types.EventTypeWithdrawCoins), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Hash.String()), + ), + ) + + return &sdk.Result{ + Events: ctx.EventManager().Events(), + }, nil +} diff --git a/x/ownership/internal/keeper/keeper.go b/x/ownership/internal/keeper/keeper.go index 7a6a3a05e..53e3c7c29 100644 --- a/x/ownership/internal/keeper/keeper.go +++ b/x/ownership/internal/keeper/keeper.go @@ -5,9 +5,11 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/mesg-foundation/engine/hash" "github.com/mesg-foundation/engine/ownership" "github.com/mesg-foundation/engine/x/ownership/internal/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/log" ) @@ -15,13 +17,16 @@ import ( type Keeper struct { storeKey sdk.StoreKey cdc *codec.Codec + + bankKeeper types.BankKeeper } // NewKeeper creates a ownership keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, bankKeeper types.BankKeeper) Keeper { keeper := Keeper{ - storeKey: key, - cdc: cdc, + storeKey: key, + cdc: cdc, + bankKeeper: bankKeeper, } return keeper } @@ -77,12 +82,31 @@ func (k Keeper) Delete(ctx sdk.Context, owner sdk.AccAddress, resourceHash hash. return fmt.Errorf("resource %q do not have any ownership", resourceHash) } + // transfer all spendable coins from resource address to owner + addr := sdk.AccAddress(crypto.AddressHash(resourceHash)) + coins := k.bankKeeper.GetCoins(ctx, addr) + if !coins.IsZero() { + if err := k.bankKeeper.SendCoins(ctx, addr, owner, coins); err != nil { + return err + } + } + + // remove all ownerships for _, hash := range hashes { store.Delete(hash) } return nil } +// WithdrawCoins try to withdraw coins to owner rom specific resource. +func (k Keeper) WithdrawCoins(ctx sdk.Context, msg types.MsgWithdrawCoins) error { + if len(k.findOwnerships(ctx.KVStore(k.storeKey), msg.Owner.String(), msg.Hash)) == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "address %s is not owner of resource %s", msg.Owner, msg.Hash) + } + addr := sdk.AccAddress(crypto.AddressHash(msg.Hash)) + return k.bankKeeper.SendCoins(ctx, addr, msg.Owner, msg.Amount) +} + // hasOwner checks if given resource has owner. Returns all ownership hash and true if has one // nil and false otherwise. func (k Keeper) findOwnerships(store sdk.KVStore, owner string, resourceHash hash.Hash) []hash.Hash { diff --git a/x/ownership/internal/types/codec.go b/x/ownership/internal/types/codec.go index b243782e1..eacaa243d 100644 --- a/x/ownership/internal/types/codec.go +++ b/x/ownership/internal/types/codec.go @@ -5,7 +5,9 @@ import ( ) // RegisterCodec registers concrete types on codec. -func RegisterCodec(cdc *codec.Codec) {} +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgWithdrawCoins{}, "ownership/WithdrawCoins", nil) +} // ModuleCdc defines the module codec. var ModuleCdc *codec.Codec diff --git a/x/ownership/internal/types/events.go b/x/ownership/internal/types/events.go new file mode 100644 index 000000000..9152c4e12 --- /dev/null +++ b/x/ownership/internal/types/events.go @@ -0,0 +1,8 @@ +package types + +// ownership module event types +const ( + EventTypeWithdrawCoins = "WithdrawCoins" + + AttributeValueCategory = ModuleName +) diff --git a/x/ownership/internal/types/expected_keepers.go b/x/ownership/internal/types/expected_keepers.go index 23128499d..c0d3713fc 100644 --- a/x/ownership/internal/types/expected_keepers.go +++ b/x/ownership/internal/types/expected_keepers.go @@ -12,3 +12,9 @@ type ParamSubspace interface { GetParamSet(ctx sdk.Context, ps params.ParamSet) SetParamSet(ctx sdk.Context, ps params.ParamSet) } + +// BankKeeper module interface. +type BankKeeper interface { + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins +} diff --git a/x/ownership/internal/types/msg.go b/x/ownership/internal/types/msg.go new file mode 100644 index 000000000..2c25c7dc2 --- /dev/null +++ b/x/ownership/internal/types/msg.go @@ -0,0 +1,58 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/mesg-foundation/engine/hash" +) + +// MsgWithdrawCoins defines a state transition to create a execution. +type MsgWithdrawCoins struct { + Hash hash.Hash `json:"hash"` + Amount sdk.Coins `json:"amount"` + Owner sdk.AccAddress `json:"owner"` +} + +// NewMsgWithdrawCoins is a constructor function for MsgWithdrawCoins. +func NewMsgWithdrawCoins(h hash.Hash, amount sdk.Coins, owner sdk.AccAddress) *MsgWithdrawCoins { + return &MsgWithdrawCoins{ + Hash: h, + Amount: amount, + Owner: owner, + } +} + +// Route should return the name of the module. +func (msg MsgWithdrawCoins) Route() string { + return ModuleName +} + +// Type returns the action. +func (msg MsgWithdrawCoins) Type() string { + return "WithdrawCoins" +} + +// ValidateBasic runs stateless checks on the message. +func (msg MsgWithdrawCoins) ValidateBasic() error { + if msg.Amount.IsAnyNegative() { + return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "price must be positive") + } + if msg.Owner.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Owner.String()) + } + if msg.Hash.IsZero() || !msg.Hash.Valid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid resource address: %s", msg.Owner.String()) + } + + return nil +} + +// GetSignBytes encodes the message for signing. +func (msg MsgWithdrawCoins) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners defines whose signature is required. +func (msg MsgWithdrawCoins) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Owner} +} diff --git a/x/runner/internal/keeper/keeper.go b/x/runner/internal/keeper/keeper.go index dc9bf1908..4d0187b4c 100644 --- a/x/runner/internal/keeper/keeper.go +++ b/x/runner/internal/keeper/keeper.go @@ -6,7 +6,9 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/mesg-foundation/engine/hash" + ownershippb "github.com/mesg-foundation/engine/ownership" "github.com/mesg-foundation/engine/runner" "github.com/mesg-foundation/engine/x/runner/internal/types" "github.com/tendermint/tendermint/libs/log" @@ -14,17 +16,19 @@ import ( // Keeper of the runner store type Keeper struct { - storeKey sdk.StoreKey - cdc *codec.Codec - instanceKeeper types.InstanceKeeper + storeKey sdk.StoreKey + cdc *codec.Codec + instanceKeeper types.InstanceKeeper + ownershipKeeper types.OwnershipKeeper } // NewKeeper creates a runner keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, instanceKeeper types.InstanceKeeper) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, instanceKeeper types.InstanceKeeper, ownershipKeeper types.OwnershipKeeper) Keeper { keeper := Keeper{ - storeKey: key, - cdc: cdc, - instanceKeeper: instanceKeeper, + storeKey: key, + cdc: cdc, + instanceKeeper: instanceKeeper, + ownershipKeeper: ownershipKeeper, } return keeper } @@ -39,7 +43,7 @@ func (k Keeper) Create(ctx sdk.Context, msg *types.MsgCreateRunner) (*runner.Run store := ctx.KVStore(k.storeKey) inst, err := k.instanceKeeper.FetchOrCreate(ctx, msg.ServiceHash, msg.EnvHash) if err != nil { - return nil, err + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) } r := &runner.Runner{ @@ -48,13 +52,18 @@ func (k Keeper) Create(ctx sdk.Context, msg *types.MsgCreateRunner) (*runner.Run } r.Hash = hash.Dump(r) if store.Has(r.Hash) { - return nil, fmt.Errorf("runner %q already exists", r.Hash) + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "runner %q already exists", r.Hash) } value, err := k.cdc.MarshalBinaryLengthPrefixed(r) if err != nil { - return nil, err + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) } + + if _, err := k.ownershipKeeper.Set(ctx, msg.Address, r.Hash, ownershippb.Ownership_Runner); err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) + } + store.Set(r.Hash, value) return r, nil } @@ -74,6 +83,9 @@ func (k Keeper) Delete(ctx sdk.Context, msg *types.MsgDeleteRunner) error { if r.Address != msg.Address.String() { return errors.New("only the runner owner can remove itself") } + if err := k.ownershipKeeper.Delete(ctx, msg.Address, r.Hash); err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) + } store.Delete(msg.RunnerHash) return nil } diff --git a/x/runner/internal/types/expected_keepers.go b/x/runner/internal/types/expected_keepers.go index 5b0fd210b..8814d2286 100644 --- a/x/runner/internal/types/expected_keepers.go +++ b/x/runner/internal/types/expected_keepers.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" "github.com/mesg-foundation/engine/hash" "github.com/mesg-foundation/engine/instance" + ownershippb "github.com/mesg-foundation/engine/ownership" ) // ParamSubspace defines the expected Subspace interfacace @@ -19,3 +20,9 @@ type ParamSubspace interface { type InstanceKeeper interface { FetchOrCreate(ctx sdk.Context, serviceHash hash.Hash, envHash hash.Hash) (*instance.Instance, error) } + +// OwnershipKeeper module interface. +type OwnershipKeeper interface { + Set(ctx sdk.Context, owner sdk.AccAddress, resourceHash hash.Hash, resource ownershippb.Ownership_Resource) (*ownershippb.Ownership, error) + Delete(ctx sdk.Context, owner sdk.AccAddress, resourceHash hash.Hash) error +}