Merge pull request #266 from cosmos/feature/iavl-app

Refactor out BaseApp, StoreApp (per spec #1 and #2)
This commit is contained in:
Ethan Frey 2017-10-25 19:13:42 +02:00 committed by GitHub
commit de87acef0d
24 changed files with 850 additions and 634 deletions

View File

@ -1,240 +0,0 @@
package app
import (
"bytes"
"fmt"
"strings"
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/errors"
"github.com/cosmos/cosmos-sdk/stack"
sm "github.com/cosmos/cosmos-sdk/state"
"github.com/cosmos/cosmos-sdk/version"
)
//nolint
const (
ModuleNameBase = "base"
ChainKey = "chain_id"
)
// Basecoin - The ABCI application
type Basecoin struct {
info *sm.ChainState
state *Store
handler sdk.Handler
tick Ticker
pending []*abci.Validator
height uint64
logger log.Logger
}
// Ticker - tick function
type Ticker func(sm.SimpleDB) ([]*abci.Validator, error)
var _ abci.Application = &Basecoin{}
// NewBasecoin - create a new instance of the basecoin application
func NewBasecoin(handler sdk.Handler, store *Store, logger log.Logger) *Basecoin {
return &Basecoin{
handler: handler,
info: sm.NewChainState(),
state: store,
logger: logger,
}
}
// NewBasecoinTick - create a new instance of the basecoin application with tick functionality
func NewBasecoinTick(handler sdk.Handler, store *Store, logger log.Logger, tick Ticker) *Basecoin {
return &Basecoin{
handler: handler,
info: sm.NewChainState(),
state: store,
logger: logger,
tick: tick,
}
}
// GetChainID returns the currently stored chain
func (app *Basecoin) GetChainID() string {
return app.info.GetChainID(app.state.Committed())
}
// GetState is back... please kill me
func (app *Basecoin) GetState() sm.SimpleDB {
return app.state.Append()
}
// Info - ABCI
func (app *Basecoin) Info(req abci.RequestInfo) abci.ResponseInfo {
resp := app.state.Info()
app.logger.Debug("Info",
"height", resp.LastBlockHeight,
"hash", fmt.Sprintf("%X", resp.LastBlockAppHash))
app.height = resp.LastBlockHeight
return abci.ResponseInfo{
Data: fmt.Sprintf("Basecoin v%v", version.Version),
LastBlockHeight: resp.LastBlockHeight,
LastBlockAppHash: resp.LastBlockAppHash,
}
}
// InitState - used to setup state (was SetOption)
// to be used by InitChain later
func (app *Basecoin) InitState(key string, value string) string {
module, key := splitKey(key)
state := app.state.Append()
if module == ModuleNameBase {
if key == ChainKey {
app.info.SetChainID(state, value)
return "Success"
}
return fmt.Sprintf("Error: unknown base option: %s", key)
}
log, err := app.handler.InitState(app.logger, state, module, key, value)
if err == nil {
return log
}
return "Error: " + err.Error()
}
// SetOption - ABCI
func (app *Basecoin) SetOption(key string, value string) string {
return "Not Implemented"
}
// DeliverTx - ABCI
func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
tx, err := sdk.LoadTx(txBytes)
if err != nil {
return errors.Result(err)
}
ctx := stack.NewContext(
app.GetChainID(),
app.height,
app.logger.With("call", "delivertx"),
)
res, err := app.handler.DeliverTx(ctx, app.state.Append(), tx)
if err != nil {
return errors.Result(err)
}
app.addValChange(res.Diff)
return sdk.ToABCI(res)
}
// CheckTx - ABCI
func (app *Basecoin) CheckTx(txBytes []byte) abci.Result {
tx, err := sdk.LoadTx(txBytes)
if err != nil {
return errors.Result(err)
}
ctx := stack.NewContext(
app.GetChainID(),
app.height,
app.logger.With("call", "checktx"),
)
res, err := app.handler.CheckTx(ctx, app.state.Check(), tx)
if err != nil {
return errors.Result(err)
}
return sdk.ToABCI(res)
}
// Query - ABCI
func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
if len(reqQuery.Data) == 0 {
resQuery.Log = "Query cannot be zero length"
resQuery.Code = abci.CodeType_EncodingError
return
}
return app.state.Query(reqQuery)
}
// Commit - ABCI
func (app *Basecoin) Commit() (res abci.Result) {
// Commit state
res = app.state.Commit()
if res.IsErr() {
cmn.PanicSanity("Error getting hash: " + res.Error())
}
return res
}
// InitChain - ABCI
func (app *Basecoin) InitChain(req abci.RequestInitChain) {
// for _, plugin := range app.plugins.GetList() {
// plugin.InitChain(app.state, validators)
// }
}
// BeginBlock - ABCI
func (app *Basecoin) BeginBlock(req abci.RequestBeginBlock) {
app.height++
// for _, plugin := range app.plugins.GetList() {
// plugin.BeginBlock(app.state, hash, header)
// }
if app.tick != nil {
diff, err := app.tick(app.state.Append())
if err != nil {
panic(err)
}
app.addValChange(diff)
}
}
// EndBlock - ABCI
// Returns a list of all validator changes made in this block
func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) {
// TODO: cleanup in case a validator exists multiple times in the list
res.Diffs = app.pending
app.pending = nil
return
}
func (app *Basecoin) addValChange(diffs []*abci.Validator) {
for _, d := range diffs {
idx := pubKeyIndex(d, app.pending)
if idx >= 0 {
app.pending[idx] = d
} else {
app.pending = append(app.pending, d)
}
}
}
// 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
}
//TODO move split key to tmlibs?
// Splits the string at the first '/'.
// if there are none, assign default module ("base").
func splitKey(key string) (string, string) {
if strings.Contains(key, "/") {
keyParts := strings.SplitN(key, "/", 2)
return keyParts[0], keyParts[1]
}
return ModuleNameBase, key
}

View File

@ -55,7 +55,7 @@ func DefaultHandler(feeDenom string) sdk.Handler {
type appTest struct {
t *testing.T
chainID string
app *Basecoin
app *BaseApp
acctIn *coin.AccountWithKey
acctOut *coin.AccountWithKey
}
@ -100,8 +100,8 @@ func (at *appTest) feeTx(coins coin.Coins, toll coin.Coin, sequence uint32) sdk.
// set the account on the app through InitState
func (at *appTest) initAccount(acct *coin.AccountWithKey) {
res := at.app.InitState("coin/account", acct.MakeOption())
require.EqualValues(at.t, res, "Success")
err := at.app.InitState("coin", "account", acct.MakeOption())
require.Nil(at.t, err, "%+v", err)
}
// reset the in and out accs to be one account each with 7mycoin
@ -112,17 +112,13 @@ func (at *appTest) reset() {
// Note: switch logger if you want to get more info
logger := log.TestingLogger()
// logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout))
store, err := NewStore("", 0, logger.With("module", "store"))
store, err := NewStoreApp("app-test", "", 0, logger)
require.Nil(at.t, err, "%+v", err)
at.app = NewBaseApp(store, DefaultHandler("mycoin"), nil)
at.app = NewBasecoin(
DefaultHandler("mycoin"),
store,
logger.With("module", "app"),
)
res := at.app.InitState("base/chain_id", at.chainID)
require.EqualValues(at.t, res, "Success")
err = at.app.InitState("base", "chain_id", at.chainID)
require.Nil(at.t, err, "%+v", err)
at.initAccount(at.acctIn)
at.initAccount(at.acctOut)
@ -146,9 +142,9 @@ func getAddr(addr []byte, state state.SimpleDB) (coin.Coins, error) {
func (at *appTest) exec(t *testing.T, tx sdk.Tx, checkTx bool) (res abci.Result, diffIn, diffOut coin.Coins) {
require := require.New(t)
initBalIn, err := getBalance(at.acctIn.Actor(), at.app.GetState())
initBalIn, err := getBalance(at.acctIn.Actor(), at.app.Append())
require.Nil(err, "%+v", err)
initBalOut, err := getBalance(at.acctOut.Actor(), at.app.GetState())
initBalOut, err := getBalance(at.acctOut.Actor(), at.app.Append())
require.Nil(err, "%+v", err)
txBytes := wire.BinaryBytes(tx)
@ -158,9 +154,9 @@ func (at *appTest) exec(t *testing.T, tx sdk.Tx, checkTx bool) (res abci.Result,
res = at.app.DeliverTx(txBytes)
}
endBalIn, err := getBalance(at.acctIn.Actor(), at.app.GetState())
endBalIn, err := getBalance(at.acctIn.Actor(), at.app.Append())
require.Nil(err, "%+v", err)
endBalOut, err := getBalance(at.acctOut.Actor(), at.app.GetState())
endBalOut, err := getBalance(at.acctOut.Actor(), at.app.Append())
require.Nil(err, "%+v", err)
return res, endBalIn.Minus(initBalIn), endBalOut.Minus(initBalOut)
}
@ -172,29 +168,24 @@ func TestInitState(t *testing.T) {
require := require.New(t)
logger := log.TestingLogger()
store, err := NewStore("", 0, logger.With("module", "store"))
store, err := NewStoreApp("app-test", "", 0, logger)
require.Nil(err, "%+v", err)
app := NewBasecoin(
DefaultHandler("atom"),
store,
logger.With("module", "app"),
)
app := NewBaseApp(store, DefaultHandler("atom"), nil)
//testing ChainID
chainID := "testChain"
res := app.InitState("base/chain_id", chainID)
err = app.InitState("base", "chain_id", chainID)
require.Nil(err, "%+v", err)
assert.EqualValues(app.GetChainID(), chainID)
assert.EqualValues(res, "Success")
// make a nice account...
bal := coin.Coins{{"atom", 77}, {"eth", 12}}
acct := coin.NewAccountWithKey(bal)
res = app.InitState("coin/account", acct.MakeOption())
require.EqualValues(res, "Success")
err = app.InitState("coin", "account", acct.MakeOption())
require.Nil(err, "%+v", err)
// make sure it is set correctly, with some balance
coins, err := getBalance(acct.Actor(), app.GetState())
coins, err := getBalance(acct.Actor(), app.Append())
require.Nil(err)
assert.Equal(bal, coins)
@ -218,23 +209,22 @@ func TestInitState(t *testing.T) {
}
]
}`
res = app.InitState("coin/account", unsortAcc)
require.EqualValues(res, "Success")
err = app.InitState("coin", "account", unsortAcc)
require.Nil(err, "%+v", err)
coins, err = getAddr(unsortAddr, app.GetState())
coins, err = getAddr(unsortAddr, app.Append())
require.Nil(err)
assert.True(coins.IsValid())
assert.Equal(unsortCoins, coins)
res = app.InitState("base/dslfkgjdas", "")
assert.NotEqual(res, "Success")
err = app.InitState("base", "dslfkgjdas", "")
require.NotNil(err)
res = app.InitState("dslfkgjdas", "")
assert.NotEqual(res, "Success")
res = app.InitState("dslfkgjdas/szfdjzs", "")
assert.NotEqual(res, "Success")
err = app.InitState("", "dslfkgjdas", "")
require.NotNil(err)
err = app.InitState("dslfkgjdas", "szfdjzs", "")
require.NotNil(err)
}
// Test CheckTx and DeliverTx with insufficient and sufficient balance
@ -301,19 +291,3 @@ func TestQuery(t *testing.T) {
})
assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit")
}
func TestSplitKey(t *testing.T) {
assert := assert.New(t)
prefix, suffix := splitKey("foo/bar")
assert.EqualValues("foo", prefix)
assert.EqualValues("bar", suffix)
prefix, suffix = splitKey("foobar")
assert.EqualValues("base", prefix)
assert.EqualValues("foobar", suffix)
prefix, suffix = splitKey("some/complex/issue")
assert.EqualValues("some", prefix)
assert.EqualValues("complex/issue", suffix)
}

115
app/base.go Normal file
View File

@ -0,0 +1,115 @@
package app
import (
"fmt"
abci "github.com/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/errors"
"github.com/cosmos/cosmos-sdk/stack"
)
// BaseApp - The ABCI application
type BaseApp struct {
*StoreApp
handler sdk.Handler
clock sdk.Ticker
}
var _ abci.Application = &BaseApp{}
// NewBaseApp extends a StoreApp with a handler and a ticker,
// which it binds to the proper abci calls
func NewBaseApp(store *StoreApp, handler sdk.Handler, clock sdk.Ticker) *BaseApp {
return &BaseApp{
StoreApp: store,
handler: handler,
clock: clock,
}
}
// DeliverTx - ABCI - dispatches to the handler
func (app *BaseApp) DeliverTx(txBytes []byte) abci.Result {
tx, err := sdk.LoadTx(txBytes)
if err != nil {
return errors.Result(err)
}
ctx := stack.NewContext(
app.GetChainID(),
app.WorkingHeight(),
app.Logger().With("call", "delivertx"),
)
res, err := app.handler.DeliverTx(ctx, app.Append(), tx)
if err != nil {
return errors.Result(err)
}
app.AddValChange(res.Diff)
return sdk.ToABCI(res)
}
// CheckTx - ABCI - dispatches to the handler
func (app *BaseApp) CheckTx(txBytes []byte) abci.Result {
tx, err := sdk.LoadTx(txBytes)
if err != nil {
return errors.Result(err)
}
ctx := stack.NewContext(
app.GetChainID(),
app.WorkingHeight(),
app.Logger().With("call", "checktx"),
)
res, err := app.handler.CheckTx(ctx, app.Check(), tx)
if err != nil {
return errors.Result(err)
}
return sdk.ToABCI(res)
}
// BeginBlock - ABCI - triggers Tick actions
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) {
// execute tick if present
if app.clock != nil {
ctx := stack.NewContext(
app.GetChainID(),
app.WorkingHeight(),
app.Logger().With("call", "tick"),
)
diff, err := app.clock.Tick(ctx, app.Append())
if err != nil {
panic(err)
}
app.AddValChange(diff)
}
}
// InitState - used to setup state (was SetOption)
// to be used by InitChain later
//
// TODO: rethink this a bit more....
func (app *BaseApp) InitState(module, key, value string) error {
state := app.Append()
logger := app.Logger().With("module", module, "key", key)
if module == sdk.ModuleNameBase {
if key == sdk.ChainKey {
app.info.SetChainID(state, value)
return nil
}
logger.Error("Invalid genesis option")
return fmt.Errorf("Unknown base option: %s", key)
}
log, err := app.handler.InitState(logger, state, module, key, value)
if err != nil {
logger.Error("Invalid genesis option", "err", err)
} else {
logger.Info(log)
}
return err
}

19
app/doc.go Normal file
View File

@ -0,0 +1,19 @@
/*
Package app contains data structures that provide basic
data storage functionality and act as a bridge between the abci
interface and the internal sdk representations.
StoreApp handles creating a datastore or loading an existing one
from disk, provides helpers to use in the transaction workflow
(check/deliver/commit), and provides bindings to the ABCI interface
for functionality such as handshaking with tendermint on restart,
querying the data store, and handling begin/end block and commit messages.
It does not handle CheckTx or DeliverTx, or have any logic for modifying
the state, and is quite generic if you don't wish to use the standard Handlers.
BaseApp embeds StoreApp and extends it for the standard sdk usecase, where
we dispatch all CheckTx/DeliverTx messages to a handler (which may contain
decorators and a router to multiple modules), and supports a Ticker which
is called every BeginBlock.
*/
package app

View File

@ -1,99 +0,0 @@
package app
import (
"encoding/json"
"github.com/pkg/errors"
cmn "github.com/tendermint/tmlibs/common"
)
// LoadGenesis - Load the genesis file into memory
func (app *Basecoin) LoadGenesis(path string) error {
genDoc, err := loadGenesis(path)
if err != nil {
return err
}
// set chain_id
app.InitState("base/chain_id", genDoc.ChainID)
// set accounts
for _, acct := range genDoc.AppOptions.Accounts {
_ = app.InitState("coin/account", string(acct))
}
// set plugin options
for _, kv := range genDoc.AppOptions.pluginOptions {
_ = app.InitState(kv.Key, kv.Value)
}
return nil
}
type keyValue struct {
Key string `json:"key"`
Value string `json:"value"`
}
// FullGenesisDoc - includes tendermint (in the json, we ignore here)
type FullGenesisDoc struct {
ChainID string `json:"chain_id"`
AppOptions *GenesisDoc `json:"app_options"`
}
// GenesisDoc - All genesis values
type GenesisDoc struct {
Accounts []json.RawMessage `json:"accounts"`
PluginOptions []json.RawMessage `json:"plugin_options"`
pluginOptions []keyValue // unmarshaled rawmessages
}
func loadGenesis(filePath string) (*FullGenesisDoc, error) {
bytes, err := cmn.ReadFile(filePath)
if err != nil {
return nil, errors.Wrap(err, "loading genesis file")
}
// the basecoin genesis go-wire/data :)
genDoc := new(FullGenesisDoc)
err = json.Unmarshal(bytes, genDoc)
if err != nil {
return nil, errors.Wrap(err, "unmarshaling genesis file")
}
if genDoc.AppOptions == nil {
genDoc.AppOptions = new(GenesisDoc)
}
pluginOpts, err := parseGenesisList(genDoc.AppOptions.PluginOptions)
if err != nil {
return nil, err
}
genDoc.AppOptions.pluginOptions = pluginOpts
return genDoc, nil
}
func parseGenesisList(kvzIn []json.RawMessage) (kvz []keyValue, err error) {
if len(kvzIn)%2 != 0 {
return nil, errors.New("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]")
}
for i := 0; i < len(kvzIn); i += 2 {
kv := keyValue{}
rawK := []byte(kvzIn[i])
err := json.Unmarshal(rawK, &(kv.Key))
if err != nil {
return nil, errors.Errorf("Non-string key: %s", string(rawK))
}
// convert value to string if possible (otherwise raw json)
rawV := kvzIn[i+1]
err = json.Unmarshal(rawV, &(kv.Value))
if err != nil {
kv.Value = string(rawV)
}
kvz = append(kvz, kv)
}
return kvz, nil
}

View File

@ -2,59 +2,44 @@ package app
import (
"encoding/hex"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/genesis"
"github.com/cosmos/cosmos-sdk/modules/coin"
)
const genesisFilepath = "./testdata/genesis.json"
const genesisAcctFilepath = "./testdata/genesis2.json"
// 2b is just like 2, but add carl who has inconsistent
// pubkey and address
const genesisBadAcctFilepath = "./testdata/genesis2b.json"
func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) {
logger := log.TestingLogger()
store, err := NewStore("", 0, logger)
store, err := MockStoreApp("genesis", logger)
require.Nil(t, err, "%+v", err)
app := NewBasecoin(DefaultHandler("mycoin"), store, logger)
err = app.LoadGenesis("./testdata/genesis3.json")
app := NewBaseApp(store, DefaultHandler("mycoin"), nil)
err = genesis.Load(app, "./testdata/genesis3.json")
require.Nil(t, err, "%+v", err)
}
func TestLoadGenesis(t *testing.T) {
assert, require := assert.New(t), require.New(t)
func TestLoadGenesisFailsWithUnknownOptions(t *testing.T) {
require := require.New(t)
logger := log.TestingLogger()
store, err := NewStore("", 0, logger)
store, err := MockStoreApp("genesis", logger)
require.Nil(err, "%+v", err)
app := NewBasecoin(DefaultHandler("mycoin"), store, logger)
err = app.LoadGenesis(genesisFilepath)
require.Nil(err, "%+v", err)
// check the chain id
assert.Equal("foo_bar_chain", app.GetChainID())
// and check the account info - previously calculated values
addr, _ := hex.DecodeString("eb98e0688217cfdeb70eddf4b33cdcc37fc53197")
coins, err := getAddr(addr, app.GetState())
require.Nil(err)
assert.True(coins.IsPositive())
// make sure balance is proper
assert.Equal(2, len(coins))
assert.True(coins.IsValid())
// note, that we now sort them to be valid
assert.EqualValues(654321, coins[0].Amount)
assert.EqualValues("ETH", coins[0].Denom)
assert.EqualValues(12345, coins[1].Amount)
assert.EqualValues("blank", coins[1].Denom)
app := NewBaseApp(store, DefaultHandler("mycoin"), nil)
err = genesis.Load(app, genesisFilepath)
require.NotNil(err, "%+v", err)
}
// Fix for issue #89, change the parse format for accounts in genesis.json
@ -62,11 +47,11 @@ func TestLoadGenesisAccountAddress(t *testing.T) {
assert, require := assert.New(t), require.New(t)
logger := log.TestingLogger()
store, err := NewStore("", 0, logger)
store, err := MockStoreApp("genesis", logger)
require.Nil(err, "%+v", err)
app := NewBaseApp(store, DefaultHandler("mycoin"), nil)
app := NewBasecoin(DefaultHandler("mycoin"), store, logger)
err = app.LoadGenesis(genesisAcctFilepath)
err = genesis.Load(app, genesisAcctFilepath)
require.Nil(err, "%+v", err)
// check the chain id
@ -83,9 +68,6 @@ func TestLoadGenesisAccountAddress(t *testing.T) {
{"62035D628DE7543332544AA60D90D3693B6AD51B", true, true, coin.Coins{{"one", 111}}},
// this comes from an address, should be stored proper (bob)
{"C471FB670E44D219EE6DF2FC284BE38793ACBCE1", true, false, coin.Coins{{"two", 222}}},
// this one had a mismatched address and pubkey, should not store under either (carl)
{"1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is given addr
{"700BEC5ED18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is addr of the given pubkey
// this comes from a secp256k1 public key, should be stored proper (sam)
{"979F080B1DD046C452C2A8A250D18646C6B669D4", true, true, coin.Coins{{"four", 444}}},
}
@ -93,7 +75,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) {
for i, tc := range cases {
addr, err := hex.DecodeString(tc.addr)
require.Nil(err, tc.addr)
coins, err := getAddr(addr, app.GetState())
coins, err := getAddr(addr, app.Append())
require.Nil(err, "%+v", err)
if !tc.exists {
assert.True(coins.IsZero(), "%d", i)
@ -105,23 +87,15 @@ func TestLoadGenesisAccountAddress(t *testing.T) {
}
}
func TestParseGenesisList(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// When you define an account in genesis with address
// and pubkey that don't match
func TestLoadGenesisAccountInconsistentAddress(t *testing.T) {
require := require.New(t)
bytes, err := cmn.ReadFile(genesisFilepath)
require.Nil(err, "loading genesis file %+v", err)
// the basecoin genesis go-wire/data :)
genDoc := new(FullGenesisDoc)
err = json.Unmarshal(bytes, genDoc)
require.Nil(err, "unmarshaling genesis file %+v", err)
pluginOpts, err := parseGenesisList(genDoc.AppOptions.PluginOptions)
logger := log.TestingLogger()
store, err := MockStoreApp("genesis", logger)
require.Nil(err, "%+v", err)
genDoc.AppOptions.pluginOptions = pluginOpts
assert.Equal(genDoc.AppOptions.pluginOptions[0].Key, "plugin1/key1")
assert.Equal(genDoc.AppOptions.pluginOptions[1].Key, "plugin1/key2")
assert.Equal(genDoc.AppOptions.pluginOptions[0].Value, "value1")
assert.Equal(genDoc.AppOptions.pluginOptions[1].Value, "value2")
app := NewBaseApp(store, DefaultHandler("mycoin"), nil)
err = genesis.Load(app, genesisBadAcctFilepath)
require.NotNil(err)
}

View File

@ -1,132 +1,140 @@
package app
import (
"bytes"
"fmt"
"path"
"path/filepath"
"strings"
"github.com/pkg/errors"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/iavl"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/state"
"github.com/cosmos/cosmos-sdk/errors"
sm "github.com/cosmos/cosmos-sdk/state"
)
// Store contains the merkle tree, and all info to handle abci requests
type Store struct {
state.State
// StoreApp contains a data store and all info needed
// to perform queries and handshakes.
//
// It should be embeded in another struct for CheckTx,
// DeliverTx and initializing state from the genesis.
type StoreApp struct {
// Name is what is returned from info
Name string
// this is the database state
info *sm.ChainState
state *sm.State
// cached validator changes from DeliverTx
pending []*abci.Validator
// height is last committed block, DeliverTx is the next one
height uint64
logger log.Logger
}
// MockStore returns an in-memory store only intended for testing
func MockStore() *Store {
res, err := NewStore("", 0, log.NewNopLogger())
// NewStoreApp creates a data store to handle queries
func NewStoreApp(appName, dbName string, cacheSize int, logger log.Logger) (*StoreApp, error) {
state, err := loadState(dbName, cacheSize)
if err != nil {
// should never happen, abort test if it does
panic(err)
return nil, err
}
return res
app := &StoreApp{
Name: appName,
state: state,
height: state.LatestHeight(),
info: sm.NewChainState(),
logger: logger.With("module", "app"),
}
return app, nil
}
// NewStore initializes an in-memory iavl.VersionedTree, or attempts to load a
// persistant tree from disk
func NewStore(dbName string, cacheSize int, logger log.Logger) (*Store, error) {
// memory backed case, just for testing
if dbName == "" {
tree := iavl.NewVersionedTree(
0,
dbm.NewMemDB(),
)
store := &Store{
State: state.NewState(tree),
logger: logger,
}
return store, nil
}
// MockStoreApp returns a Store app with no persistence
func MockStoreApp(appName string, logger log.Logger) (*StoreApp, error) {
return NewStoreApp(appName, "", 0, logger)
}
// Expand the path fully
dbPath, err := filepath.Abs(dbName)
if err != nil {
return nil, errors.Wrap(err, "Invalid Database Name")
}
// GetChainID returns the currently stored chain
func (app *StoreApp) GetChainID() string {
return app.info.GetChainID(app.state.Committed())
}
// Some external calls accidently add a ".db", which is now removed
dbPath = strings.TrimSuffix(dbPath, path.Ext(dbPath))
// Split the database name into it's components (dir, name)
dir := path.Dir(dbPath)
name := path.Base(dbPath)
// Make sure the path exists
empty, _ := cmn.IsDirEmpty(dbPath + ".db")
// Open database called "dir/name.db", if it doesn't exist it will be created
db := dbm.NewDB(name, dbm.LevelDBBackendStr, dir)
tree := iavl.NewVersionedTree(cacheSize, db)
if empty {
logger.Info("no existing db, creating new db")
} else {
logger.Info("loading existing db")
if err = tree.Load(); err != nil {
return nil, errors.Wrap(err, "Loading tree")
}
}
res := &Store{
State: state.NewState(tree),
logger: logger,
}
res.height = res.State.LatestHeight()
return res, nil
// Logger returns the application base logger
func (app *StoreApp) Logger() log.Logger {
return app.logger
}
// Hash gets the last hash stored in the database
func (s *Store) Hash() []byte {
return s.State.LatestHash()
func (app *StoreApp) Hash() []byte {
return app.state.LatestHash()
}
// Info implements abci.Application. It returns the height, hash and size (in the data).
// Committed returns the committed state,
// also exposing historical queries
// func (app *StoreApp) Committed() *Bonsai {
// return app.state.committed
// }
// Append returns the working state for DeliverTx
func (app *StoreApp) Append() sm.SimpleDB {
return app.state.Append()
}
// Check returns the working state for CheckTx
func (app *StoreApp) Check() sm.SimpleDB {
return app.state.Check()
}
// CommittedHeight gets the last block height committed
// to the db
func (app *StoreApp) CommittedHeight() uint64 {
return app.height
}
// WorkingHeight gets the current block we are writing
func (app *StoreApp) WorkingHeight() uint64 {
return app.height + 1
}
// Info implements abci.Application. It returns the height and hash,
// as well as the abci name and version.
//
// The height is the block that holds the transactions, not the apphash itself.
func (s *Store) Info() abci.ResponseInfo {
s.logger.Info("Info synced",
"height", s.height,
"hash", fmt.Sprintf("%X", s.Hash()))
func (app *StoreApp) Info(req abci.RequestInfo) abci.ResponseInfo {
hash := app.Hash()
app.logger.Info("Info synced",
"height", app.CommittedHeight(),
"hash", fmt.Sprintf("%X", hash))
return abci.ResponseInfo{
Data: cmn.Fmt("size:%v", s.State.Size()),
LastBlockHeight: s.height,
LastBlockAppHash: s.Hash(),
Data: app.Name,
LastBlockHeight: app.CommittedHeight(),
LastBlockAppHash: hash,
}
}
// Commit implements abci.Application
func (s *Store) Commit() abci.Result {
s.height++
hash, err := s.State.Commit(s.height)
if err != nil {
return abci.NewError(abci.CodeType_InternalError, err.Error())
}
s.logger.Debug("Commit synced",
"height", s.height,
"hash", fmt.Sprintf("%X", hash),
)
if s.State.Size() == 0 {
return abci.NewResultOK(nil, "Empty hash for empty tree")
}
return abci.NewResultOK(hash, "")
// SetOption - ABCI
func (app *StoreApp) SetOption(key string, value string) string {
return "Not Implemented"
}
// Query implements abci.Application
func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
// Query - ABCI
func (app *StoreApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
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 := s.State.Committed()
tree := app.state.Committed()
height := reqQuery.Height
if height == 0 {
@ -135,10 +143,10 @@ func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery)
// we must retrun most recent, even if apphash
// is not yet in the blockchain
// if tree.Tree.VersionExists(s.height - 1) {
// height = s.height - 1
// if tree.Tree.VersionExists(app.height - 1) {
// height = app.height - 1
// } else {
height = s.height
height = app.CommittedHeight()
// }
}
resQuery.Height = height
@ -166,3 +174,98 @@ func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery)
}
return
}
// Commit implements abci.Application
func (app *StoreApp) Commit() (res abci.Result) {
app.height++
hash, err := app.state.Commit(app.height)
if err != nil {
// die if we can't commit, not to recover
panic(err)
}
app.logger.Debug("Commit synced",
"height", app.height,
"hash", fmt.Sprintf("%X", hash),
)
if app.state.Size() == 0 {
return abci.NewResultOK(nil, "Empty hash for empty tree")
}
return abci.NewResultOK(hash, "")
}
// InitChain - ABCI
func (app *StoreApp) InitChain(req abci.RequestInitChain) {}
// BeginBlock - ABCI
func (app *StoreApp) BeginBlock(req abci.RequestBeginBlock) {}
// EndBlock - ABCI
// Returns a list of all validator changes made in this block
func (app *StoreApp) EndBlock(height uint64) (res abci.ResponseEndBlock) {
// TODO: cleanup in case a validator exists multiple times in the list
res.Diffs = app.pending
app.pending = nil
return
}
// AddValChange is meant to be called by apps on DeliverTx
// results, this is added to the cache for the endblock
// changeset
func (app *StoreApp) AddValChange(diffs []*abci.Validator) {
for _, d := range diffs {
idx := pubKeyIndex(d, app.pending)
if idx >= 0 {
app.pending[idx] = d
} else {
app.pending = append(app.pending, d)
}
}
}
// 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
}
func loadState(dbName string, cacheSize int) (*sm.State, error) {
// memory backed case, just for testing
if dbName == "" {
tree := iavl.NewVersionedTree(0, dbm.NewMemDB())
return sm.NewState(tree), nil
}
// Expand the path fully
dbPath, err := filepath.Abs(dbName)
if err != nil {
return nil, errors.ErrInternal("Invalid Database Name")
}
// Some external calls accidently add a ".db", which is now removed
dbPath = strings.TrimSuffix(dbPath, path.Ext(dbPath))
// Split the database name into it's components (dir, name)
dir := path.Dir(dbPath)
name := path.Base(dbPath)
// Make sure the path exists
empty, _ := cmn.IsDirEmpty(dbPath + ".db")
// Open database called "dir/name.db", if it doesn't exist it will be created
db := dbm.NewDB(name, dbm.LevelDBBackendStr, dir)
tree := iavl.NewVersionedTree(cacheSize, db)
if !empty {
if err = tree.Load(); err != nil {
return nil, errors.ErrInternal("Loading tree: " + err.Error())
}
}
return sm.NewState(tree), nil
}

View File

@ -22,19 +22,6 @@
"amount": 222
}
]
}, {
"name": "carl",
"address": "1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9",
"pub_key": {
"type": "ed25519",
"data": "177C0AC45E86257F0708DC085D592AB22AAEECD1D26381B757F7C96135921858"
},
"coins": [
{
"denom": "three",
"amount": 333
}
]
}, {
"name": "sam",
"pub_key": {

52
app/testdata/genesis2b.json vendored Normal file
View File

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

View File

@ -39,9 +39,10 @@ func TestEndBlock(t *testing.T) {
assert, require := assert.New(t), require.New(t)
logger := log.NewNopLogger()
store := MockStore()
handler := base.ValSetHandler{}
app := NewBasecoin(handler, store, logger)
store, err := MockStoreApp("vals", logger)
require.Nil(err, "%+v", err)
app := NewBaseApp(store, handler, nil)
val1 := makeVal()
val2 := makeVal()

View File

@ -10,7 +10,7 @@ import (
"github.com/tendermint/tmlibs/log"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/app"
sdkapp "github.com/cosmos/cosmos-sdk/app"
"github.com/cosmos/cosmos-sdk/modules/auth"
"github.com/cosmos/cosmos-sdk/modules/base"
"github.com/cosmos/cosmos-sdk/modules/coin"
@ -21,7 +21,7 @@ import (
)
type BenchApp struct {
App *app.Basecoin
App *sdkapp.BaseApp
Accounts []*coin.AccountWithKey
ChainID string
}
@ -53,27 +53,20 @@ func NewBenchApp(h sdk.Handler, chainID string, n int,
// logger := log.NewFilter(log.NewTMLogger(os.Stdout), log.AllowError())
// logger = log.NewTracingLogger(logger)
// TODO: disk writing
var store *app.Store
var err error
dbDir, cache := "", 0
if persist {
tmpDir, _ := ioutil.TempDir("", "bc-app-benchmark")
store, err = app.NewStore(tmpDir, 500, logger)
} else {
store, err = app.NewStore("", 0, logger)
dbDir, _ = ioutil.TempDir("", "bc-app-benchmark")
cache = 500
}
store, err := sdkapp.NewStoreApp("bench", dbDir, cache, logger)
if err != nil {
panic(err)
}
app := sdkapp.NewBaseApp(store, h, nil)
app := app.NewBasecoin(
h,
store,
logger.With("module", "app"),
)
res := app.InitState("base/chain_id", chainID)
if res != "Success" {
err = app.InitState("base", "chain_id", chainID)
if err != nil {
panic("cannot set chain")
}
@ -82,8 +75,8 @@ func NewBenchApp(h sdk.Handler, chainID string, n int,
accts := make([]*coin.AccountWithKey, n)
for i := 0; i < n; i++ {
accts[i] = coin.NewAccountWithKey(money)
res := app.InitState("coin/account", accts[i].MakeOption())
if res != "Success" {
err = app.InitState("coin", "account", accts[i].MakeOption())
if err != nil {
panic("can't set account")
}
}

View File

@ -18,7 +18,7 @@ import (
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/app"
sdkapp "github.com/cosmos/cosmos-sdk/app"
"github.com/cosmos/cosmos-sdk/modules/eyes"
)
@ -26,11 +26,12 @@ var node *nm.Node
func TestMain(m *testing.M) {
logger := log.TestingLogger()
store, err := app.NewStore("", 0, logger)
store, err := sdkapp.MockStoreApp("query", logger)
if err != nil {
panic(err)
}
app := app.NewBasecoin(eyes.NewHandler(), store, logger)
app := sdkapp.NewBaseApp(store, eyes.NewHandler(), nil)
node = rpctest.StartTendermint(app)
code := m.Run()

View File

@ -11,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/modules/auth"
"github.com/cosmos/cosmos-sdk/modules/base"
"github.com/cosmos/cosmos-sdk/modules/coin"
"github.com/cosmos/cosmos-sdk/modules/eyes"
"github.com/cosmos/cosmos-sdk/modules/fee"
"github.com/cosmos/cosmos-sdk/modules/ibc"
"github.com/cosmos/cosmos-sdk/modules/nonce"
@ -45,6 +46,8 @@ func BuildApp(feeDenom string) sdk.Handler {
coin.NewHandler(),
stack.WrapHandler(roles.NewHandler()),
stack.WrapHandler(ibc.NewHandler()),
// and just for run, add eyes as well
stack.WrapHandler(eyes.NewHandler()),
)
}

View File

@ -12,7 +12,7 @@ test01initOption() {
GENESIS_FILE=${SERVE_DIR}/genesis.json
HEX="deadbeef1234deadbeef1234deadbeef1234aaaa"
${SERVER_EXE} init ${HEX} --home="$SERVE_DIR" -p=app1/key1/val1 -p='"app2/key2/{""name"": ""joe"", ""age"": ""100""}"' >/dev/null
${SERVER_EXE} init ${HEX} --home="$SERVE_DIR" -p=eyes/key1/val1 -p='"eyes/key2/{""name"": ""joe"", ""age"": ""100""}"' >/dev/null
if ! assertTrue "line=${LINENO}" $?; then return 1; fi
OPTION1KEY=$(cat ${GENESIS_FILE} | jq '.app_options.plugin_options[2]')
@ -21,9 +21,9 @@ test01initOption() {
OPTION2VAL=$(cat ${GENESIS_FILE} | jq '.app_options.plugin_options[5]')
OPTION2VALEXPECTED=$(echo '{"name": "joe", "age": "100"}' | jq '.')
assertEquals "line=${LINENO}" '"app1/key1"' $OPTION1KEY
assertEquals "line=${LINENO}" '"eyes/key1"' $OPTION1KEY
assertEquals "line=${LINENO}" '"val1"' $OPTION1VAL
assertEquals "line=${LINENO}" '"app2/key2"' $OPTION2KEY
assertEquals "line=${LINENO}" '"eyes/key2"' $OPTION2KEY
assertEquals "line=${LINENO}" "$OPTION2VALEXPECTED" "$OPTION2VAL"
}

View File

@ -66,7 +66,7 @@ test01GetInsecure() {
INFO=$(${CLIENT_EXE} rpc info)
assertTrue "line=${LINENO}, get info" "$?"
DATA=$(echo $INFO | jq .response.data)
assertEquals "line=${LINENO}, basecoin info" '"Basecoin v0.7.1"' "$DATA"
assertEquals "line=${LINENO}, basecoin info" '"basecoin v0.7.1"' "$DATA"
}
test02GetSecure() {

View File

@ -27,22 +27,18 @@ func TestCounterPlugin(t *testing.T) {
logger := log.TestingLogger()
// logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout))
store, err := app.NewStore("", 0, logger.With("module", "store"))
require.Nil(err, "%+v", err)
h := NewHandler("gold")
bcApp := app.NewBasecoin(
h,
store,
logger.With("module", "app"),
)
bcApp.InitState("base/chain_id", chainID)
store, err := app.MockStoreApp("counter", logger)
require.Nil(err, "%+v", err)
bcApp := app.NewBaseApp(store, h, nil)
err = bcApp.InitState("base", "chain_id", chainID)
require.Nil(err, "%+v", err)
// Account initialization
bal := coin.Coins{{"", 1000}, {"gold", 1000}}
acct := coin.NewAccountWithKey(bal)
log := bcApp.InitState("coin/account", acct.MakeOption())
require.Equal("Success", log)
err = bcApp.InitState("coin", "account", acct.MakeOption())
require.Nil(err, "%+v", err)
// Deliver a CounterTx
DeliverCounterTx := func(valid bool, counterFee coin.Coins, sequence uint32) abci.Result {

61
genesis/doc.go Normal file
View File

@ -0,0 +1,61 @@
/*
Package genesis provides some utility functions for parsing
a standard genesis file to initialize your abci application.
We wish to support using one genesis file to initialize both
tendermint and the application, so this file format is designed
to be embedable in the tendermint genesis.json file. We reuse
the same chain_id field for tendermint, ignore the other fields,
and add a special app_options field that contains information just
for the abci app (and ignored by tendermint).
The use of this file format for your application is not required by
the sdk and is only used by default in the start command, if you wish
to write your own start command, you can use any other method to
store and parse options for your abci application. The important part is
that the same data is available on every node.
Example file format:
{
"chain_id": "foo_bar_chain",
"app_options": {
"accounts": [{
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
"pub_key": {
"type": "ed25519",
"data": "6880DB93598E283A67C4D88FC67A8858AA2DE70F713FE94A5109E29C137100C2"
},
"coins": [
{
"denom": "ETH",
"amount": 654321
}
]
}],
"plugin_options": [
"plugin1/key1", "value1",
"profile/set", {"name": "john", age: 37}
]
}
}
Note that there are two subfields under app_options. The first one "accounts"
is a special case for the coin module, which is assumed to be used by most
applications. It is simply a list of accounts with an identifier and their
initial balance. The account must be identified by EITHER an address
(20 bytes in hex) or a pubkey (in the go-crypto json format), not both as in
this example. "coins" defines the initial balance of the account.
Configuration options for every other module should be placed under
"plugin_options" as key value pairs (there must be an even number of items).
The first value must be "<module>/<key>" to define the option to be set.
The second value is parsed as raw json and is the value to pass to the
application. This may be a string, an array, a map or any other valid json
structure that the module can parse.
Note that we don't use a map for plugin_options, as we will often wish
to have many values for the same key, to run this setup many times,
just as we support setting many accounts.
*/
package genesis

153
genesis/parse.go Normal file
View File

@ -0,0 +1,153 @@
package genesis
import (
"encoding/json"
"strings"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/pkg/errors"
cmn "github.com/tendermint/tmlibs/common"
)
// Option just holds module/key/value triples from
// parsing the genesis file
type Option struct {
Module string
Key string
Value string
}
// InitStater is anything that can handle app options
// from genesis file. Setting the merkle store, config options,
// or anything else
type InitStater interface {
InitState(module, key, value string) error
}
// Load parses the genesis file and sets the initial
// state based on that
func Load(app InitStater, filePath string) error {
opts, err := GetOptions(filePath)
if err != nil {
return err
}
// execute all the genesis init options
// abort on any error
for _, opt := range opts {
err = app.InitState(opt.Module, opt.Key, opt.Value)
if err != nil {
return err
}
}
return nil
}
// GetOptions parses the genesis file in a format
// that can easily be handed into InitStaters
func GetOptions(path string) ([]Option, error) {
genDoc, err := load(path)
if err != nil {
return nil, err
}
opts := genDoc.AppOptions
cnt := 1 + len(opts.Accounts) + len(opts.pluginOptions)
res := make([]Option, cnt)
res[0] = Option{sdk.ModuleNameBase, sdk.ChainKey, genDoc.ChainID}
i := 1
// set accounts
for _, acct := range opts.Accounts {
res[i] = Option{"coin", "account", string(acct)}
i++
}
// set plugin options
for _, kv := range opts.pluginOptions {
module, key := splitKey(kv.Key)
res[i] = Option{module, key, kv.Value}
i++
}
return res, nil
}
type keyValue struct {
Key string `json:"key"`
Value string `json:"value"`
}
// FullDoc - includes tendermint (in the json, we ignore here)
type FullDoc struct {
ChainID string `json:"chain_id"`
AppOptions *Doc `json:"app_options"`
}
// Doc - All genesis values
type Doc struct {
Accounts []json.RawMessage `json:"accounts"`
PluginOptions []json.RawMessage `json:"plugin_options"`
pluginOptions []keyValue // unmarshaled rawmessages
}
func load(filePath string) (*FullDoc, error) {
bytes, err := cmn.ReadFile(filePath)
if err != nil {
return nil, errors.Wrap(err, "loading genesis file")
}
// the basecoin genesis go-wire/data :)
genDoc := new(FullDoc)
err = json.Unmarshal(bytes, genDoc)
if err != nil {
return nil, errors.Wrap(err, "unmarshaling genesis file")
}
if genDoc.AppOptions == nil {
genDoc.AppOptions = new(Doc)
}
pluginOpts, err := parseList(genDoc.AppOptions.PluginOptions)
if err != nil {
return nil, err
}
genDoc.AppOptions.pluginOptions = pluginOpts
return genDoc, nil
}
func parseList(kvzIn []json.RawMessage) (kvz []keyValue, err error) {
if len(kvzIn)%2 != 0 {
return nil, errors.New("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]")
}
for i := 0; i < len(kvzIn); i += 2 {
kv := keyValue{}
rawK := []byte(kvzIn[i])
err := json.Unmarshal(rawK, &(kv.Key))
if err != nil {
return nil, errors.Errorf("Non-string key: %s", string(rawK))
}
// convert value to string if possible (otherwise raw json)
rawV := kvzIn[i+1]
err = json.Unmarshal(rawV, &(kv.Value))
if err != nil {
kv.Value = string(rawV)
}
kvz = append(kvz, kv)
}
return kvz, nil
}
// Splits the string at the first '/'.
// if there are none, assign default module ("base").
func splitKey(key string) (string, string) {
if strings.Contains(key, "/") {
keyParts := strings.SplitN(key, "/", 2)
return keyParts[0], keyParts[1]
}
return sdk.ModuleNameBase, key
}

78
genesis/parse_test.go Normal file
View File

@ -0,0 +1,78 @@
package genesis
import (
"encoding/json"
"testing"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/tmlibs/common"
)
const genesisFilepath = "./testdata/genesis.json"
func TestParseList(t *testing.T) {
assert, require := assert.New(t), require.New(t)
bytes, err := cmn.ReadFile(genesisFilepath)
require.Nil(err, "loading genesis file %+v", err)
// the basecoin genesis go-wire/data :)
genDoc := new(FullDoc)
err = json.Unmarshal(bytes, genDoc)
require.Nil(err, "unmarshaling genesis file %+v", err)
pluginOpts, err := parseList(genDoc.AppOptions.PluginOptions)
require.Nil(err, "%+v", err)
genDoc.AppOptions.pluginOptions = pluginOpts
assert.Equal(genDoc.AppOptions.pluginOptions[0].Key, "plugin1/key1")
assert.Equal(genDoc.AppOptions.pluginOptions[1].Key, "plugin1/key2")
assert.Equal(genDoc.AppOptions.pluginOptions[0].Value, "value1")
assert.Equal(genDoc.AppOptions.pluginOptions[1].Value, "value2")
}
func TestGetOptions(t *testing.T) {
assert, require := assert.New(t), require.New(t)
opts, err := GetOptions(genesisFilepath)
require.Nil(err, "loading genesis file %+v", err)
require.Equal(4, len(opts))
chain := opts[0]
assert.Equal(sdk.ModuleNameBase, chain.Module)
assert.Equal(sdk.ChainKey, chain.Key)
assert.Equal("foo_bar_chain", chain.Value)
acct := opts[1]
assert.Equal("coin", acct.Module)
assert.Equal("account", acct.Key)
p1 := opts[2]
assert.Equal("plugin1", p1.Module)
assert.Equal("key1", p1.Key)
assert.Equal("value1", p1.Value)
p2 := opts[3]
assert.Equal("plugin1", p2.Module)
assert.Equal("key2", p2.Key)
assert.Equal("value2", p2.Value)
}
func TestSplitKey(t *testing.T) {
assert := assert.New(t)
prefix, suffix := splitKey("foo/bar")
assert.EqualValues("foo", prefix)
assert.EqualValues("bar", suffix)
prefix, suffix = splitKey("foobar")
assert.EqualValues("base", prefix)
assert.EqualValues("foobar", suffix)
prefix, suffix = splitKey("some/complex/issue")
assert.EqualValues("some", prefix)
assert.EqualValues("complex/issue", suffix)
}

22
genesis/testdata/genesis.json vendored Normal file
View File

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

View File

@ -8,6 +8,13 @@ import (
"github.com/cosmos/cosmos-sdk/state"
)
const (
// ModuleNameBase is the module name for internal functionality
ModuleNameBase = "base"
// ChainKey is the option key for setting the chain id
ChainKey = "chain_id"
)
// Handler is anything that processes a transaction
type Handler interface {
// Checker verifies there are valid fees and estimates work
@ -25,6 +32,18 @@ type Handler interface {
// BeginBlock(store state.SimpleDB, hash []byte, header *abci.Header)
}
// Ticker can be executed every block
type Ticker interface {
Tick(Context, state.SimpleDB) ([]*abci.Validator, error)
}
// TickerFunc allows a function to implement the interface
type TickerFunc func(Context, state.SimpleDB) ([]*abci.Validator, error)
func (t TickerFunc) Tick(ctx Context, store state.SimpleDB) ([]*abci.Validator, error) {
return t(ctx, store)
}
// Named ensures there is a name for the item
type Named interface {
Name() string

View File

@ -1,10 +1,12 @@
package eyes
import (
wire "github.com/tendermint/go-wire"
"github.com/tendermint/tmlibs/log"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/errors"
"github.com/cosmos/cosmos-sdk/state"
wire "github.com/tendermint/go-wire"
)
const (
@ -19,7 +21,6 @@ const (
// Handler allows us to set and remove data
type Handler struct {
sdk.NopInitState
sdk.NopInitValidate
}
@ -35,6 +36,16 @@ func (Handler) Name() string {
return Name
}
// InitState - sets the genesis state
func (h Handler) InitState(l log.Logger, store state.SimpleDB,
module, key, value string) (log string, err error) {
if module != Name {
return "", errors.ErrUnknownModule(module)
}
store.Set([]byte(key), []byte(value))
return key, nil
}
// CheckTx verifies if the transaction is properly formated
func (h Handler) CheckTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx) (res sdk.CheckResult, err error) {
err = tx.ValidateBasic()

View File

@ -21,6 +21,8 @@ import (
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/app"
"github.com/cosmos/cosmos-sdk/genesis"
"github.com/cosmos/cosmos-sdk/version"
)
// StartCmd - command to start running the abci app (and tendermint)!
@ -31,7 +33,7 @@ var StartCmd = &cobra.Command{
}
// GetTickStartCmd - initialize a command as the start command with tick
func GetTickStartCmd(tick app.Ticker) *cobra.Command {
func GetTickStartCmd(tick sdk.Ticker) *cobra.Command {
startCmd := &cobra.Command{
Use: "start",
Short: "Start this full node",
@ -70,42 +72,47 @@ func addStartFlag(startCmd *cobra.Command) {
}
//returns the start command which uses the tick
func tickStartCmd(tick app.Ticker) func(cmd *cobra.Command, args []string) error {
func tickStartCmd(clock sdk.Ticker) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
rootDir := viper.GetString(cli.HomeFlag)
store, err := app.NewStore(
cmdName := cmd.Root().Name()
appName := fmt.Sprintf("%s v%v", cmdName, version.Version)
storeApp, err := app.NewStoreApp(
appName,
path.Join(rootDir, "data", "merkleeyes.db"),
EyesCacheSize,
logger.With("module", "store"),
)
logger.With("module", "app"))
if err != nil {
return err
}
// Create Basecoin app
basecoinApp := app.NewBasecoinTick(Handler, store, logger.With("module", "app"), tick)
return start(rootDir, store, basecoinApp)
basecoinApp := app.NewBaseApp(storeApp, Handler, clock)
return start(rootDir, basecoinApp)
}
}
func startCmd(cmd *cobra.Command, args []string) error {
rootDir := viper.GetString(cli.HomeFlag)
store, err := app.NewStore(
cmdName := cmd.Root().Name()
appName := fmt.Sprintf("%s v%v", cmdName, version.Version)
storeApp, err := app.NewStoreApp(
appName,
path.Join(rootDir, "data", "merkleeyes.db"),
EyesCacheSize,
logger.With("module", "store"),
)
logger.With("module", "app"))
if err != nil {
return err
}
// Create Basecoin app
basecoinApp := app.NewBasecoin(Handler, store, logger.With("module", "app"))
return start(rootDir, store, basecoinApp)
basecoinApp := app.NewBaseApp(storeApp, Handler, nil)
return start(rootDir, basecoinApp)
}
func start(rootDir string, store *app.Store, basecoinApp *app.Basecoin) error {
func start(rootDir string, basecoinApp *app.BaseApp) error {
// if chain_id has not been set yet, load the genesis.
// else, assume it's been loaded
@ -113,7 +120,7 @@ func start(rootDir string, store *app.Store, basecoinApp *app.Basecoin) error {
// If genesis file exists, set key-value options
genesisFile := path.Join(rootDir, "genesis.json")
if _, err := os.Stat(genesisFile); err == nil {
err := basecoinApp.LoadGenesis(genesisFile)
err = genesis.Load(basecoinApp, genesisFile)
if err != nil {
return errors.Errorf("Error in LoadGenesis: %v\n", err)
}

View File

@ -5,19 +5,17 @@ import "github.com/tendermint/iavl"
// State represents the app states, separating the commited state (for queries)
// from the working state (for CheckTx and AppendTx)
type State struct {
committed *Bonsai
deliverTx SimpleDB
checkTx SimpleDB
persistent bool
committed *Bonsai
deliverTx SimpleDB
checkTx SimpleDB
}
func NewState(tree *iavl.VersionedTree) State {
func NewState(tree *iavl.VersionedTree) *State {
base := NewBonsai(tree)
return State{
committed: base,
deliverTx: base.Checkpoint(),
checkTx: base.Checkpoint(),
persistent: true,
return &State{
committed: base,
deliverTx: base.Checkpoint(),
checkTx: base.Checkpoint(),
}
}
@ -45,14 +43,6 @@ func (s State) LatestHash() []byte {
return s.committed.Tree.Hash()
}
// BatchSet is used for some weird magic in storing the new height
func (s *State) BatchSet(key, value []byte) {
if s.persistent {
// This is in the batch with the Save, but not in the tree
s.committed.Tree.BatchSet(key, value)
}
}
// Commit save persistent nodes to the database and re-copies the trees
func (s *State) Commit(version uint64) ([]byte, error) {
// commit (if we didn't do hash earlier)
@ -62,15 +52,11 @@ func (s *State) Commit(version uint64) ([]byte, error) {
}
var hash []byte
if s.persistent {
if s.committed.Tree.Size() > 0 || s.committed.Tree.LatestVersion() > 0 {
hash, err = s.committed.Tree.SaveVersion(version)
if err != nil {
return nil, err
}
if s.committed.Tree.Size() > 0 || s.committed.Tree.LatestVersion() > 0 {
hash, err = s.committed.Tree.SaveVersion(version)
if err != nil {
return nil, err
}
} else {
hash = s.committed.Tree.Hash()
}
s.deliverTx = s.committed.Checkpoint()