From 98be0e7f76fe89aa2d2c786ff5f2481d0022d9a4 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 27 Jun 2018 14:42:30 -0700 Subject: [PATCH] Improved apps with better handling/routing and simpler MsgIssue --- docs/core/examples/app1.go | 65 ++++++------ docs/core/examples/app2.go | 196 ++++++++++++++++--------------------- docs/core/examples/app3.go | 115 ++++++++++++---------- docs/core/examples/app4.go | 73 +++++++------- 4 files changed, 214 insertions(+), 235 deletions(-) diff --git a/docs/core/examples/app1.go b/docs/core/examples/app1.go index ec5b779e7..56fcea0ec 100644 --- a/docs/core/examples/app1.go +++ b/docs/core/examples/app1.go @@ -2,7 +2,6 @@ package app import ( "encoding/json" - "reflect" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" @@ -35,7 +34,7 @@ func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp1Handler(keyAccount)) + AddRoute("send", handleMsgSend(keyAccount)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount) @@ -65,7 +64,7 @@ func NewMsgSend(from, to sdk.Address, amt sdk.Coins) MsgSend { } // Implements Msg. -func (msg MsgSend) Type() string { return "bank" } +func (msg MsgSend) Type() string { return "send" } // Implements Msg. Ensure the addresses are good and the // amount is positive. @@ -105,41 +104,40 @@ func (msg MsgSend) Tags() sdk.Tags { //------------------------------------------------------------------ // Handler for the message -func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler { +// Handle MsgSend. +// NOTE: msg.From, msg.To, and msg.Amount were already validated +func handleMsgSend(key *sdk.KVStoreKey) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case MsgSend: - return handleMsgSend(ctx, keyAcc, msg) - default: - errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() + sendMsg, ok := msg.(MsgSend) + if !ok { + // Create custom error message and return result + // Note: Using unreserved error codespace + return sdk.NewError(2, 1, "Send Message is malformed").Result() + } + + + // Load the store. + store := ctx.KVStore(key) + + // Debit from the sender. + if res := handleFrom(store, sendMsg.From, sendMsg.Amount); !res.IsOK() { + return res + } + + // Credit the receiver. + if res := handleTo(store, sendMsg.To, sendMsg.Amount); !res.IsOK() { + return res + } + + // Return a success (Code 0). + // Add list of key-value pair descriptors ("tags"). + return sdk.Result{ + Tags: sendMsg.Tags(), } } } -// Handle MsgSend. -// NOTE: msg.From, msg.To, and msg.Amount were already validated -func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result { - // Load the store. - store := ctx.KVStore(key) - - // Debit from the sender. - if res := handleFrom(store, msg.From, msg.Amount); !res.IsOK() { - return res - } - - // Credit the receiver. - if res := handleTo(store, msg.To, msg.Amount); !res.IsOK() { - return res - } - - // Return a success (Code 0). - // Add list of key-value pair descriptors ("tags"). - return sdk.Result{ - Tags: msg.Tags(), - } -} - +// Convenience Handlers func handleFrom(store sdk.KVStore, from sdk.Address, amt sdk.Coins) sdk.Result { // Get sender account from the store. accBytes := store.Get(from) @@ -210,6 +208,7 @@ func handleTo(store sdk.KVStore, to sdk.Address, amt sdk.Coins) sdk.Result { return sdk.Result{} } +// Simple account struct type appAccount struct { Coins sdk.Coins `json:"coins"` } diff --git a/docs/core/examples/app2.go b/docs/core/examples/app2.go index 5959fcd39..ff5ee981f 100644 --- a/docs/core/examples/app2.go +++ b/docs/core/examples/app2.go @@ -49,7 +49,8 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp2Handler(keyAccount, keyMain)) + AddRoute("send", handleMsgSend(keyAccount)). + AddRoute("issue", handleMsgIssue(keyAccount, keyMain)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount, keyMain) @@ -71,36 +72,32 @@ type CoinMetadata struct { //------------------------------------------------------------------ // Msgs -// Create Output struct to allow single message to issue arbitrary coins to multiple users -type Output struct { - Address sdk.Address - Coins sdk.Coins -} - -// Single permissioned issuer can issue multiple outputs +// Single permissioned issuer can issue Coin to Receiver +// if he is the issuer in Coin Metadata // Implements sdk.Msg Interface type MsgIssue struct { Issuer sdk.Address - Outputs []Output + Receiver sdk.Address + Coin sdk.Coin } // nolint -func (msg MsgIssue) Type() string { return "bank" } +func (msg MsgIssue) Type() string { return "issue" } func (msg MsgIssue) ValidateBasic() sdk.Error { if len(msg.Issuer) == 0 { return sdk.ErrInvalidAddress("Issuer address cannot be empty") } - for _, o := range msg.Outputs { - if len(o.Address) == 0 { - return sdk.ErrInvalidAddress("Output address cannot be empty") - } - // Cannot issue zero or negative coins - if !o.Coins.IsPositive() { - return sdk.ErrInvalidCoins("Cannot issue 0 or negative coin amounts") - } + if len(msg.Receiver) == 0 { + return sdk.ErrInvalidAddress("Receiver address cannot be empty") } + + // Cannot issue zero or negative coins + if !msg.Coin.IsPositive() { + return sdk.ErrInvalidCoins("Cannot issue 0 or negative coin amounts") + } + return nil } @@ -117,112 +114,89 @@ func (msg MsgIssue) GetSigners() []sdk.Address { return []sdk.Address{msg.Issuer} } +// Returns the sdk.Tags for the message +func (msg MsgIssue) Tags() sdk.Tags { + return sdk.NewTags("issuer", []byte(msg.Issuer.String())). + AppendTag("receiver", []byte(msg.Receiver.String())) +} + //------------------------------------------------------------------ // Handler for the message -func NewApp2Handler(keyAcc *sdk.KVStoreKey, keyMain *sdk.KVStoreKey) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case MsgSend: - return handleMsgSend(ctx, keyAcc, msg) - case MsgIssue: - return handleMsgIssue(ctx, keyMain, keyAcc, msg) - default: - errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() - } - } -} - // Handle Msg Issue -func handleMsgIssue(ctx sdk.Context, keyMain *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey, msg MsgIssue) sdk.Result { - store := ctx.KVStore(keyMain) - accStore := ctx.KVStore(keyAcc) - - for _, o := range msg.Outputs { - for _, coin := range o.Coins { - bz := store.Get([]byte(coin.Denom)) - var metadata CoinMetadata - - if bz == nil { - // Coin not set yet, initialize with issuer and default values - // Coin amount can't be above default value - if coin.Amount.GT(sdk.NewInt(1000000)) { - return sdk.ErrInvalidCoins("Cannot issue that many new coins").Result() - } - metadata = CoinMetadata{ - TotalSupply: sdk.NewInt(1000000), - CurrentSupply: sdk.NewInt(0), - Issuer: msg.Issuer, - Decimal: 10, - } - } else { - // Decode coin metadata - err := json.Unmarshal(bz, &metadata) - if err != nil { - return sdk.ErrInternal("Decoding coin metadata failed").Result() - } - } - - // Return error result if msg Issuer is not equal to coin issuer - if !reflect.DeepEqual(metadata.Issuer, msg.Issuer) { - return sdk.ErrUnauthorized(fmt.Sprintf("Msg issuer cannot issue these coins: %s", coin.Denom)).Result() - } - - // Issuer cannot issue more than remaining supply - issuerSupply := metadata.TotalSupply.Sub(metadata.CurrentSupply) - if coin.Amount.GT(issuerSupply) { - return sdk.ErrInsufficientCoins(fmt.Sprintf("Issuer cannot issue that many coins. Current issuer supply: %d", issuerSupply.Int64())).Result() - } - - // Update coin metadata - metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) - - val, err := json.Marshal(metadata) - if err != nil { - return sdk.ErrInternal("Encoding coin metadata failed").Result() - } - - // Update coin metadata in store - store.Set([]byte(coin.Denom), val) +func handleMsgIssue(keyMain *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + issueMsg, ok := msg.(MsgIssue) + if !ok { + return sdk.NewError(2, 1, "IssueMsg is malformed").Result() } - // Add coins to receiver account - bz := accStore.Get(o.Address) - var acc appAccount - if bz == nil { - // Receiver account does not already exist, create a new one. - acc = appAccount{} - } else { - // Receiver account already exists. Retrieve and decode it. - err := json.Unmarshal(bz, &acc) - if err != nil { - return sdk.ErrInternal("Account decoding error").Result() - } + store := ctx.KVStore(keyMain) + accStore := ctx.KVStore(keyAcc) + + if res := handleMetaData(store, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { + return res } - // Add amount to receiver's old coins - receiverCoins := acc.Coins.Plus(o.Coins) - - // Update receiver account - acc.Coins = receiverCoins - - // Encode receiver account - val, err := json.Marshal(acc) - if err != nil { - return sdk.ErrInternal("Account encoding error").Result() + // Issue coins to receiver using previously defined handleTo function + if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() { + return res } - // set account with new issued coins in store - store.Set(o.Address, val) + return sdk.Result{ + Tags: issueMsg.Tags(), + } } - - return sdk.Result{ - // TODO: Tags - } - } +func handleMetaData(store sdk.KVStore, issuer sdk.Address, coin sdk.Coin) sdk.Result { + bz := store.Get([]byte(coin.Denom)) + var metadata CoinMetadata + + if bz == nil { + // Coin not set yet, initialize with issuer and default values + // Coin amount can't be above default value + if coin.Amount.GT(sdk.NewInt(1000000)) { + return sdk.ErrInvalidCoins("Cannot issue that many new coins").Result() + } + metadata = CoinMetadata{ + TotalSupply: sdk.NewInt(1000000), + CurrentSupply: sdk.NewInt(0), + Issuer: issuer, + Decimal: 10, + } + } else { + // Decode coin metadata + err := json.Unmarshal(bz, &metadata) + if err != nil { + return sdk.ErrInternal("Decoding coin metadata failed").Result() + } + } + + if !reflect.DeepEqual(metadata.Issuer, issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() + } + + // Update coin current circulating supply + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + // Current supply cannot exceed total supply + if metadata.TotalSupply.LT(metadata.CurrentSupply) { + return sdk.ErrInsufficientCoins("Issuer cannot issue more than total supply of coin").Result() + } + + val, err := json.Marshal(metadata) + if err != nil { + return sdk.ErrInternal(fmt.Sprintf("Error encoding metadata: %s", err.Error())).Result() + } + + // Update store with new metadata + store.Set([]byte(coin.Denom), val) + + return sdk.Result{} +} + + //------------------------------------------------------------------ // Tx diff --git a/docs/core/examples/app3.go b/docs/core/examples/app3.go index 25036ef1b..28d51c77a 100644 --- a/docs/core/examples/app3.go +++ b/docs/core/examples/app3.go @@ -2,8 +2,8 @@ package app import ( "encoding/json" - "fmt" "reflect" + "fmt" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" @@ -44,7 +44,8 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp3Handler(accountKeeper, metadataMapper)) + AddRoute("send", betterHandleMsgSend(accountKeeper)). + AddRoute("issue", betterHandleMsgIssue(metadataMapper, accountKeeper)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount, keyMain, keyFees) @@ -55,71 +56,77 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { return app } -func NewApp3Handler(accountKeeper bank.Keeper, metadataMapper MetaDataMapper) sdk.Handler { +func betterHandleMsgSend(accountKeeper bank.Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case MsgSend: - return betterHandleMsgSend(ctx, accountKeeper, msg) - case MsgIssue: - return betterHandleMsgIssue(ctx, metadataMapper, accountKeeper, msg) - default: - errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() + sendMsg, ok := msg.(MsgSend) + if !ok { + return sdk.NewError(2, 1, "Send Message Malformed").Result() + } + + // Subtract coins from sender account + _, _, err := accountKeeper.SubtractCoins(ctx, sendMsg.From, sendMsg.Amount) + if err != nil { + // if error, return its result + return err.Result() + } + + // Add coins to receiver account + _, _, err = accountKeeper.AddCoins(ctx, sendMsg.To, sendMsg.Amount) + if err != nil { + // if error, return its result + return err.Result() + } + + return sdk.Result{ + Tags: sendMsg.Tags(), } } } -func betterHandleMsgSend(ctx sdk.Context, accountKeeper bank.Keeper, msg MsgSend) sdk.Result { - // Subtract coins from sender account - _, _, err := accountKeeper.SubtractCoins(ctx, msg.From, msg.Amount) - if err != nil { - // if error, return its result - return err.Result() - } - - // Add coins to receiver account - _, _, err = accountKeeper.AddCoins(ctx, msg.To, msg.Amount) - if err != nil { - // if error, return its result - return err.Result() - } - - return sdk.Result{} -} - -func betterHandleMsgIssue(ctx sdk.Context, metadataMapper MetaDataMapper, accountKeeper bank.Keeper, msg MsgIssue) sdk.Result { - for _, o := range msg.Outputs { - for _, coin := range o.Coins { - metadata := metadataMapper.GetMetaData(ctx, coin.Denom) - if len(metadata.Issuer) == 0 { - // coin doesn't have issuer yet, set issuer to msg issuer - metadata.Issuer = msg.Issuer - } - - // Check that msg Issuer is authorized to issue these coins - if !reflect.DeepEqual(metadata.Issuer, msg.Issuer) { - return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue these coins: %s", coin.Denom)).Result() - } - - // Issuer cannot issue more than remaining supply - issuerSupply := metadata.TotalSupply.Sub(metadata.CurrentSupply) - if coin.Amount.GT(issuerSupply) { - return sdk.ErrInsufficientCoins(fmt.Sprintf("Issuer cannot issue that many coins. Current issuer supply: %d", issuerSupply.Int64())).Result() - } - - // update metadata current circulating supply - metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) - - metadataMapper.SetMetaData(ctx, coin.Denom, metadata) +func betterHandleMsgIssue(metadataMapper MetaDataMapper, accountKeeper bank.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + issueMsg, ok := msg.(MsgIssue) + if !ok { + return sdk.NewError(2, 1, "Issue Message Malformed").Result() } + if res := betterHandleMetaData(ctx, metadataMapper, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { + return res + } + // Add newly issued coins to output address - _, _, err := accountKeeper.AddCoins(ctx, o.Address, o.Coins) + _, _, err := accountKeeper.AddCoins(ctx, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}) if err != nil { return err.Result() } + + return sdk.Result{ + Tags: issueMsg.Tags(), + } + } +} + +func betterHandleMetaData(ctx sdk.Context, metadataMapper MetaDataMapper, issuer sdk.Address, coin sdk.Coin) sdk.Result { + metadata := metadataMapper.GetMetaData(ctx, coin.Denom) + + // Metadata was created fresh, should set issuer to msg issuer + if len(metadata.Issuer) == 0 { + metadata.Issuer = issuer } + if !reflect.DeepEqual(metadata.Issuer, issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() + } + + // Update current circulating supply + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + // Current supply cannot exceed total supply + if metadata.TotalSupply.LT(metadata.CurrentSupply) { + return sdk.ErrInsufficientCoins("Issuer cannot issue more than total supply of coin").Result() + } + + metadataMapper.SetMetaData(ctx, coin.Denom, metadata) return sdk.Result{} } diff --git a/docs/core/examples/app4.go b/docs/core/examples/app4.go index c7048409e..c8905f9a9 100644 --- a/docs/core/examples/app4.go +++ b/docs/core/examples/app4.go @@ -47,7 +47,8 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp4Handler(accountKeeper, metadataMapper)) + AddRoute("send", betterHandleMsgSend(accountKeeper)). + AddRoute("issue", evenBetterHandleMsgIssue(metadataMapper, accountKeeper)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount, keyMain, keyFees) @@ -130,50 +131,48 @@ func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper, metadataM //--------------------------------------------------------------------------------------------- // Now that initializing coin metadata is done in InitChainer we can simplifiy handleMsgIssue -func NewApp4Handler(accountKeeper bank.Keeper, metadataMapper MetaDataMapper) sdk.Handler { +func evenBetterHandleMsgIssue(metadataMapper MetaDataMapper, accountKeeper bank.Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - switch msg := msg.(type) { - case MsgSend: - return betterHandleMsgSend(ctx, accountKeeper, msg) - case MsgIssue: - // use new MsgIssue handler - return evenBetterHandleMsgIssue(ctx, metadataMapper, accountKeeper, msg) - default: - errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() + issueMsg, ok := msg.(MsgIssue) + if !ok { + return sdk.NewError(2, 1, "Issue Message Malformed").Result() + } + + if res := evenBetterHandleMetaData(ctx, metadataMapper, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { + return res + } + + _, _, err := accountKeeper.AddCoins(ctx, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}) + if err != nil { + return err.Result() + } + + return sdk.Result{ + Tags: issueMsg.Tags(), } } } -func evenBetterHandleMsgIssue(ctx sdk.Context, metadataMapper MetaDataMapper, accountKeeper bank.Keeper, msg MsgIssue) sdk.Result { - for _, o := range msg.Outputs { - for _, coin := range o.Coins { - // Metadata is no longer created on the fly since it is initalized at genesis with InitChain - metadata := metadataMapper.GetMetaData(ctx, coin.Denom) - // Check that msg Issuer is authorized to issue these coins - if !reflect.DeepEqual(metadata.Issuer, msg.Issuer) { - return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue these coins: %s", coin.Denom)).Result() - } +func evenBetterHandleMetaData(ctx sdk.Context, metadataMapper MetaDataMapper, issuer sdk.Address, coin sdk.Coin) sdk.Result { + metadata := metadataMapper.GetMetaData(ctx, coin.Denom) - // Issuer cannot issue more than remaining supply - issuerSupply := metadata.TotalSupply.Sub(metadata.CurrentSupply) - if coin.Amount.GT(issuerSupply) { - return sdk.ErrInsufficientCoins(fmt.Sprintf("Issuer cannot issue that many coins. Current issuer supply: %d", issuerSupply.Int64())).Result() - } - - // update metadata current circulating supply - metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) - - metadataMapper.SetMetaData(ctx, coin.Denom, metadata) - } - - // Add newly issued coins to output address - _, _, err := accountKeeper.AddCoins(ctx, o.Address, o.Coins) - if err != nil { - return err.Result() - } + if reflect.DeepEqual(metadata, CoinMetadata{}) { + return sdk.ErrInvalidCoins(fmt.Sprintf("Cannot find metadata for coin: %s", coin.Denom)).Result() } + if !reflect.DeepEqual(metadata.Issuer, issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() + } + + // Update current circulating supply + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + // Current supply cannot exceed total supply + if metadata.TotalSupply.LT(metadata.CurrentSupply) { + return sdk.ErrInsufficientCoins("Issuer cannot issue more than total supply of coin").Result() + } + + metadataMapper.SetMetaData(ctx, coin.Denom, metadata) return sdk.Result{} }