Finish app1-3, app4 done minus staking

This commit is contained in:
Aditya Sripal 2018-06-26 19:09:54 -07:00 committed by Ethan Buchman
parent e6aec82f40
commit d1c9b8bdb9
4 changed files with 523 additions and 21 deletions

View File

@ -114,7 +114,6 @@ func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler {
// Handle MsgSend.
func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result {
// NOTE: from, to, and amount were already validated
store := ctx.KVStore(key)
// deduct msg amount from sender account
@ -155,10 +154,10 @@ func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result
bz = store.Get(msg.To)
var acc2 account
if bz == nil {
// Sender account does not already exist, create a new one.
acc2 = account{}
// Receiver account does not already exist, create a new one.
acc2 = account{Address: msg.To}
} else {
// Sender account already exists. Retrieve and decode it.
// Receiver account already exists. Retrieve and decode it.
err = json.Unmarshal(bz, &acc2)
if err != nil {
return sdk.ErrInternal("Account decoding error").Result()
@ -185,7 +184,9 @@ func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result
}
type account struct {
Address sdk.Address `json:"address"`
Coins sdk.Coins `json:"coins"`
SequenceNumber int64 `json:"sequence"`
}
//------------------------------------------------------------------

View File

@ -2,23 +2,35 @@ package app
import (
"reflect"
"encoding/json"
"fmt"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/go-crypto"
bapp "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
)
const (
app2Name = "App2"
)
var (
issuer = crypto.GenPrivKeyEd25519().PubKey().Address()
)
func NewCodec() *wire.Codec {
// TODO register
return nil
cdc := wire.NewCodec()
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil)
cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil)
return cdc
}
func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
@ -29,16 +41,19 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
app := bapp.NewBaseApp(app2Name, cdc, logger, db)
// Create a key for accessing the account store.
keyMain := sdk.NewKVStoreKey("main")
keyAccount := sdk.NewKVStoreKey("acc")
keyIssuer := sdk.NewKVStoreKey("issuer")
// set antehandler function
app.SetAnteHandler(antehandler)
// Register message routes.
// Note the handler gets access to the account store.
app.Router().
AddRoute("bank", NewApp2Handler(keyAccount, keyIssuer))
AddRoute("bank", NewApp2Handler(keyAccount, keyMain))
// Mount stores and load the latest state.
app.MountStoresIAVL(keyAccount, keyIssuer)
app.MountStoresIAVL(keyAccount, keyMain)
err := app.LoadLatestVersion(keyAccount)
if err != nil {
cmn.Exit(err.Error())
@ -46,21 +61,73 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
return app
}
// Coin Metadata
type CoinMetadata struct {
TotalSupply sdk.Int
CurrentSupply sdk.Int
Issuer sdk.Address
Decimal uint64
}
//------------------------------------------------------------------
// Msgs
// TODO: MsgIssue
// 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
// Implements sdk.Msg Interface
type MsgIssue struct {
Issuer sdk.Address
Outputs []Output
}
// nolint
func (msg MsgIssue) Type() string { return "bank" }
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")
}
}
return nil
}
func (msg MsgIssue) GetSignBytes() []byte {
bz, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return bz
}
// Implements Msg. Return the signer.
func (msg MsgIssue) GetSigners() []sdk.Address {
return []sdk.Address{msg.Issuer}
}
//------------------------------------------------------------------
// Handler for the message
func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler {
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:
// TODO
return handleMsgIssue(ctx, keyMain, keyAcc, msg)
default:
errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name()
return sdk.ErrUnknownRequest(errMsg).Result()
@ -68,12 +135,102 @@ func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler {
}
}
// 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)
}
// Add coins to receiver account
bz := accStore.Get(o.Address)
var acc account
if bz == nil {
// Receiver account does not already exist, create a new one.
acc = account{}
} else {
// Receiver account already exists. Retrieve and decode it.
err := json.Unmarshal(bz, &acc)
if err != nil {
return sdk.ErrInternal("Account decoding error").Result()
}
}
// 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()
}
// set account with new issued coins in store
store.Set(o.Address, val)
}
return sdk.Result{
// TODO: Tags
}
}
//------------------------------------------------------------------
// Tx
// Simple tx to wrap the Msg.
type app2Tx struct {
sdk.Msg
Signatures []auth.StdSignature
}
// This tx only has one Msg.
@ -85,3 +242,46 @@ func (tx app2Tx) GetMsgs() []sdk.Msg {
func (tx app2Tx) GetMemo() string {
return ""
}
func (tx app2Tx) GetSignatures() []auth.StdSignature {
return tx.Signatures
}
//------------------------------------------------------------------
// Simple antehandler that ensures msg signers has signed over msg signBytes w/ no replay protection
// Implement sdk.AnteHandler interface
func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) {
appTx, ok := tx.(app2Tx)
if !ok {
// set abort boolean to true so that we don't continue to process failed tx
return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true
}
// expect only one msg in app2Tx
msg := tx.GetMsgs()[0]
signerAddrs := msg.GetSigners()
signBytes := msg.GetSignBytes()
if len(signerAddrs) != len(appTx.GetSignatures()) {
return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true
}
for i, addr := range signerAddrs {
sig := appTx.GetSignatures()[i]
// check that submitted pubkey belongs to required address
if !reflect.DeepEqual(sig.PubKey.Address(), addr) {
return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true
}
// check that signature is over expected signBytes
if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) {
return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true
}
}
// authentication passed, app to continue processing by sending msg to handler
return ctx, sdk.Result{}, false
}

View File

@ -1,12 +1,19 @@
package app
import (
"reflect"
"encoding/json"
"fmt"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
bapp "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
)
const (
@ -22,17 +29,24 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp {
// Create a key for accessing the account store.
keyAccount := sdk.NewKVStoreKey("acc")
keyIssuer := sdk.NewKVStoreKey("issuer")
keyMain := sdk.NewKVStoreKey("main")
keyFees := sdk.NewKVStoreKey("fee")
// TODO: accounts, ante handler
// Set various mappers/keepers to interact easily with underlying stores
accountMapper := auth.NewAccountMapper(cdc, keyAccount, &auth.BaseAccount{})
accountKeeper := bank.NewKeeper(accountMapper)
metadataMapper := NewApp3MetaDataMapper(keyMain)
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
// Register message routes.
// Note the handler gets access to the account store.
app.Router().
AddRoute("bank", NewApp2Handler(keyAccount, keyIssuer))
AddRoute("bank", NewApp3Handler(accountKeeper, metadataMapper))
// Mount stores and load the latest state.
app.MountStoresIAVL(keyAccount, keyIssuer)
app.MountStoresIAVL(keyAccount, keyMain, keyFees)
err := app.LoadLatestVersion(keyAccount)
if err != nil {
cmn.Exit(err.Error())
@ -40,6 +54,124 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp {
return app
}
func NewApp3Handler(accountKeeper bank.Keeper, metadataMapper MetaDataMapper) 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()
}
}
}
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)
}
// Add newly issued coins to output address
_, _, err := accountKeeper.AddCoins(ctx, o.Address, o.Coins)
if err != nil {
return err.Result()
}
}
return sdk.Result{}
}
//------------------------------------------------------------------
// Mapper for Coin Metadata
// Example of a very simple user-defined mapper
type MetaDataMapper interface {
GetMetaData(sdk.Context, string) CoinMetadata
SetMetaData(sdk.Context, string, CoinMetadata)
}
type App3MetaDataMapper struct {
mainKey *sdk.KVStoreKey
}
func NewApp3MetaDataMapper(key *sdk.KVStoreKey) App3MetaDataMapper {
return App3MetaDataMapper{mainKey: key}
}
func (mdm App3MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMetadata {
store := ctx.KVStore(mdm.mainKey)
bz := store.Get([]byte(denom))
if bz == nil {
// Coin metadata doesn't exist, create new metadata with default params
return CoinMetadata{
TotalSupply: sdk.NewInt(1000000),
}
}
var metadata CoinMetadata
err := json.Unmarshal(bz, &metadata)
if err != nil {
panic(err)
}
return metadata
}
func (mdm App3MetaDataMapper) SetMetaData(ctx sdk.Context, denom string, metadata CoinMetadata) {
store := ctx.KVStore(mdm.mainKey)
val, err := json.Marshal(metadata)
if err != nil {
panic(err)
}
store.Set([]byte(denom), val)
}
//------------------------------------------------------------------
// StdTx

View File

@ -1,12 +1,20 @@
package app
import (
"encoding/json"
"reflect"
"fmt"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
abci "github.com/tendermint/abci/types"
bapp "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
)
const (
@ -18,23 +26,31 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp {
cdc := NewCodec()
// Create the base application object.
app := bapp.NewBaseApp(app4Name, cdc, logger, db)
app := bapp.NewBaseApp(app3Name, cdc, logger, db)
// Create a key for accessing the account store.
keyAccount := sdk.NewKVStoreKey("acc")
keyIssuer := sdk.NewKVStoreKey("issuer")
keyMain := sdk.NewKVStoreKey("main")
keyFees := sdk.NewKVStoreKey("fee")
// TODO: accounts, ante handler
// Set various mappers/keepers to interact easily with underlying stores
accountMapper := auth.NewAccountMapper(cdc, keyAccount, &auth.BaseAccount{})
accountKeeper := bank.NewKeeper(accountMapper)
metadataMapper := NewApp4MetaDataMapper(keyMain)
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
// TODO: AccountMapper, CoinKeepr
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
// Set InitChainer
app.SetInitChainer(NewInitChainer(cdc, accountMapper, metadataMapper))
// Register message routes.
// Note the handler gets access to the account store.
app.Router().
AddRoute("bank", NewApp2Handler(keyAccount, keyIssuer))
AddRoute("bank", NewApp4Handler(accountKeeper, metadataMapper))
// Mount stores and load the latest state.
app.MountStoresIAVL(keyAccount, keyIssuer)
app.MountStoresIAVL(keyAccount, keyMain, keyFees)
err := app.LoadLatestVersion(keyAccount)
if err != nil {
cmn.Exit(err.Error())
@ -42,6 +58,159 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp {
return app
}
type GenesisState struct {
Accounts []*GenesisAccount `json:"accounts"`
Coins []*GenesisCoin `json:"coins"`
}
// GenesisAccount doesn't need pubkey or sequence
type GenesisAccount struct {
Address sdk.Address `json:"address"`
Coins sdk.Coins `json:"coins"`
}
func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount, err error) {
baseAcc := auth.BaseAccount{
Address: ga.Address,
Coins: ga.Coins.Sort(),
}
return &baseAcc, nil
}
// GenesisCoin enforces CurrentSupply is 0 at genesis.
type GenesisCoin struct {
Issuer sdk.Address `json:"issuer"`
TotalSupply sdk.Int `json:"total_supply`
Decimal uint64 `json:"decimals"`
}
func (gc *GenesisCoin) ToMetaData() CoinMetadata {
return CoinMetadata{
Issuer: gc.Issuer,
TotalSupply: gc.TotalSupply,
Decimal: gc.Decimal,
}
}
// InitChainer will set initial balances for accounts as well as initial coin metadata
// MsgIssue can no longer be used to create new coin
func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper, metadataMapper MetaDataMapper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes
genesisState := new(GenesisState)
err := cdc.UnmarshalJSON(stateJSON, genesisState)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
for _, gacc := range genesisState.Accounts {
acc, err := gacc.ToAccount()
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
acc.AccountNumber = accountMapper.GetNextAccountNumber(ctx)
accountMapper.SetAccount(ctx, acc)
}
return abci.ResponseInitChain{}
}
}
//---------------------------------------------------------------------------------------------
// Now that initializing coin metadata is done in InitChainer we can simplifiy handleMsgIssue
func NewApp4Handler(accountKeeper bank.Keeper, metadataMapper MetaDataMapper) 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()
}
}
}
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()
}
// 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()
}
}
return sdk.Result{}
}
//---------------------------------------------------------------------------------------------
// Simpler MetaDataMapper no longer able to initalize default CoinMetaData
type App4MetaDataMapper struct {
mainKey *sdk.KVStoreKey
}
func NewApp4MetaDataMapper(key *sdk.KVStoreKey) App4MetaDataMapper {
return App4MetaDataMapper{mainKey: key}
}
func (mdm App4MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMetadata {
store := ctx.KVStore(mdm.mainKey)
bz := store.Get([]byte(denom))
if bz == nil {
// Coin metadata doesn't exist, create new metadata with default params
return CoinMetadata{}
}
var metadata CoinMetadata
err := json.Unmarshal(bz, &metadata)
if err != nil {
panic(err)
}
return metadata
}
func (mdm App4MetaDataMapper) SetMetaData(ctx sdk.Context, denom string, metadata CoinMetadata) {
store := ctx.KVStore(mdm.mainKey)
val, err := json.Marshal(metadata)
if err != nil {
panic(err)
}
store.Set([]byte(denom), val)
}
//------------------------------------------------------------------
// AccountMapper