Improved apps with better handling/routing and simpler MsgIssue

This commit is contained in:
Aditya Sripal 2018-06-27 14:42:30 -07:00 committed by Ethan Buchman
parent 6bbe295d7f
commit 98be0e7f76
4 changed files with 214 additions and 235 deletions

View File

@ -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"`
}

View File

@ -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

View File

@ -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()
}
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()
}
// 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)
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{}
}

View File

@ -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{}
}