-
Notifications
You must be signed in to change notification settings - Fork 0
/
app2.go
247 lines (198 loc) · 6.53 KB
/
app2.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package app
import (
"bytes"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
bapp "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
app2Name = "App2"
)
var (
issuer = ed25519.GenPrivKey().PubKey().Address()
)
func NewCodec() *codec.Codec {
cdc := codec.New()
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil)
cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil)
cryptoAmino.RegisterAmino(cdc)
return cdc
}
func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
cdc := NewCodec()
// Create the base application object.
app := bapp.NewBaseApp(app2Name, logger, db, tx2Decoder(cdc), sdk.CollectConfig{})
// Create a key for accessing the account store.
keyAccount := sdk.NewKVStoreKey("acc")
// Create a key for accessing the issue store.
keyIssue := sdk.NewKVStoreKey("issue")
// set antehandler function
app.SetAnteHandler(antehandler)
// Register message routes.
// Note the handler gets access to the account store.
app.Router().
AddRoute("send", handleMsgSend(keyAccount)).
AddRoute("issue", handleMsgIssue(keyAccount, keyIssue))
// Mount stores and load the latest state.
app.MountStoresIAVL(keyAccount, keyIssue)
err := app.LoadLatestVersion(keyAccount)
if err != nil {
cmn.Exit(err.Error())
}
return app
}
//------------------------------------------------------------------
// Msgs
// MsgIssue to allow a registered issuer
// to issue new coins.
type MsgIssue struct {
Issuer sdk.AccAddress
Receiver sdk.AccAddress
Coin sdk.Coin
}
// Implements Msg.
// nolint
func (msg MsgIssue) Route() string { return "issue" }
func (msg MsgIssue) Type() string { return "issue" }
// Implements Msg. Ensures addresses are valid and Coin is positive
func (msg MsgIssue) ValidateBasic() sdk.Error {
if len(msg.Issuer) == 0 {
return sdk.ErrInvalidAddress("Issuer address cannot be empty")
}
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
}
// Implements Msg. Get canonical sign bytes for MsgIssue
func (msg MsgIssue) GetSignBytes() []byte {
bz, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(bz)
}
// Implements Msg. Return the signer.
func (msg MsgIssue) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{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()))
}
func (msg MsgIssue) GetInvolvedAddresses() []sdk.AccAddress {
return msg.GetSigners()
}
//------------------------------------------------------------------
// Handler for the message
// Handle MsgIssue.
func handleMsgIssue(keyIssue *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, "MsgIssue is malformed").Result()
}
// Retrieve stores
issueStore := ctx.KVStore(keyIssue)
accStore := ctx.KVStore(keyAcc)
// Handle updating coin info
if res := handleIssuer(issueStore, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
return res
}
// Issue coins to receiver using previously defined handleTo function
if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() {
return res
}
return sdk.Result{
// Return result with Issue msg tags
Tags: issueMsg.Tags(),
}
}
}
func handleIssuer(store sdk.KVStore, issuer sdk.AccAddress, coin sdk.Coin) sdk.Result {
// the issuer address is stored directly under the coin denomination
denom := []byte(coin.Denom)
infoBytes := store.Get(denom)
if infoBytes == nil {
return sdk.ErrInvalidCoins(fmt.Sprintf("Unknown coin type %s", coin.Denom)).Result()
}
var coinInfo coinInfo
err := json.Unmarshal(infoBytes, &coinInfo)
if err != nil {
return sdk.ErrInternal("Error when deserializing coinInfo").Result()
}
// Msg Issuer is not authorized to issue these coins
if !bytes.Equal(coinInfo.Issuer, issuer) {
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
}
return sdk.Result{}
}
// coinInfo stores meta data about a coin
type coinInfo struct {
Issuer sdk.AccAddress `json:"issuer"`
}
//------------------------------------------------------------------
// Tx
// Simple tx to wrap the Msg.
type app2Tx struct {
sdk.Msg
PubKey crypto.PubKey
Signature []byte
}
// This tx only has one Msg.
func (tx app2Tx) GetMsgs() []sdk.Msg {
return []sdk.Msg{tx.Msg}
}
func (tx app2Tx) GetSignature() []byte {
return tx.Signature
}
// Amino decode app2Tx. Capable of decoding both MsgSend and MsgIssue
func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx app2Tx
err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx)
if err != nil {
return nil, sdk.ErrTxDecode(err.Error())
}
return tx, nil
}
}
//------------------------------------------------------------------
// Simple anteHandler that ensures msg signers have signed.
// Provides no replay protection.
func antehandler(ctx sdk.Context, tx sdk.Tx, mode sdk.RunTxMode) (_ 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 and one signer in app2Tx
msg := tx.GetMsgs()[0]
signerAddr := msg.GetSigners()[0]
signBytes := msg.GetSignBytes()
sig := appTx.GetSignature()
// check that submitted pubkey belongs to required address
if !bytes.Equal(appTx.PubKey.Address(), signerAddr) {
return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true
}
// check that signature is over expected signBytes
if !appTx.PubKey.VerifyBytes(signBytes, sig) {
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
}