cosmos-sdk/app.App -> cosmos-sdk/baseapp.BaseApp
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
var mainHeaderKey = []byte("header")
|
||||
|
||||
// BaseApp - The ABCI application
|
||||
type BaseApp struct {
|
||||
logger log.Logger
|
||||
|
||||
// Application name from abci.Info
|
||||
name string
|
||||
|
||||
// Main (uncached) state
|
||||
ms sdk.CommitMultiStore
|
||||
|
||||
// Unmarshal []byte into sdk.Tx
|
||||
txDecoder sdk.TxDecoder
|
||||
|
||||
// Ante handler for fee and auth.
|
||||
defaultAnteHandler sdk.AnteHandler
|
||||
|
||||
// Handle any kind of message.
|
||||
router Router
|
||||
|
||||
//--------------------
|
||||
// Volatile
|
||||
|
||||
// CheckTx state, a cache-wrap of `.ms`.
|
||||
msCheck sdk.CacheMultiStore
|
||||
|
||||
// DeliverTx state, a cache-wrap of `.ms`.
|
||||
msDeliver sdk.CacheMultiStore
|
||||
|
||||
// Current block header
|
||||
header abci.Header
|
||||
|
||||
// Cached validator changes from DeliverTx.
|
||||
valUpdates []abci.Validator
|
||||
}
|
||||
|
||||
var _ abci.Application = &BaseApp{}
|
||||
|
||||
func NewBaseApp(name string, ms sdk.CommitMultiStore) *BaseApp {
|
||||
return &BaseApp{
|
||||
logger: makeDefaultLogger(),
|
||||
name: name,
|
||||
ms: ms,
|
||||
router: NewRouter(),
|
||||
}
|
||||
}
|
||||
|
||||
func (app *BaseApp) Name() string {
|
||||
return app.name
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetTxDecoder(txDecoder sdk.TxDecoder) {
|
||||
app.txDecoder = txDecoder
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetDefaultAnteHandler(ah sdk.AnteHandler) {
|
||||
app.defaultAnteHandler = ah
|
||||
}
|
||||
|
||||
func (app *BaseApp) Router() Router {
|
||||
return app.router
|
||||
}
|
||||
|
||||
/* TODO consider:
|
||||
func (app *BaseApp) SetBeginBlocker(...) {}
|
||||
func (app *BaseApp) SetEndBlocker(...) {}
|
||||
func (app *BaseApp) SetInitStater(...) {}
|
||||
*/
|
||||
|
||||
func (app *BaseApp) LoadLatestVersion(mainKey sdk.SubstoreKey) error {
|
||||
app.ms.LoadLatestVersion()
|
||||
return app.initFromStore(mainKey)
|
||||
}
|
||||
|
||||
func (app *BaseApp) LoadVersion(version int64, mainKey sdk.SubstoreKey) error {
|
||||
app.ms.LoadVersion(version)
|
||||
return app.initFromStore(mainKey)
|
||||
}
|
||||
|
||||
// The last CommitID of the multistore.
|
||||
func (app *BaseApp) LastCommitID() sdk.CommitID {
|
||||
return app.ms.LastCommitID()
|
||||
}
|
||||
|
||||
// The last commited block height.
|
||||
func (app *BaseApp) LastBlockHeight() int64 {
|
||||
return app.ms.LastCommitID().Version
|
||||
}
|
||||
|
||||
// Initializes the remaining logic from app.ms.
|
||||
func (app *BaseApp) initFromStore(mainKey sdk.SubstoreKey) error {
|
||||
lastCommitID := app.ms.LastCommitID()
|
||||
main := app.ms.GetKVStore(mainKey)
|
||||
header := abci.Header{}
|
||||
|
||||
// Main store should exist.
|
||||
if main == nil {
|
||||
return errors.New("BaseApp expects MultiStore with 'main' KVStore")
|
||||
}
|
||||
|
||||
// If we've committed before, we expect main://<mainHeaderKey>.
|
||||
if !lastCommitID.IsZero() {
|
||||
headerBytes := main.Get(mainHeaderKey)
|
||||
if len(headerBytes) == 0 {
|
||||
errStr := fmt.Sprintf("Version > 0 but missing key %s", mainHeaderKey)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
err := proto.Unmarshal(headerBytes, &header)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to parse Header")
|
||||
}
|
||||
lastVersion := lastCommitID.Version
|
||||
if header.Height != lastVersion {
|
||||
errStr := fmt.Sprintf("Expected main://%s.Height %v but got %v", mainHeaderKey, lastVersion, header.Height)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
}
|
||||
|
||||
// Set BaseApp state.
|
||||
app.header = header
|
||||
app.msCheck = nil
|
||||
app.msDeliver = nil
|
||||
app.valUpdates = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
|
||||
|
||||
lastCommitID := app.ms.LastCommitID()
|
||||
|
||||
return abci.ResponseInfo{
|
||||
Data: app.name,
|
||||
LastBlockHeight: lastCommitID.Version,
|
||||
LastBlockAppHash: lastCommitID.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) {
|
||||
// TODO: Implement
|
||||
return
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
|
||||
// TODO: Use req.Validators
|
||||
return
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
|
||||
// TODO: See app/query.go
|
||||
return
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
|
||||
app.header = req.Header
|
||||
app.msDeliver = app.ms.CacheMultiStore()
|
||||
app.msCheck = app.ms.CacheMultiStore()
|
||||
return
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
|
||||
|
||||
result := app.runTx(true, txBytes)
|
||||
|
||||
return abci.ResponseCheckTx{
|
||||
Code: result.Code,
|
||||
Data: result.Data,
|
||||
Log: result.Log,
|
||||
GasWanted: result.GasWanted,
|
||||
Fee: cmn.KI64Pair{
|
||||
[]byte(result.FeeDenom),
|
||||
result.FeeAmount,
|
||||
},
|
||||
Tags: result.Tags,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
|
||||
|
||||
result := app.runTx(false, txBytes)
|
||||
|
||||
// After-handler hooks.
|
||||
if result.Code == abci.CodeTypeOK {
|
||||
app.valUpdates = append(app.valUpdates, result.ValidatorUpdates...)
|
||||
} else {
|
||||
// Even though the Code is not OK, there will be some side
|
||||
// effects, like those caused by fee deductions or sequence
|
||||
// incrementations.
|
||||
}
|
||||
|
||||
// Tell the blockchain engine (i.e. Tendermint).
|
||||
return abci.ResponseDeliverTx{
|
||||
Code: result.Code,
|
||||
Data: result.Data,
|
||||
Log: result.Log,
|
||||
GasWanted: result.GasWanted,
|
||||
GasUsed: result.GasUsed,
|
||||
Tags: result.Tags,
|
||||
}
|
||||
}
|
||||
|
||||
func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte) (result sdk.Result) {
|
||||
|
||||
// Handle any panics.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
result = sdk.Result{
|
||||
Code: 1, // TODO
|
||||
Log: fmt.Sprintf("Recovered: %v\n", r),
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var store sdk.MultiStore
|
||||
if isCheckTx {
|
||||
store = app.msCheck
|
||||
} else {
|
||||
store = app.msDeliver
|
||||
}
|
||||
|
||||
// Initialize arguments to Handler.
|
||||
var ctx = sdk.NewContext(
|
||||
store,
|
||||
app.header,
|
||||
isCheckTx,
|
||||
txBytes,
|
||||
)
|
||||
|
||||
// Decode the Tx.
|
||||
tx, err := app.txDecoder(txBytes)
|
||||
if err != nil {
|
||||
return sdk.Result{
|
||||
Code: 1, // TODO
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: override default ante handler w/ custom ante handler.
|
||||
|
||||
// Run the ante handler.
|
||||
ctx, result, abort := app.defaultAnteHandler(ctx, tx)
|
||||
if isCheckTx || abort {
|
||||
return result
|
||||
}
|
||||
|
||||
// Match and run route.
|
||||
msgType := tx.Type()
|
||||
handler := app.router.Route(msgType)
|
||||
result = handler(ctx, tx)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
|
||||
res.ValidatorUpdates = app.valUpdates
|
||||
app.valUpdates = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Implements ABCI
|
||||
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
||||
app.msDeliver.Write()
|
||||
commitID := app.ms.Commit()
|
||||
app.logger.Debug("Commit synced",
|
||||
"commit", commitID,
|
||||
)
|
||||
return abci.ResponseCommit{
|
||||
Data: commitID.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// Misc.
|
||||
|
||||
// Return index of list with validator of same PubKey, or -1 if no match
|
||||
func pubKeyIndex(val *abci.Validator, list []*abci.Validator) int {
|
||||
for i, v := range list {
|
||||
if bytes.Equal(val.PubKey, v.PubKey) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Make a simple default logger
|
||||
// TODO: Make log capturable for each transaction, and return it in
|
||||
// ResponseDeliverTx.Log and ResponseCheckTx.Log.
|
||||
func makeDefaultLogger() log.Logger {
|
||||
return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/go-crypto"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// A mock transaction to update a validator's voting power.
|
||||
type testTx struct {
|
||||
Addr []byte
|
||||
NewPower int64
|
||||
}
|
||||
|
||||
const txType = "testTx"
|
||||
|
||||
func (tx testTx) Type() string { return txType }
|
||||
func (tx testTx) Get(key interface{}) (value interface{}) { return nil }
|
||||
func (tx testTx) GetSignBytes() []byte { return nil }
|
||||
func (tx testTx) ValidateBasic() error { return nil }
|
||||
func (tx testTx) GetSigners() []crypto.Address { return nil }
|
||||
func (tx testTx) GetFeePayer() crypto.Address { return nil }
|
||||
func (tx testTx) GetSignatures() []sdk.StdSignature { return nil }
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
store, storeKeys := newCommitMultiStore()
|
||||
|
||||
// Create app.
|
||||
app := NewBaseApp(t.Name(), store)
|
||||
app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, error) {
|
||||
var ttx testTx
|
||||
fromJSON(txBytes, &ttx)
|
||||
return ttx, nil
|
||||
})
|
||||
|
||||
app.SetDefaultAnteHandler(func(ctx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { return })
|
||||
app.Router().AddRoute(txType, func(ctx sdk.Context, tx sdk.Tx) sdk.Result {
|
||||
// TODO
|
||||
return sdk.Result{}
|
||||
})
|
||||
|
||||
// Load latest state, which should be empty.
|
||||
err := app.LoadLatestVersion(storeKeys["main"])
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, app.LastBlockHeight(), int64(0))
|
||||
|
||||
// Create the validators
|
||||
var numVals = 3
|
||||
var valSet = make([]abci.Validator, numVals)
|
||||
for i := 0; i < numVals; i++ {
|
||||
valSet[i] = makeVal(secret(i))
|
||||
}
|
||||
|
||||
// Initialize the chain
|
||||
app.InitChain(abci.RequestInitChain{
|
||||
Validators: valSet,
|
||||
})
|
||||
|
||||
// Simulate the start of a block.
|
||||
app.BeginBlock(abci.RequestBeginBlock{})
|
||||
|
||||
// Add 1 to each validator's voting power.
|
||||
for i, val := range valSet {
|
||||
tx := testTx{
|
||||
Addr: makePubKey(secret(i)).Address(),
|
||||
NewPower: val.Power + 1,
|
||||
}
|
||||
txBytes := toJSON(tx)
|
||||
res := app.DeliverTx(txBytes)
|
||||
assert.True(t, res.IsOK(), "%#v", res)
|
||||
}
|
||||
|
||||
// Simulate the end of a block.
|
||||
// Get the summary of validator updates.
|
||||
res := app.EndBlock(abci.RequestEndBlock{})
|
||||
valUpdates := res.ValidatorUpdates
|
||||
|
||||
// Assert that validator updates are correct.
|
||||
for _, val := range valSet {
|
||||
// Sanity
|
||||
assert.NotEqual(t, len(val.PubKey), 0)
|
||||
|
||||
// Find matching update and splice it out.
|
||||
for j := 0; j < len(valUpdates); {
|
||||
valUpdate := valUpdates[j]
|
||||
|
||||
// Matched.
|
||||
if bytes.Equal(valUpdate.PubKey, val.PubKey) {
|
||||
assert.Equal(t, valUpdate.Power, val.Power+1)
|
||||
if j < len(valUpdates)-1 {
|
||||
// Splice it out.
|
||||
valUpdates = append(valUpdates[:j], valUpdates[j+1:]...)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Not matched.
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
assert.Equal(t, len(valUpdates), 0, "Some validator updates were unexpected")
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
func randPower() int64 {
|
||||
return cmn.RandInt64()
|
||||
}
|
||||
|
||||
func makeVal(secret string) abci.Validator {
|
||||
return abci.Validator{
|
||||
PubKey: makePubKey(secret).Bytes(),
|
||||
Power: randPower(),
|
||||
}
|
||||
}
|
||||
|
||||
func makePubKey(secret string) crypto.PubKey {
|
||||
return makePrivKey(secret).PubKey()
|
||||
}
|
||||
|
||||
func makePrivKey(secret string) crypto.PrivKey {
|
||||
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
|
||||
return privKey
|
||||
}
|
||||
|
||||
func secret(index int) string {
|
||||
return fmt.Sprintf("secret%d", index)
|
||||
}
|
||||
|
||||
func copyVal(val abci.Validator) abci.Validator {
|
||||
// val2 := *val
|
||||
// return &val2
|
||||
return val
|
||||
}
|
||||
|
||||
func toJSON(o interface{}) []byte {
|
||||
bz, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// fmt.Println(">> toJSON:", string(bz))
|
||||
return bz
|
||||
}
|
||||
|
||||
func fromJSON(bz []byte, ptr interface{}) {
|
||||
// fmt.Println(">> fromJSON:", string(bz))
|
||||
err := json.Unmarshal(bz, ptr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a sample CommitMultiStore
|
||||
func newCommitMultiStore() (sdk.CommitMultiStore, map[string]sdk.SubstoreKey) {
|
||||
dbMain := dbm.NewMemDB()
|
||||
dbXtra := dbm.NewMemDB()
|
||||
keyMain := sdk.NewKVStoreKey("main")
|
||||
keyXtra := sdk.NewKVStoreKey("xtra")
|
||||
ms := store.NewCommitMultiStore(dbMain) // Also store rootMultiStore metadata here (it shouldn't clash)
|
||||
ms.SetSubstoreLoader(keyMain, store.NewIAVLStoreLoader(dbMain, 0, 0))
|
||||
ms.SetSubstoreLoader(keyXtra, store.NewIAVLStoreLoader(dbXtra, 0, 0))
|
||||
return ms, map[string]sdk.SubstoreKey{
|
||||
"main": keyMain,
|
||||
"xtra": keyXtra,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
Package baseapp contains data structures that provide basic data storage
|
||||
functionality and act as a bridge between the ABCI interface and the SDK
|
||||
abstractions.
|
||||
|
||||
BaseApp has no state except the CommitMultiStore you provide upon init.
|
||||
|
||||
See examples/basecoin/app/* for usage.
|
||||
*/
|
||||
package baseapp
|
||||
@@ -0,0 +1,54 @@
|
||||
package baseapp
|
||||
|
||||
/*
|
||||
XXX Make this work with MultiStore.
|
||||
XXX It will require some interfaces updates in store/types.go.
|
||||
|
||||
if len(reqQuery.Data) == 0 {
|
||||
resQuery.Log = "Query cannot be zero length"
|
||||
resQuery.Code = abci.CodeType_EncodingError
|
||||
return
|
||||
}
|
||||
|
||||
// set the query response height to current
|
||||
tree := app.state.Committed()
|
||||
|
||||
height := reqQuery.Height
|
||||
if height == 0 {
|
||||
// TODO: once the rpc actually passes in non-zero
|
||||
// heights we can use to query right after a tx
|
||||
// we must retrun most recent, even if apphash
|
||||
// is not yet in the blockchain
|
||||
|
||||
withProof := app.CommittedHeight() - 1
|
||||
if tree.Tree.VersionExists(withProof) {
|
||||
height = withProof
|
||||
} else {
|
||||
height = app.CommittedHeight()
|
||||
}
|
||||
}
|
||||
resQuery.Height = height
|
||||
|
||||
switch reqQuery.Path {
|
||||
case "/store", "/key": // Get by key
|
||||
key := reqQuery.Data // Data holds the key bytes
|
||||
resQuery.Key = key
|
||||
if reqQuery.Prove {
|
||||
value, proof, err := tree.GetVersionedWithProof(key, height)
|
||||
if err != nil {
|
||||
resQuery.Log = err.Error()
|
||||
break
|
||||
}
|
||||
resQuery.Value = value
|
||||
resQuery.Proof = proof.Bytes()
|
||||
} else {
|
||||
value := tree.Get(key)
|
||||
resQuery.Value = value
|
||||
}
|
||||
|
||||
default:
|
||||
resQuery.Code = abci.CodeType_UnknownRequest
|
||||
resQuery.Log = cmn.Fmt("Unexpected Query path: %v", reqQuery.Path)
|
||||
}
|
||||
return
|
||||
*/
|
||||
@@ -0,0 +1,46 @@
|
||||
package baseapp
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
AddRoute(r string, h sdk.Handler)
|
||||
Route(path string) (h sdk.Handler)
|
||||
}
|
||||
|
||||
type route struct {
|
||||
r string
|
||||
h sdk.Handler
|
||||
}
|
||||
|
||||
type router struct {
|
||||
routes []route
|
||||
}
|
||||
|
||||
func NewRouter() *router {
|
||||
return &router{
|
||||
routes: make([]route, 0),
|
||||
}
|
||||
}
|
||||
|
||||
var isAlpha = regexp.MustCompile(`^[a-zA-Z]+$`).MatchString
|
||||
|
||||
func (rtr *router) AddRoute(r string, h sdk.Handler) {
|
||||
if !isAlpha(r) {
|
||||
panic("route expressions can only contain alphanumeric characters")
|
||||
}
|
||||
rtr.routes = append(rtr.routes, route{r, h})
|
||||
}
|
||||
|
||||
// TODO handle expressive matches.
|
||||
func (rtr *router) Route(path string) (h sdk.Handler) {
|
||||
for _, route := range rtr.routes {
|
||||
if route.r == path {
|
||||
return route.h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"chain_id": "foo_bar_chain",
|
||||
"app_options": {
|
||||
"accounts": [{
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "6880db93598e283a67c4d88fc67a8858aa2de70f713fe94a5109e29c137100c2"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "blank",
|
||||
"amount": 12345
|
||||
},
|
||||
{
|
||||
"denom": "ETH",
|
||||
"amount": 654321
|
||||
}
|
||||
]
|
||||
}],
|
||||
"plugin_options": ["plugin1/key1", "value1", "plugin1/key2", "value2"]
|
||||
}
|
||||
}
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"chain_id": "addr_accounts_chain",
|
||||
"app_options": {
|
||||
"accounts": [{
|
||||
"name": "alice",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "one",
|
||||
"amount": 111
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"name": "bob",
|
||||
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
|
||||
"coins": [
|
||||
{
|
||||
"denom": "two",
|
||||
"amount": 222
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"name": "sam",
|
||||
"pub_key": {
|
||||
"type": "secp256k1",
|
||||
"data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "four",
|
||||
"amount": 444
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"chain_id": "addr_accounts_chain",
|
||||
"app_options": {
|
||||
"accounts": [{
|
||||
"name": "alice",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "one",
|
||||
"amount": 111
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"name": "bob",
|
||||
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
|
||||
"coins": [
|
||||
{
|
||||
"denom": "two",
|
||||
"amount": 222
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"name": "carl",
|
||||
"address": "1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9",
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "177C0AC45E86257F0708DC085D592AB22AAEECD1D26381B757F7C96135921858"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "three",
|
||||
"amount": 333
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"name": "sam",
|
||||
"pub_key": {
|
||||
"type": "secp256k1",
|
||||
"data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E"
|
||||
},
|
||||
"coins": [
|
||||
{
|
||||
"denom": "four",
|
||||
"amount": 444
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"chain_id": "foo_bar_chain"
|
||||
}
|
||||
Reference in New Issue
Block a user