Merge pull request #314 from cosmos/sdk2-app

New App structure
This commit is contained in:
Ethan Buchman 2018-01-05 11:30:51 -05:00 committed by GitHub
commit 8f14e4be66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 383 additions and 277 deletions

View File

@ -11,10 +11,10 @@ import (
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
sdk "github.com/cosmos/cosmos-sdk" "github.com/cosmos/cosmos-sdk/types"
) )
const mainKeyHeader = "header" var mainHeaderKey = []byte("header")
// App - The ABCI application // App - The ABCI application
type App struct { type App struct {
@ -23,17 +23,20 @@ type App struct {
// App name from abci.Info // App name from abci.Info
name string name string
// DeliverTx (main) state // Main (uncached) state
store MultiStore ms types.CommitMultiStore
// CheckTx state // CheckTx state, a cache-wrap of `.ms`.
storeCheck CacheMultiStore msCheck types.CacheMultiStore
// DeliverTx state, a cache-wrap of `.ms`.
msDeliver types.CacheMultiStore
// Current block header // Current block header
header *abci.Header header abci.Header
// Handler for CheckTx and DeliverTx. // Handler for CheckTx and DeliverTx.
handler sdk.Handler handler types.Handler
// Cached validator changes from DeliverTx // Cached validator changes from DeliverTx
valUpdates []abci.Validator valUpdates []abci.Validator
@ -48,59 +51,71 @@ func NewApp(name string) *App {
} }
} }
func (app *App) SetStore(store MultiStore) { func (app *App) Name() string {
app.store = store return app.name
} }
func (app *App) SetHandler(handler Handler) { func (app *App) SetCommitMultiStore(ms types.CommitMultiStore) {
app.ms = ms
}
func (app *App) SetHandler(handler types.Handler) {
app.handler = handler app.handler = handler
} }
func (app *App) LoadLatestVersion() error { func (app *App) LoadLatestVersion() error {
store := app.store app.ms.LoadLatestVersion()
store.LoadLastVersion()
return app.initFromStore() return app.initFromStore()
} }
func (app *App) LoadVersion(version int64) error { func (app *App) LoadVersion(version int64) error {
store := app.store app.ms.LoadVersion(version)
store.LoadVersion(version)
return app.initFromStore() return app.initFromStore()
} }
// Initializes the remaining logic from app.store. // The last CommitID of the multistore.
func (app *App) LastCommitID() types.CommitID {
return app.ms.LastCommitID()
}
// The last commited block height.
func (app *App) LastBlockHeight() int64 {
return app.ms.LastCommitID().Version
}
// Initializes the remaining logic from app.ms.
func (app *App) initFromStore() error { func (app *App) initFromStore() error {
store := app.store lastCommitID := app.ms.LastCommitID()
lastCommitID := store.LastCommitID() main := app.ms.GetKVStore("main")
main := store.GetKVStore("main") header := abci.Header{}
header := (*abci.Header)(nil)
storeCheck := store.CacheMultiStore()
// Main store should exist. // Main store should exist.
if store.GetKVStore("main") == nil { if app.ms.GetKVStore("main") == nil {
return errors.New("App expects MultiStore with 'main' KVStore") return errors.New("App expects MultiStore with 'main' KVStore")
} }
// If we've committed before, we expect main://<mainKeyHeader>. // If we've committed before, we expect main://<mainHeaderKey>.
if !lastCommitID.IsZero() { if !lastCommitID.IsZero() {
headerBytes, ok := main.Get(mainKeyHeader) headerBytes := main.Get(mainHeaderKey)
if !ok { if len(headerBytes) == 0 {
errStr := fmt.Sprintf("Version > 0 but missing key %s", mainKeyHeader) errStr := fmt.Sprintf("Version > 0 but missing key %s", mainHeaderKey)
return errors.New(errStr) return errors.New(errStr)
} }
err = proto.Unmarshal(headerBytes, header) err := proto.Unmarshal(headerBytes, &header)
if err != nil { if err != nil {
return errors.Wrap(err, "Failed to parse Header") return errors.Wrap(err, "Failed to parse Header")
} }
if header.Height != lastCommitID.Version { lastVersion := lastCommitID.Version
errStr := fmt.Sprintf("Expected main://%s.Height %v but got %v", mainKeyHeader, version, headerHeight) if header.Height != lastVersion {
errStr := fmt.Sprintf("Expected main://%s.Height %v but got %v", mainHeaderKey, lastVersion, header.Height)
return errors.New(errStr) return errors.New(errStr)
} }
} }
// Set App state. // Set App state.
app.header = header app.header = header
app.storeCheck = app.store.CacheMultiStore() app.msCheck = nil
app.msDeliver = nil
app.valUpdates = nil app.valUpdates = nil
return nil return nil
@ -108,21 +123,83 @@ func (app *App) initFromStore() error {
//---------------------------------------- //----------------------------------------
// DeliverTx - ABCI - dispatches to the handler // Implements ABCI
func (app *App) DeliverTx(txBytes []byte) abci.ResponseDeliverTx { func (app *App) 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 *App) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) {
// TODO: Implement
return
}
// Implements ABCI
func (app *App) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
// TODO: Use req.Validators
return
}
// Implements ABCI
func (app *App) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
// TODO: See app/query.go
return
}
// Implements ABCI
func (app *App) 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 *App) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
// Initialize arguments to Handler.
var isCheckTx = true
var ctx = types.NewContext(app.header, isCheckTx, txBytes)
var tx types.Tx = nil // nil until a decorator parses one.
// Run the handler.
var result = app.handler(ctx, app.ms, tx)
// Tell the blockchain engine (i.e. Tendermint).
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 *App) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
// Initialize arguments to Handler. // Initialize arguments to Handler.
var isCheckTx = false var isCheckTx = false
var ctx = sdk.NewContext(app.header, isCheckTx, txBytes) var ctx = types.NewContext(app.header, isCheckTx, txBytes)
var store = app.store var tx types.Tx = nil // nil until a decorator parses one.
var tx Tx = nil // nil until a decorator parses one.
// Run the handler. // Run the handler.
var result = app.handler(ctx, app.store, tx) var result = app.handler(ctx, app.ms, tx)
// After-handler hooks. // After-handler hooks.
if result.Code == abci.CodeType_OK { if result.Code == abci.CodeTypeOK {
app.valUpdates = append(app.valUpdates, result.ValUpdate) app.valUpdates = append(app.valUpdates, result.ValidatorUpdates...)
} else { } else {
// Even though the Code is not OK, there will be some side effects, // Even though the Code is not OK, there will be some side effects,
// like those caused by fee deductions or sequence incrementations. // like those caused by fee deductions or sequence incrementations.
@ -133,132 +210,34 @@ func (app *App) DeliverTx(txBytes []byte) abci.ResponseDeliverTx {
Code: result.Code, Code: result.Code,
Data: result.Data, Data: result.Data,
Log: result.Log, Log: result.Log,
GasWanted: result.GasWanted,
GasUsed: result.GasUsed,
Tags: result.Tags, Tags: result.Tags,
} }
} }
// CheckTx - ABCI - dispatches to the handler // Implements ABCI
func (app *App) CheckTx(txBytes []byte) abci.ResponseCheckTx { func (app *App) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
res.ValidatorUpdates = app.valUpdates
// Initialize arguments to Handler.
var isCheckTx = true
var ctx = sdk.NewContext(app.header, isCheckTx, txBytes)
var store = app.store
var tx Tx = nil // nil until a decorator parses one.
// Run the handler.
var result = app.handler(ctx, app.store, tx)
// Tell the blockchain engine (i.e. Tendermint).
return abci.ResponseDeliverTx{
Code: result.Code,
Data: result.Data,
Log: result.Log,
Gas: result.Gas,
FeeDenom: result.FeeDenom,
FeeAmount: result.FeeAmount,
}
}
// Info - ABCI
func (app *App) Info(req abci.RequestInfo) abci.ResponseInfo {
lastCommitID := app.store.LastCommitID()
return abci.ResponseInfo{
Data: app.Name,
LastBlockHeight: lastCommitID.Version,
LastBlockAppHash: lastCommitID.Hash,
}
}
// SetOption - ABCI
func (app *App) SetOption(key string, value string) string {
return "Not Implemented"
}
// Query - ABCI
func (app *App) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
/*
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
*/
}
// Commit implements abci.Application
func (app *App) Commit() (res abci.Result) {
commitID := app.store.Commit()
app.logger.Debug("Commit synced",
"commit", commitID,
)
return abci.NewResultOK(hash, "")
}
// InitChain - ABCI
func (app *App) InitChain(req abci.RequestInitChain) {}
// BeginBlock - ABCI
func (app *App) BeginBlock(req abci.RequestBeginBlock) {
app.header = req.Header
}
// EndBlock - ABCI
// Returns a list of all validator changes made in this block
func (app *App) EndBlock(height uint64) (res abci.ResponseEndBlock) {
// XXX Update to res.Updates.
res.Diffs = app.valUpdates
app.valUpdates = nil app.valUpdates = nil
return return
} }
// Implements ABCI
func (app *App) 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 // Return index of list with validator of same PubKey, or -1 if no match
func pubKeyIndex(val *abci.Validator, list []*abci.Validator) int { func pubKeyIndex(val *abci.Validator, list []*abci.Validator) int {
for i, v := range list { for i, v := range list {
@ -275,38 +254,3 @@ func pubKeyIndex(val *abci.Validator, list []*abci.Validator) int {
func makeDefaultLogger() log.Logger { func makeDefaultLogger() log.Logger {
return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
} }
// InitState - used to setup state (was SetOption)
// to be call from setting up the genesis file
func (app *InitApp) 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.initState.InitState(logger, state, module, key, value)
if err != nil {
logger.Error("Invalid genesis option", "err", err)
} else {
logger.Info(log)
}
return err
}
// InitChain - ABCI - sets the initial validators
func (app *InitApp) InitChain(req abci.RequestInitChain) {
// return early if no InitValidator registered
if app.initVals == nil {
return
}
logger, store := app.Logger(), app.Append()
app.initVals.InitValidators(logger, store, req.Validators)
}

View File

@ -7,12 +7,13 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk" "github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
) )
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
@ -24,31 +25,33 @@ func TestBasic(t *testing.T) {
} }
// Create app. // Create app.
app := sdk.NewApp(t.Name()) app := NewApp(t.Name())
app.SetStore(mockMultiStore()) app.SetCommitMultiStore(newCommitMultiStore())
app.SetHandler(func(ctx Context, store MultiStore, tx Tx) Result { app.SetHandler(func(ctx types.Context, store types.MultiStore, tx types.Tx) types.Result {
// This could be a decorator. // This could be a decorator.
fromJSON(ctx.TxBytes(), &tx) var ttx testTx
fromJSON(ctx.TxBytes(), &ttx)
fmt.Println(">>", tx) // XXX
return types.Result{}
}) })
// Load latest state, which should be empty. // Load latest state, which should be empty.
err := app.LoadLatestVersion() err := app.LoadLatestVersion()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, app.NextVersion(), 1) assert.Equal(t, app.LastBlockHeight(), int64(0))
// Create the validators // Create the validators
var numVals = 3 var numVals = 3
var valSet = make([]*abci.Validator, numVals) var valSet = make([]abci.Validator, numVals)
for i := 0; i < numVals; i++ { for i := 0; i < numVals; i++ {
valSet[i] = makeVal(secret(i)) valSet[i] = makeVal(secret(i))
} }
// Initialize the chain // Initialize the chain
app.InitChain(abci.RequestInitChain{ app.InitChain(abci.RequestInitChain{
Validators: valset, Validators: valSet,
}) })
// Simulate the start of a block. // Simulate the start of a block.
@ -62,24 +65,26 @@ func TestBasic(t *testing.T) {
} }
txBytes := toJSON(tx) txBytes := toJSON(tx)
res := app.DeliverTx(txBytes) res := app.DeliverTx(txBytes)
require.True(res.IsOK(), "%#v", res) assert.True(t, res.IsOK(), "%#v", res)
} }
// Simulate the end of a block. // Simulate the end of a block.
// Get the summary of validator updates. // Get the summary of validator updates.
res := app.EndBlock(app.height) res := app.EndBlock(abci.RequestEndBlock{})
valUpdates := res.ValidatorUpdates valUpdates := res.ValidatorUpdates
// Assert that validator updates are correct. // Assert that validator updates are correct.
for _, val := range valSet { for _, val := range valSet {
// Sanity
assert.NotEqual(t, len(val.PubKey), 0)
// Find matching update and splice it out. // Find matching update and splice it out.
for j := 0; j < len(valUpdates); { for j := 0; j < len(valUpdates); {
assert.NotEqual(len(valUpdates.PubKey), 0) valUpdate := valUpdates[j]
// Matched. // Matched.
if bytes.Equal(valUpdate.PubKey, val.PubKey) { if bytes.Equal(valUpdate.PubKey, val.PubKey) {
assert.Equal(valUpdate.NewPower, val.Power+1) assert.Equal(t, valUpdate.Power, val.Power+1)
if j < len(valUpdates)-1 { if j < len(valUpdates)-1 {
// Splice it out. // Splice it out.
valUpdates = append(valUpdates[:j], valUpdates[j+1:]...) valUpdates = append(valUpdates[:j], valUpdates[j+1:]...)
@ -100,9 +105,9 @@ func randPower() int64 {
return cmn.RandInt64() return cmn.RandInt64()
} }
func makeVal(secret string) *abci.Validator { func makeVal(secret string) abci.Validator {
return &abci.Validator{ return abci.Validator{
PubKey: makePubKey(string).Bytes(), PubKey: makePubKey(secret).Bytes(),
Power: randPower(), Power: randPower(),
} }
} }
@ -112,29 +117,43 @@ func makePubKey(secret string) crypto.PubKey {
} }
func makePrivKey(secret string) crypto.PrivKey { func makePrivKey(secret string) crypto.PrivKey {
return crypto.GenPrivKeyEd25519FromSecret([]byte(id)) privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
return privKey.Wrap()
} }
func secret(index int) []byte { func secret(index int) string {
return []byte(fmt.Sprintf("secret%d", index)) return fmt.Sprintf("secret%d", index)
} }
func copyVal(val *abci.Validator) *abci.Validator { func copyVal(val abci.Validator) abci.Validator {
val2 := *val // val2 := *val
return &val2 // return &val2
return val
} }
func toJSON(o interface{}) []byte { func toJSON(o interface{}) []byte {
bytes, err := json.Marshal(o) bz, err := json.Marshal(o)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return bytes // fmt.Println(">> toJSON:", string(bz))
return bz
} }
func fromJSON(bytes []byte, ptr interface{}) { func fromJSON(bz []byte, ptr interface{}) {
err := json.Unmarshal(bytes, ptr) // fmt.Println(">> fromJSON:", string(bz))
err := json.Unmarshal(bz, ptr)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
// Creates a sample CommitMultiStore
func newCommitMultiStore() types.CommitMultiStore {
dbMain := dbm.NewMemDB()
dbXtra := dbm.NewMemDB()
ms := store.NewMultiStore(dbMain) // Also store rootMultiStore metadata here (it shouldn't clash)
ms.SetSubstoreLoader("main", store.NewIAVLStoreLoader(dbMain, 0, 0))
ms.SetSubstoreLoader("xtra", store.NewIAVLStoreLoader(dbXtra, 0, 0))
return ms
}

54
app/query.go Normal file
View File

@ -0,0 +1,54 @@
package app
/*
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
*/

4
glide.lock generated
View File

@ -112,7 +112,7 @@ imports:
- leveldb/table - leveldb/table
- leveldb/util - leveldb/util
- name: github.com/tendermint/abci - name: github.com/tendermint/abci
version: e4b9f1abe794a2117a59738a1294e09b46d0fa00 version: 8f87efd7f86c2bae9df69aff69d715593d5d79f7
subpackages: subpackages:
- client - client
- example/dummy - example/dummy
@ -174,7 +174,7 @@ imports:
- types - types
- version - version
- name: github.com/tendermint/tmlibs - name: github.com/tendermint/tmlibs
version: b70ae4919befb6ae3e5cb40ae8174e122e771d08 version: b25df389db3c98f4b964bd39511c199f02d07715
subpackages: subpackages:
- autofile - autofile
- cli - cli

View File

@ -48,6 +48,7 @@ type iavlStore struct {
tree *iavl.VersionedTree tree *iavl.VersionedTree
// How many old versions we hold onto. // How many old versions we hold onto.
// A value of 0 means keep all history.
numHistory int64 numHistory int64
} }
@ -71,7 +72,7 @@ func (st *iavlStore) Commit() CommitID {
} }
// Release an old version of history // Release an old version of history
if st.numHistory < st.tree.Version64() { if st.numHistory > 0 && (st.numHistory < st.tree.Version64()) {
toRelease := version - st.numHistory toRelease := version - st.numHistory
st.tree.DeleteVersion(toRelease) st.tree.DeleteVersion(toRelease)
} }

View File

@ -26,6 +26,8 @@ type rootMultiStore struct {
substores map[string]CommitStore substores map[string]CommitStore
} }
var _ CommitMultiStore = (*rootMultiStore)(nil)
func NewMultiStore(db dbm.DB) *rootMultiStore { func NewMultiStore(db dbm.DB) *rootMultiStore {
return &rootMultiStore{ return &rootMultiStore{
db: db, db: db,
@ -35,6 +37,7 @@ func NewMultiStore(db dbm.DB) *rootMultiStore {
} }
} }
// Implements CommitMultiStore.
func (rs *rootMultiStore) SetSubstoreLoader(name string, loader CommitStoreLoader) { func (rs *rootMultiStore) SetSubstoreLoader(name string, loader CommitStoreLoader) {
if _, ok := rs.storeLoaders[name]; ok { if _, ok := rs.storeLoaders[name]; ok {
panic(fmt.Sprintf("rootMultiStore duplicate substore name " + name)) panic(fmt.Sprintf("rootMultiStore duplicate substore name " + name))
@ -42,17 +45,18 @@ func (rs *rootMultiStore) SetSubstoreLoader(name string, loader CommitStoreLoade
rs.storeLoaders[name] = loader rs.storeLoaders[name] = loader
} }
// Call once after all calls to SetSubstoreLoader are complete. // Implements CommitMultiStore.
func (rs *rootMultiStore) GetSubstore(name string) CommitStore {
return rs.substores[name]
}
// Implements CommitMultiStore.
func (rs *rootMultiStore) LoadLatestVersion() error { func (rs *rootMultiStore) LoadLatestVersion() error {
ver := getLatestVersion(rs.db) ver := getLatestVersion(rs.db)
return rs.LoadVersion(ver) return rs.LoadVersion(ver)
} }
// NOTE: Returns 0 unless LoadVersion() or LoadLatestVersion() is called. // Implements CommitMultiStore.
func (rs *rootMultiStore) NextVersion() int64 {
return rs.nextVersion
}
func (rs *rootMultiStore) LoadVersion(ver int64) error { func (rs *rootMultiStore) LoadVersion(ver int64) error {
// Special logic for version 0 // Special logic for version 0
@ -106,10 +110,13 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error {
return nil return nil
} }
// Implements CommitStore //----------------------------------------
// +CommitStore
// Implements CommitStore.
func (rs *rootMultiStore) Commit() CommitID { func (rs *rootMultiStore) Commit() CommitID {
// Commit substores // Commit substores.
version := rs.nextVersion version := rs.nextVersion
state := commitSubstores(version, rs.substores) state := commitSubstores(version, rs.substores)
@ -129,27 +136,36 @@ func (rs *rootMultiStore) Commit() CommitID {
return commitID return commitID
} }
// Implements CommitStore // Implements CommitStore.
func (rs *rootMultiStore) CacheWrap() CacheWrap { func (rs *rootMultiStore) CacheWrap() CacheWrap {
return rs.CacheMultiStore().(CacheWrap) return rs.CacheMultiStore().(CacheWrap)
} }
// Get the last committed CommitID //----------------------------------------
// +MultiStore
// Implements MultiStore.
func (rs *rootMultiStore) LastCommitID() CommitID { func (rs *rootMultiStore) LastCommitID() CommitID {
return rs.lastCommitID return rs.lastCommitID
} }
// Implements MultiStore // Implements MultiStore.
// NOTE: Returns 0 unless LoadVersion() or LoadLatestVersion() is called.
func (rs *rootMultiStore) NextVersion() int64 {
return rs.nextVersion
}
// Implements MultiStore.
func (rs *rootMultiStore) CacheMultiStore() CacheMultiStore { func (rs *rootMultiStore) CacheMultiStore() CacheMultiStore {
return newCacheMultiStoreFromRMS(rs) return newCacheMultiStoreFromRMS(rs)
} }
// Implements MultiStore // Implements MultiStore.
func (rs *rootMultiStore) GetCommitStore(name string) CommitStore { func (rs *rootMultiStore) GetStore(name string) interface{} {
return rs.substores[name] return rs.substores[name]
} }
// Implements MultiStore // Implements MultiStore.
func (rs *rootMultiStore) GetKVStore(name string) KVStore { func (rs *rootMultiStore) GetKVStore(name string) KVStore {
return rs.substores[name].(KVStore) return rs.substores[name].(KVStore)
} }
@ -208,6 +224,7 @@ func (sc substoreCore) Hash() []byte {
} }
//---------------------------------------- //----------------------------------------
// Misc.
func getLatestVersion(db dbm.DB) int64 { func getLatestVersion(db dbm.DB) int64 {
var latest int64 var latest int64

View File

@ -1,12 +1,9 @@
package coin package types
import ( import (
"fmt" "fmt"
"sort" "sort"
"strconv"
"strings" "strings"
"github.com/pkg/errors"
) )
// Coin hold some amount of one currency // Coin hold some amount of one currency
@ -200,3 +197,11 @@ var _ sort.Interface = Coins{}
// Sort is a helper function to sort the set of coins inplace // Sort is a helper function to sort the set of coins inplace
func (coins Coins) Sort() { sort.Sort(coins) } func (coins Coins) Sort() { sort.Sort(coins) }
//----------------------------------------
// Misc
type Coinser interface {
GetCoins() Coins
SetCoins(Coins)
}

View File

@ -1,4 +1,4 @@
package coin package types
import ( import (
"testing" "testing"

View File

@ -2,10 +2,46 @@ package types
import ( import (
"context" "context"
"github.com/golang/protobuf/proto"
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
) )
/*
A note on Context security:
The intent of Context is for it to be an immutable object that can be cloned
and updated cheaply with WithValue() and passed forward to the next decorator
or handler. For example,
```golang
func Decorator(ctx Context, ms MultiStore, tx Tx, next Handler) Result {
// Clone and update context with new kv pair.
ctx2 := ctx.WithValueSDK(key, value)
// Call the next decorator/handler.
res := next(ctx2, ms, tx)
// At this point, while `ctx` and `ctx2`'s shallow values haven't changed,
// it's possible that slices or addressable struct fields have been
// modified by the call to `next(...)`.
//
// This is generally undesirable because it prevents a decorator from
// rolling back all side effects--which is the intent of immutable
// `Context`s and store cache-wraps.
}
```
While well-written decorators wouldn't mutate any mutable context values, a malicious or buggy plugin can create unwanted side-effects, so it is highly advised for users of Context to only set immutable values. To help enforce this contract, we require values to be certain primitive types, or a Cloner.
*/
type Cloner interface {
Clone() interface{} // deep copy
}
type Context struct { type Context struct {
context.Context context.Context
// Don't add any other fields here, // Don't add any other fields here,
@ -13,9 +49,7 @@ type Context struct {
} }
func NewContext(header abci.Header, isCheckTx bool, txBytes []byte) Context { func NewContext(header abci.Header, isCheckTx bool, txBytes []byte) Context {
c := Context{ c := Context{context.Background()}
Context: context.Background(),
}
c = c.setBlockHeader(header) c = c.setBlockHeader(header)
c = c.setBlockHeight(header.Height) c = c.setBlockHeight(header.Height)
c = c.setChainID(header.ChainID) c = c.setChainID(header.ChainID)
@ -24,16 +58,47 @@ func NewContext(header abci.Header, isCheckTx bool, txBytes []byte) Context {
return c return c
} }
// The original context.Context API. func (c Context) Value(key interface{}) interface{} {
func (c Context) WithValue(key interface{}, value interface{}) context.Context { value := c.Context.Value(key)
return context.WithValue(c.Context, key, value) if cloner, ok := value.(Cloner); ok {
return cloner.Clone()
}
if message, ok := value.(proto.Message); ok {
return proto.Clone(message)
}
return value
} }
// Like WithValue() but retains this API. func (c Context) WithValue(key interface{}, value Cloner) Context {
func (c Context) WithValueSDK(key interface{}, value interface{}) Context { return c.withValue(key, value)
return Context{ }
Context: context.WithValue(c.Context, key, value),
} func (c Context) WithValueProto(key interface{}, value proto.Message) Context {
return c.withValue(key, value)
}
func (c Context) WithValueString(key interface{}, value string) Context {
return c.withValue(key, value)
}
func (c Context) WithValueInt32(key interface{}, value int32) Context {
return c.withValue(key, value)
}
func (c Context) WithValueUint32(key interface{}, value uint32) Context {
return c.withValue(key, value)
}
func (c Context) WithValueUint64(key interface{}, value uint64) Context {
return c.withValue(key, value)
}
func (c Context) WithValueUnsafe(key interface{}, value interface{}) Context {
return c.withValue(key, value)
}
func (c Context) withValue(key interface{}, value interface{}) Context {
return Context{context.WithValue(c.Context, key, value)}
} }
//---------------------------------------- //----------------------------------------
@ -71,25 +136,26 @@ func (c Context) TxBytes() []byte {
// Unexposed to prevent overriding. // Unexposed to prevent overriding.
func (c Context) setBlockHeader(header abci.Header) Context { func (c Context) setBlockHeader(header abci.Header) Context {
return c.WithValueSDK(contextKeyBlockHeader, header) var _ proto.Message = &header // for cloning.
return c.withValue(contextKeyBlockHeader, header)
} }
// Unexposed to prevent overriding. // Unexposed to prevent overriding.
func (c Context) setBlockHeight(height int64) Context { func (c Context) setBlockHeight(height int64) Context {
return c.WithValueSDK(contextKeyBlockHeight, height) return c.withValue(contextKeyBlockHeight, height)
} }
// Unexposed to prevent overriding. // Unexposed to prevent overriding.
func (c Context) setChainID(chainID string) Context { func (c Context) setChainID(chainID string) Context {
return c.WithValueSDK(contextKeyChainID, chainID) return c.withValue(contextKeyChainID, chainID)
} }
// Unexposed to prevent overriding. // Unexposed to prevent overriding.
func (c Context) setIsCheckTx(isCheckTx bool) Context { func (c Context) setIsCheckTx(isCheckTx bool) Context {
return c.WithValueSDK(contextKeyIsCheckTx, isCheckTx) return c.withValue(contextKeyIsCheckTx, isCheckTx)
} }
// Unexposed to prevent overriding. // Unexposed to prevent overriding.
func (c Context) setTxBytes(txBytes []byte) Context { func (c Context) setTxBytes(txBytes []byte) Context {
return c.WithValueSDK(contextKeyTxBytes, txBytes) return c.withValue(contextKeyTxBytes, txBytes)
} }

View File

@ -17,8 +17,8 @@ type Result struct {
// Log is just debug information. NOTE: nondeterministic. // Log is just debug information. NOTE: nondeterministic.
Log string Log string
// GasAllocated is the maximum units of work we allow this tx to perform. // GasWanted is the maximum units of work we allow this tx to perform.
GasAllocated int64 GasWanted int64
// GasUsed is the amount of gas actually consumed. NOTE: not used. // GasUsed is the amount of gas actually consumed. NOTE: not used.
GasUsed int64 GasUsed int64
@ -28,7 +28,7 @@ type Result struct {
FeeDenom string FeeDenom string
// Changes to the validator set. // Changes to the validator set.
ValSetDiff []abci.Validator ValidatorUpdates []abci.Validator
// Tags are used for transaction indexing and pubsub. // Tags are used for transaction indexing and pubsub.
Tags []cmn.KVPair Tags []cmn.KVPair

View File

@ -26,7 +26,7 @@ type MultiStore interface {
// call CacheMultiStore.Write(). // call CacheMultiStore.Write().
CacheMultiStore() CacheMultiStore CacheMultiStore() CacheMultiStore
// Convenience // Convenience for fetching substores.
GetStore(name string) interface{} GetStore(name string) interface{}
GetKVStore(name string) KVStore GetKVStore(name string) KVStore
} }
@ -56,7 +56,11 @@ type CommitMultiStore interface {
// Add a substore loader. // Add a substore loader.
SetSubstoreLoader(name string, loader CommitStoreLoader) SetSubstoreLoader(name string, loader CommitStoreLoader)
// Gets the substore, which is a CommitSubstore.
GetSubstore(name string) CommitStore
// Load the latest persisted version. // Load the latest persisted version.
// Called once after all calls to SetSubstoreLoader are complete.
LoadLatestVersion() error LoadLatestVersion() error
// Load a specific persisted version. When you load an old version, or // Load a specific persisted version. When you load an old version, or

View File

@ -1,6 +0,0 @@
package coin
type Coinser interface {
GetCoins() Coins
SetCoins(Coins)
}

View File

@ -5,6 +5,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto" crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire/data" "github.com/tendermint/go-wire/data"
) )
@ -16,7 +17,7 @@ type GenesisAccount struct {
Address data.Bytes `json:"address"` Address data.Bytes `json:"address"`
// this from types.Account (don't know how to embed this properly) // this from types.Account (don't know how to embed this properly)
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known. PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
Balance Coins `json:"coins"` Balance types.Coins `json:"coins"`
} }
// ToAccount - GenesisAccount struct to a basecoin Account // ToAccount - GenesisAccount struct to a basecoin Account

View File

@ -4,6 +4,7 @@ package coin
import ( import (
"fmt" "fmt"
"github.com/cosmos/cosmos-sdk/types"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
@ -17,7 +18,7 @@ type CoinMsg interface {
// Input is a source of coins in a transaction. // Input is a source of coins in a transaction.
type Input struct { type Input struct {
Address cmn.Bytes Address cmn.Bytes
Coins Coins Coins types.Coins
} }
func (in Input) ValidateBasic() error { func (in Input) ValidateBasic() error {
@ -38,7 +39,7 @@ func (txIn TxInput) String() string {
} }
// NewTxInput - create a transaction input, used with SendTx // NewTxInput - create a transaction input, used with SendTx
func NewTxInput(addr Actor, coins Coins) TxInput { func NewTxInput(addr Actor, coins types.Coins) TxInput {
input := TxInput{ input := TxInput{
Address: addr, Address: addr,
Coins: coins, Coins: coins,
@ -51,7 +52,7 @@ func NewTxInput(addr Actor, coins Coins) TxInput {
// TxOutput - expected coin movement output, used with SendTx // TxOutput - expected coin movement output, used with SendTx
type TxOutput struct { type TxOutput struct {
Address Actor `json:"address"` Address Actor `json:"address"`
Coins Coins `json:"coins"` Coins types.Coins `json:"coins"`
} }
// ValidateBasic - validate transaction output // ValidateBasic - validate transaction output
@ -77,7 +78,7 @@ func (txOut TxOutput) String() string {
} }
// NewTxOutput - create a transaction output, used with SendTx // NewTxOutput - create a transaction output, used with SendTx
func NewTxOutput(addr Actor, coins Coins) TxOutput { func NewTxOutput(addr Actor, coins types.Coins) TxOutput {
output := TxOutput{ output := TxOutput{
Address: addr, Address: addr,
Coins: coins, Coins: coins,
@ -103,7 +104,7 @@ func NewSendTx(in []TxInput, out []TxOutput) SendTx { // types.Tx {
// NewSendOneTx is a helper for the standard (?) case where there is exactly // NewSendOneTx is a helper for the standard (?) case where there is exactly
// one sender and one recipient // one sender and one recipient
func NewSendOneTx(sender, recipient Actor, amount Coins) SendTx { func NewSendOneTx(sender, recipient Actor, amount types.Coins) SendTx {
in := []TxInput{{Address: sender, Coins: amount}} in := []TxInput{{Address: sender, Coins: amount}}
out := []TxOutput{{Address: recipient, Coins: amount}} out := []TxOutput{{Address: recipient, Coins: amount}}
return SendTx{Inputs: in, Outputs: out} return SendTx{Inputs: in, Outputs: out}
@ -120,7 +121,7 @@ func (tx SendTx) ValidateBasic() error {
return ErrNoOutputs() return ErrNoOutputs()
} }
// make sure all inputs and outputs are individually valid // make sure all inputs and outputs are individually valid
var totalIn, totalOut Coins var totalIn, totalOut types.Coins
for _, in := range tx.Inputs { for _, in := range tx.Inputs {
if err := in.ValidateBasic(); err != nil { if err := in.ValidateBasic(); err != nil {
return err return err
@ -153,11 +154,11 @@ type CreditTx struct {
// Credit is the amount to change the credit... // Credit is the amount to change the credit...
// This may be negative to remove some over-issued credit, // This may be negative to remove some over-issued credit,
// but can never bring the credit or the balance to negative // but can never bring the credit or the balance to negative
Credit Coins `json:"credit"` Credit types.Coins `json:"credit"`
} }
// NewCreditTx - modify the credit granted to a given account // NewCreditTx - modify the credit granted to a given account
func NewCreditTx(debitor Actor, credit Coins) CreditTx { func NewCreditTx(debitor Actor, credit types.Coins) CreditTx {
return CreditTx{Debitor: debitor, Credit: credit} return CreditTx{Debitor: debitor, Credit: credit}
} }