Revert "delete everything" (includes everything non-go-crypto)

This reverts commit 96a3502
This commit is contained in:
Liamsi
2018-06-20 17:35:30 -07:00
parent 587505d4d2
commit d2c05bc5b9
533 changed files with 69873 additions and 162 deletions
+79
View File
@@ -0,0 +1,79 @@
package state
import (
cmn "github.com/tendermint/tmlibs/common"
)
type (
ErrInvalidBlock error
ErrProxyAppConn error
ErrUnknownBlock struct {
Height int64
}
ErrBlockHashMismatch struct {
CoreHash []byte
AppHash []byte
Height int64
}
ErrAppBlockHeightTooHigh struct {
CoreHeight int64
AppHeight int64
}
ErrLastStateMismatch struct {
Height int64
Core []byte
App []byte
}
ErrStateMismatch struct {
Got *State
Expected *State
}
ErrNoValSetForHeight struct {
Height int64
}
ErrNoConsensusParamsForHeight struct {
Height int64
}
ErrNoABCIResponsesForHeight struct {
Height int64
}
)
func (e ErrUnknownBlock) Error() string {
return cmn.Fmt("Could not find block #%d", e.Height)
}
func (e ErrBlockHashMismatch) Error() string {
return cmn.Fmt("App block hash (%X) does not match core block hash (%X) for height %d", e.AppHash, e.CoreHash, e.Height)
}
func (e ErrAppBlockHeightTooHigh) Error() string {
return cmn.Fmt("App block height (%d) is higher than core (%d)", e.AppHeight, e.CoreHeight)
}
func (e ErrLastStateMismatch) Error() string {
return cmn.Fmt("Latest tendermint block (%d) LastAppHash (%X) does not match app's AppHash (%X)", e.Height, e.Core, e.App)
}
func (e ErrStateMismatch) Error() string {
return cmn.Fmt("State after replay does not match saved state. Got ----\n%v\nExpected ----\n%v\n", e.Got, e.Expected)
}
func (e ErrNoValSetForHeight) Error() string {
return cmn.Fmt("Could not find validator set for height #%d", e.Height)
}
func (e ErrNoConsensusParamsForHeight) Error() string {
return cmn.Fmt("Could not find consensus params for height #%d", e.Height)
}
func (e ErrNoABCIResponsesForHeight) Error() string {
return cmn.Fmt("Could not find results for height #%d", e.Height)
}
+398
View File
@@ -0,0 +1,398 @@
package state
import (
"fmt"
fail "github.com/ebuchman/fail-test"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tendermint/proxy"
"github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
)
//-----------------------------------------------------------------------------
// BlockExecutor handles block execution and state updates.
// It exposes ApplyBlock(), which validates & executes the block, updates state w/ ABCI responses,
// then commits and updates the mempool atomically, then saves state.
// BlockExecutor provides the context and accessories for properly executing a block.
type BlockExecutor struct {
// save state, validators, consensus params, abci responses here
db dbm.DB
// execute the app against this
proxyApp proxy.AppConnConsensus
// events
eventBus types.BlockEventPublisher
// update these with block results after commit
mempool Mempool
evpool EvidencePool
logger log.Logger
}
// NewBlockExecutor returns a new BlockExecutor with a NopEventBus.
// Call SetEventBus to provide one.
func NewBlockExecutor(db dbm.DB, logger log.Logger, proxyApp proxy.AppConnConsensus,
mempool Mempool, evpool EvidencePool) *BlockExecutor {
return &BlockExecutor{
db: db,
proxyApp: proxyApp,
eventBus: types.NopEventBus{},
mempool: mempool,
evpool: evpool,
logger: logger,
}
}
// SetEventBus - sets the event bus for publishing block related events.
// If not called, it defaults to types.NopEventBus.
func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) {
blockExec.eventBus = eventBus
}
// ValidateBlock validates the given block against the given state.
// If the block is invalid, it returns an error.
// Validation does not mutate state, but does require historical information from the stateDB,
// ie. to verify evidence from a validator at an old height.
func (blockExec *BlockExecutor) ValidateBlock(state State, block *types.Block) error {
return validateBlock(blockExec.db, state, block)
}
// ApplyBlock validates the block against the state, executes it against the app,
// fires the relevant events, commits the app, and saves the new state and responses.
// It's the only function that needs to be called
// from outside this package to process and commit an entire block.
// It takes a blockID to avoid recomputing the parts hash.
func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, block *types.Block) (State, error) {
if err := blockExec.ValidateBlock(state, block); err != nil {
return state, ErrInvalidBlock(err)
}
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block, state.LastValidators, blockExec.db)
if err != nil {
return state, ErrProxyAppConn(err)
}
fail.Fail() // XXX
// save the results before we commit
saveABCIResponses(blockExec.db, block.Height, abciResponses)
fail.Fail() // XXX
// update the state with the block and responses
state, err = updateState(state, blockID, block.Header, abciResponses)
if err != nil {
return state, fmt.Errorf("Commit failed for application: %v", err)
}
// lock mempool, commit app state, update mempoool
appHash, err := blockExec.Commit(block)
if err != nil {
return state, fmt.Errorf("Commit failed for application: %v", err)
}
// Update evpool with the block and state.
blockExec.evpool.Update(block, state)
fail.Fail() // XXX
// update the app hash and save the state
state.AppHash = appHash
SaveState(blockExec.db, state)
fail.Fail() // XXX
// events are fired after everything else
// NOTE: if we crash between Commit and Save, events wont be fired during replay
fireEvents(blockExec.logger, blockExec.eventBus, block, abciResponses)
return state, nil
}
// Commit locks the mempool, runs the ABCI Commit message, and updates the mempool.
// It returns the result of calling abci.Commit (the AppHash), and an error.
// The Mempool must be locked during commit and update because state is typically reset on Commit and old txs must be replayed
// against committed state before new txs are run in the mempool, lest they be invalid.
func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) {
blockExec.mempool.Lock()
defer blockExec.mempool.Unlock()
// while mempool is Locked, flush to ensure all async requests have completed
// in the ABCI app before Commit.
err := blockExec.mempool.FlushAppConn()
if err != nil {
blockExec.logger.Error("Client error during mempool.FlushAppConn", "err", err)
return nil, err
}
// Commit block, get hash back
res, err := blockExec.proxyApp.CommitSync()
if err != nil {
blockExec.logger.Error("Client error during proxyAppConn.CommitSync", "err", err)
return nil, err
}
// ResponseCommit has no error code - just data
blockExec.logger.Info("Committed state",
"height", block.Height,
"txs", block.NumTxs,
"appHash", fmt.Sprintf("%X", res.Data))
// Update mempool.
if err := blockExec.mempool.Update(block.Height, block.Txs); err != nil {
return nil, err
}
return res.Data, nil
}
//---------------------------------------------------------
// Helper functions for executing blocks and updating state
// Executes block's transactions on proxyAppConn.
// Returns a list of transaction results and updates to the validator set
func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
block *types.Block, lastValSet *types.ValidatorSet, stateDB dbm.DB) (*ABCIResponses, error) {
var validTxs, invalidTxs = 0, 0
txIndex := 0
abciResponses := NewABCIResponses(block)
// Execute transactions and get hash
proxyCb := func(req *abci.Request, res *abci.Response) {
switch r := res.Value.(type) {
case *abci.Response_DeliverTx:
// TODO: make use of res.Log
// TODO: make use of this info
// Blocks may include invalid txs.
txRes := r.DeliverTx
if txRes.Code == abci.CodeTypeOK {
validTxs++
} else {
logger.Debug("Invalid tx", "code", txRes.Code, "log", txRes.Log)
invalidTxs++
}
abciResponses.DeliverTx[txIndex] = txRes
txIndex++
}
}
proxyAppConn.SetResponseCallback(proxyCb)
signVals, byzVals := getBeginBlockValidatorInfo(block, lastValSet, stateDB)
// Begin block
_, err := proxyAppConn.BeginBlockSync(abci.RequestBeginBlock{
Hash: block.Hash(),
Header: types.TM2PB.Header(block.Header),
Validators: signVals,
ByzantineValidators: byzVals,
})
if err != nil {
logger.Error("Error in proxyAppConn.BeginBlock", "err", err)
return nil, err
}
// Run txs of block
for _, tx := range block.Txs {
proxyAppConn.DeliverTxAsync(tx)
if err := proxyAppConn.Error(); err != nil {
return nil, err
}
}
// End block
abciResponses.EndBlock, err = proxyAppConn.EndBlockSync(abci.RequestEndBlock{block.Height})
if err != nil {
logger.Error("Error in proxyAppConn.EndBlock", "err", err)
return nil, err
}
logger.Info("Executed block", "height", block.Height, "validTxs", validTxs, "invalidTxs", invalidTxs)
valUpdates := abciResponses.EndBlock.ValidatorUpdates
if len(valUpdates) > 0 {
logger.Info("Updates to validators", "updates", abci.ValidatorsString(valUpdates))
}
return abciResponses, nil
}
func getBeginBlockValidatorInfo(block *types.Block, lastValSet *types.ValidatorSet, stateDB dbm.DB) ([]abci.SigningValidator, []abci.Evidence) {
// Sanity check that commit length matches validator set size -
// only applies after first block
if block.Height > 1 {
precommitLen := len(block.LastCommit.Precommits)
valSetLen := len(lastValSet.Validators)
if precommitLen != valSetLen {
// sanity check
panic(fmt.Sprintf("precommit length (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v",
precommitLen, valSetLen, block.Height, block.LastCommit.Precommits, lastValSet.Validators))
}
}
// determine which validators did not sign last block.
signVals := make([]abci.SigningValidator, len(lastValSet.Validators))
for i, val := range lastValSet.Validators {
var vote *types.Vote
if i < len(block.LastCommit.Precommits) {
vote = block.LastCommit.Precommits[i]
}
val := abci.SigningValidator{
Validator: types.TM2PB.Validator(val),
SignedLastBlock: vote != nil,
}
signVals[i] = val
}
byzVals := make([]abci.Evidence, len(block.Evidence.Evidence))
for i, ev := range block.Evidence.Evidence {
// We need the validator set. We already did this in validateBlock.
// TODO: Should we instead cache the valset in the evidence itself and add
// `SetValidatorSet()` and `ToABCI` methods ?
valset, err := LoadValidators(stateDB, ev.Height())
if err != nil {
panic(err) // shouldn't happen
}
byzVals[i] = types.TM2PB.Evidence(ev, valset, block.Time)
}
return signVals, byzVals
}
// If more or equal than 1/3 of total voting power changed in one block, then
// a light client could never prove the transition externally. See
// ./lite/doc.go for details on how a light client tracks validators.
func updateValidators(currentSet *types.ValidatorSet, abciUpdates []abci.Validator) error {
updates, err := types.PB2TM.Validators(abciUpdates)
if err != nil {
return err
}
// these are tendermint types now
for _, valUpdate := range updates {
address := valUpdate.Address
_, val := currentSet.GetByAddress(address)
if val == nil {
// add val
added := currentSet.Add(valUpdate)
if !added {
return fmt.Errorf("Failed to add new validator %v", valUpdate)
}
} else if valUpdate.VotingPower == 0 {
// remove val
_, removed := currentSet.Remove(address)
if !removed {
return fmt.Errorf("Failed to remove validator %X", address)
}
} else {
// update val
updated := currentSet.Update(valUpdate)
if !updated {
return fmt.Errorf("Failed to update validator %X to %v", address, valUpdate)
}
}
}
return nil
}
// updateState returns a new State updated according to the header and responses.
func updateState(state State, blockID types.BlockID, header *types.Header,
abciResponses *ABCIResponses) (State, error) {
// copy the valset so we can apply changes from EndBlock
// and update s.LastValidators and s.Validators
prevValSet := state.Validators.Copy()
nextValSet := prevValSet.Copy()
// update the validator set with the latest abciResponses
lastHeightValsChanged := state.LastHeightValidatorsChanged
if len(abciResponses.EndBlock.ValidatorUpdates) > 0 {
err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates)
if err != nil {
return state, fmt.Errorf("Error changing validator set: %v", err)
}
// change results from this height but only applies to the next height
lastHeightValsChanged = header.Height + 1
}
// Update validator accums and set state variables
nextValSet.IncrementAccum(1)
// update the params with the latest abciResponses
nextParams := state.ConsensusParams
lastHeightParamsChanged := state.LastHeightConsensusParamsChanged
if abciResponses.EndBlock.ConsensusParamUpdates != nil {
// NOTE: must not mutate s.ConsensusParams
nextParams = state.ConsensusParams.Update(abciResponses.EndBlock.ConsensusParamUpdates)
err := nextParams.Validate()
if err != nil {
return state, fmt.Errorf("Error updating consensus params: %v", err)
}
// change results from this height but only applies to the next height
lastHeightParamsChanged = header.Height + 1
}
// NOTE: the AppHash has not been populated.
// It will be filled on state.Save.
return State{
ChainID: state.ChainID,
LastBlockHeight: header.Height,
LastBlockTotalTx: state.LastBlockTotalTx + header.NumTxs,
LastBlockID: blockID,
LastBlockTime: header.Time,
Validators: nextValSet,
LastValidators: state.Validators.Copy(),
LastHeightValidatorsChanged: lastHeightValsChanged,
ConsensusParams: nextParams,
LastHeightConsensusParamsChanged: lastHeightParamsChanged,
LastResultsHash: abciResponses.ResultsHash(),
AppHash: nil,
}, nil
}
// Fire NewBlock, NewBlockHeader.
// Fire TxEvent for every tx.
// NOTE: if Tendermint crashes before commit, some or all of these events may be published again.
func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *types.Block, abciResponses *ABCIResponses) {
eventBus.PublishEventNewBlock(types.EventDataNewBlock{block})
eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{block.Header})
for i, tx := range block.Data.Txs {
eventBus.PublishEventTx(types.EventDataTx{types.TxResult{
Height: block.Height,
Index: uint32(i),
Tx: tx,
Result: *(abciResponses.DeliverTx[i]),
}})
}
}
//----------------------------------------------------------------------------------------------------
// Execute block without state. TODO: eliminate
// ExecCommitBlock executes and commits a block on the proxyApp without validating or mutating the state.
// It returns the application root hash (result of abci.Commit).
func ExecCommitBlock(appConnConsensus proxy.AppConnConsensus, block *types.Block,
logger log.Logger, lastValSet *types.ValidatorSet, stateDB dbm.DB) ([]byte, error) {
_, err := execBlockOnProxyApp(logger, appConnConsensus, block, lastValSet, stateDB)
if err != nil {
logger.Error("Error executing block on proxy app", "height", block.Height, "err", err)
return nil, err
}
// Commit block, get hash back
res, err := appConnConsensus.CommitSync()
if err != nil {
logger.Error("Client error during proxyAppConn.CommitSync", "err", res)
return nil, err
}
// ResponseCommit has no error or log, just data
return res.Data, nil
}
+232
View File
@@ -0,0 +1,232 @@
package state
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/abci/example/kvstore"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/tendermint/proxy"
"github.com/tendermint/tendermint/types"
)
var (
chainID = "execution_chain"
testPartSize = 65536
nTxsPerBlock = 10
)
func TestApplyBlock(t *testing.T) {
cc := proxy.NewLocalClientCreator(kvstore.NewKVStoreApplication())
proxyApp := proxy.NewAppConns(cc, nil)
err := proxyApp.Start()
require.Nil(t, err)
defer proxyApp.Stop()
state, stateDB := state(1, 1)
blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(),
MockMempool{}, MockEvidencePool{})
block := makeBlock(state, 1)
blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()}
state, err = blockExec.ApplyBlock(state, blockID, block)
require.Nil(t, err)
// TODO check state and mempool
}
// TestBeginBlockValidators ensures we send absent validators list.
func TestBeginBlockValidators(t *testing.T) {
app := &testApp{}
cc := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(cc, nil)
err := proxyApp.Start()
require.Nil(t, err)
defer proxyApp.Stop()
state, stateDB := state(2, 2)
prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{}
prevBlockID := types.BlockID{prevHash, prevParts}
now := time.Now().UTC()
vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit}
vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now}
testCases := []struct {
desc string
lastCommitPrecommits []*types.Vote
expectedAbsentValidators []int
}{
{"none absent", []*types.Vote{vote0, vote1}, []int{}},
{"one absent", []*types.Vote{vote0, nil}, []int{1}},
{"multiple absent", []*types.Vote{nil, nil}, []int{0, 1}},
}
for _, tc := range testCases {
lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: tc.lastCommitPrecommits}
// block for height 2
block, _ := state.MakeBlock(2, makeTxs(2), lastCommit)
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), state.Validators, stateDB)
require.Nil(t, err, tc.desc)
// -> app receives a list of validators with a bool indicating if they signed
ctr := 0
for i, v := range app.Validators {
if ctr < len(tc.expectedAbsentValidators) &&
tc.expectedAbsentValidators[ctr] == i {
assert.False(t, v.SignedLastBlock)
ctr++
} else {
assert.True(t, v.SignedLastBlock)
}
}
}
}
// TestBeginBlockByzantineValidators ensures we send byzantine validators list.
func TestBeginBlockByzantineValidators(t *testing.T) {
app := &testApp{}
cc := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(cc, nil)
err := proxyApp.Start()
require.Nil(t, err)
defer proxyApp.Stop()
state, stateDB := state(2, 12)
prevHash := state.LastBlockID.Hash
prevParts := types.PartSetHeader{}
prevBlockID := types.BlockID{prevHash, prevParts}
height1, idx1, val1 := int64(8), 0, state.Validators.Validators[0].Address
height2, idx2, val2 := int64(3), 1, state.Validators.Validators[1].Address
ev1 := types.NewMockGoodEvidence(height1, idx1, val1)
ev2 := types.NewMockGoodEvidence(height2, idx2, val2)
now := time.Now()
valSet := state.Validators
testCases := []struct {
desc string
evidence []types.Evidence
expectedByzantineValidators []abci.Evidence
}{
{"none byzantine", []types.Evidence{}, []abci.Evidence{}},
{"one byzantine", []types.Evidence{ev1}, []abci.Evidence{types.TM2PB.Evidence(ev1, valSet, now)}},
{"multiple byzantine", []types.Evidence{ev1, ev2}, []abci.Evidence{
types.TM2PB.Evidence(ev1, valSet, now),
types.TM2PB.Evidence(ev2, valSet, now)}},
}
vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit}
vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now}
votes := []*types.Vote{vote0, vote1}
lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: votes}
for _, tc := range testCases {
block, _ := state.MakeBlock(10, makeTxs(2), lastCommit)
block.Time = now
block.Evidence.Evidence = tc.evidence
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), state.Validators, stateDB)
require.Nil(t, err, tc.desc)
// -> app must receive an index of the byzantine validator
assert.Equal(t, tc.expectedByzantineValidators, app.ByzantineValidators, tc.desc)
}
}
//----------------------------------------------------------------------------
// make some bogus txs
func makeTxs(height int64) (txs []types.Tx) {
for i := 0; i < nTxsPerBlock; i++ {
txs = append(txs, types.Tx([]byte{byte(height), byte(i)}))
}
return txs
}
func state(nVals, height int) (State, dbm.DB) {
vals := make([]types.GenesisValidator, nVals)
for i := 0; i < nVals; i++ {
secret := []byte(fmt.Sprintf("test%d", i))
pk := crypto.GenPrivKeyEd25519FromSecret(secret)
vals[i] = types.GenesisValidator{
pk.PubKey(), 1000, fmt.Sprintf("test%d", i),
}
}
s, _ := MakeGenesisState(&types.GenesisDoc{
ChainID: chainID,
Validators: vals,
AppHash: nil,
})
// save validators to db for 2 heights
stateDB := dbm.NewMemDB()
SaveState(stateDB, s)
for i := 1; i < height; i++ {
s.LastBlockHeight += 1
SaveState(stateDB, s)
}
return s, stateDB
}
func makeBlock(state State, height int64) *types.Block {
block, _ := state.MakeBlock(height, makeTxs(state.LastBlockHeight), new(types.Commit))
return block
}
//----------------------------------------------------------------------------
var _ abci.Application = (*testApp)(nil)
type testApp struct {
abci.BaseApplication
Validators []abci.SigningValidator
ByzantineValidators []abci.Evidence
}
func NewKVStoreApplication() *testApp {
return &testApp{}
}
func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) {
return abci.ResponseInfo{}
}
func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock {
app.Validators = req.Validators
app.ByzantineValidators = req.ByzantineValidators
return abci.ResponseBeginBlock{}
}
func (app *testApp) DeliverTx(tx []byte) abci.ResponseDeliverTx {
return abci.ResponseDeliverTx{Tags: []cmn.KVPair{}}
}
func (app *testApp) CheckTx(tx []byte) abci.ResponseCheckTx {
return abci.ResponseCheckTx{}
}
func (app *testApp) Commit() abci.ResponseCommit {
return abci.ResponseCommit{}
}
func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
return
}
+86
View File
@@ -0,0 +1,86 @@
package state
import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tendermint/types"
)
//------------------------------------------------------
// blockchain services types
// NOTE: Interfaces used by RPC must be thread safe!
//------------------------------------------------------
//------------------------------------------------------
// mempool
// Mempool defines the mempool interface as used by the ConsensusState.
// Updates to the mempool need to be synchronized with committing a block
// so apps can reset their transient state on Commit
type Mempool interface {
Lock()
Unlock()
Size() int
CheckTx(types.Tx, func(*abci.Response)) error
Reap(int) types.Txs
Update(height int64, txs types.Txs) error
Flush()
FlushAppConn() error
TxsAvailable() <-chan int64
EnableTxsAvailable()
}
// MockMempool is an empty implementation of a Mempool, useful for testing.
type MockMempool struct {
}
func (m MockMempool) Lock() {}
func (m MockMempool) Unlock() {}
func (m MockMempool) Size() int { return 0 }
func (m MockMempool) CheckTx(tx types.Tx, cb func(*abci.Response)) error { return nil }
func (m MockMempool) Reap(n int) types.Txs { return types.Txs{} }
func (m MockMempool) Update(height int64, txs types.Txs) error { return nil }
func (m MockMempool) Flush() {}
func (m MockMempool) FlushAppConn() error { return nil }
func (m MockMempool) TxsAvailable() <-chan int64 { return make(chan int64) }
func (m MockMempool) EnableTxsAvailable() {}
//------------------------------------------------------
// blockstore
// BlockStoreRPC is the block store interface used by the RPC.
type BlockStoreRPC interface {
Height() int64
LoadBlockMeta(height int64) *types.BlockMeta
LoadBlock(height int64) *types.Block
LoadBlockPart(height int64, index int) *types.Part
LoadBlockCommit(height int64) *types.Commit
LoadSeenCommit(height int64) *types.Commit
}
// BlockStore defines the BlockStore interface used by the ConsensusState.
type BlockStore interface {
BlockStoreRPC
SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit)
}
//-----------------------------------------------------------------------------------------------------
// evidence pool
// EvidencePool defines the EvidencePool interface used by the ConsensusState.
type EvidencePool interface {
PendingEvidence() []types.Evidence
AddEvidence(types.Evidence) error
Update(*types.Block, State)
}
// MockMempool is an empty implementation of a Mempool, useful for testing.
type MockEvidencePool struct {
}
func (m MockEvidencePool) PendingEvidence() []types.Evidence { return nil }
func (m MockEvidencePool) AddEvidence(types.Evidence) error { return nil }
func (m MockEvidencePool) Update(*types.Block, State) {}
+187
View File
@@ -0,0 +1,187 @@
package state
import (
"bytes"
"fmt"
"io/ioutil"
"time"
"github.com/tendermint/tendermint/types"
)
// database keys
var (
stateKey = []byte("stateKey")
)
//-----------------------------------------------------------------------------
// State is a short description of the latest committed block of the Tendermint consensus.
// It keeps all information necessary to validate new blocks,
// including the last validator set and the consensus params.
// All fields are exposed so the struct can be easily serialized,
// but none of them should be mutated directly.
// Instead, use state.Copy() or state.NextState(...).
// NOTE: not goroutine-safe.
type State struct {
// Immutable
ChainID string
// LastBlockHeight=0 at genesis (ie. block(H=0) does not exist)
LastBlockHeight int64
LastBlockTotalTx int64
LastBlockID types.BlockID
LastBlockTime time.Time
// LastValidators is used to validate block.LastCommit.
// Validators are persisted to the database separately every time they change,
// so we can query for historical validator sets.
// Note that if s.LastBlockHeight causes a valset change,
// we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1
Validators *types.ValidatorSet
LastValidators *types.ValidatorSet
LastHeightValidatorsChanged int64
// Consensus parameters used for validating blocks.
// Changes returned by EndBlock and updated after Commit.
ConsensusParams types.ConsensusParams
LastHeightConsensusParamsChanged int64
// Merkle root of the results from executing prev block
LastResultsHash []byte
// The latest AppHash we've received from calling abci.Commit()
AppHash []byte
}
// Copy makes a copy of the State for mutating.
func (state State) Copy() State {
return State{
ChainID: state.ChainID,
LastBlockHeight: state.LastBlockHeight,
LastBlockTotalTx: state.LastBlockTotalTx,
LastBlockID: state.LastBlockID,
LastBlockTime: state.LastBlockTime,
Validators: state.Validators.Copy(),
LastValidators: state.LastValidators.Copy(),
LastHeightValidatorsChanged: state.LastHeightValidatorsChanged,
ConsensusParams: state.ConsensusParams,
LastHeightConsensusParamsChanged: state.LastHeightConsensusParamsChanged,
AppHash: state.AppHash,
LastResultsHash: state.LastResultsHash,
}
}
// Equals returns true if the States are identical.
func (state State) Equals(state2 State) bool {
sbz, s2bz := state.Bytes(), state2.Bytes()
return bytes.Equal(sbz, s2bz)
}
// Bytes serializes the State using go-amino.
func (state State) Bytes() []byte {
return cdc.MustMarshalBinaryBare(state)
}
// IsEmpty returns true if the State is equal to the empty State.
func (state State) IsEmpty() bool {
return state.Validators == nil // XXX can't compare to Empty
}
// GetValidators returns the last and current validator sets.
func (state State) GetValidators() (last *types.ValidatorSet, current *types.ValidatorSet) {
return state.LastValidators, state.Validators
}
//------------------------------------------------------------------------
// Create a block from the latest state
// MakeBlock builds a block with the given txs and commit from the current state.
func (state State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (*types.Block, *types.PartSet) {
// build base block
block := types.MakeBlock(height, txs, commit)
// fill header with state data
block.ChainID = state.ChainID
block.TotalTxs = state.LastBlockTotalTx + block.NumTxs
block.LastBlockID = state.LastBlockID
block.ValidatorsHash = state.Validators.Hash()
block.AppHash = state.AppHash
block.ConsensusHash = state.ConsensusParams.Hash()
block.LastResultsHash = state.LastResultsHash
return block, block.MakePartSet(state.ConsensusParams.BlockGossip.BlockPartSizeBytes)
}
//------------------------------------------------------------------------
// Genesis
// MakeGenesisStateFromFile reads and unmarshals state from the given
// file.
//
// Used during replay and in tests.
func MakeGenesisStateFromFile(genDocFile string) (State, error) {
genDoc, err := MakeGenesisDocFromFile(genDocFile)
if err != nil {
return State{}, err
}
return MakeGenesisState(genDoc)
}
// MakeGenesisDocFromFile reads and unmarshals genesis doc from the given file.
func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) {
genDocJSON, err := ioutil.ReadFile(genDocFile)
if err != nil {
return nil, fmt.Errorf("Couldn't read GenesisDoc file: %v", err)
}
genDoc, err := types.GenesisDocFromJSON(genDocJSON)
if err != nil {
return nil, fmt.Errorf("Error reading GenesisDoc: %v", err)
}
return genDoc, nil
}
// MakeGenesisState creates state from types.GenesisDoc.
func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) {
err := genDoc.ValidateAndComplete()
if err != nil {
return State{}, fmt.Errorf("Error in genesis file: %v", err)
}
// Make validators slice
validators := make([]*types.Validator, len(genDoc.Validators))
for i, val := range genDoc.Validators {
pubKey := val.PubKey
address := pubKey.Address()
// Make validator
validators[i] = &types.Validator{
Address: address,
PubKey: pubKey,
VotingPower: val.Power,
}
}
return State{
ChainID: genDoc.ChainID,
LastBlockHeight: 0,
LastBlockID: types.BlockID{},
LastBlockTime: genDoc.GenesisTime,
Validators: types.NewValidatorSet(validators),
LastValidators: types.NewValidatorSet(nil),
LastHeightValidatorsChanged: 1,
ConsensusParams: *genDoc.ConsensusParams,
LastHeightConsensusParamsChanged: 1,
AppHash: genDoc.AppHash,
}, nil
}
+489
View File
@@ -0,0 +1,489 @@
package state
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/types"
)
// setupTestCase does setup common to all test cases
func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, State) {
config := cfg.ResetTestRoot("state_")
dbType := dbm.DBBackendType(config.DBBackend)
stateDB := dbm.NewDB("state", dbType, config.DBDir())
state, err := LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile())
assert.NoError(t, err, "expected no error on LoadStateFromDBOrGenesisFile")
tearDown := func(t *testing.T) {}
return tearDown, stateDB, state
}
// TestStateCopy tests the correct copying behaviour of State.
func TestStateCopy(t *testing.T) {
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
stateCopy := state.Copy()
assert.True(state.Equals(stateCopy),
cmn.Fmt("expected state and its copy to be identical.\ngot: %v\nexpected: %v\n",
stateCopy, state))
stateCopy.LastBlockHeight++
assert.False(state.Equals(stateCopy), cmn.Fmt(`expected states to be different. got same
%v`, state))
}
// TestStateSaveLoad tests saving and loading State from a db.
func TestStateSaveLoad(t *testing.T) {
tearDown, stateDB, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
state.LastBlockHeight++
SaveState(stateDB, state)
loadedState := LoadState(stateDB)
assert.True(state.Equals(loadedState),
cmn.Fmt("expected state and its copy to be identical.\ngot: %v\nexpected: %v\n",
loadedState, state))
}
// TestABCIResponsesSaveLoad tests saving and loading ABCIResponses.
func TestABCIResponsesSaveLoad1(t *testing.T) {
tearDown, stateDB, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
state.LastBlockHeight++
// build mock responses
block := makeBlock(state, 2)
abciResponses := NewABCIResponses(block)
abciResponses.DeliverTx[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Tags: nil}
abciResponses.DeliverTx[1] = &abci.ResponseDeliverTx{Data: []byte("bar"), Log: "ok", Tags: nil}
abciResponses.EndBlock = &abci.ResponseEndBlock{ValidatorUpdates: []abci.Validator{
types.TM2PB.ValidatorFromPubKeyAndPower(crypto.GenPrivKeyEd25519().PubKey(), 10),
}}
saveABCIResponses(stateDB, block.Height, abciResponses)
loadedABCIResponses, err := LoadABCIResponses(stateDB, block.Height)
assert.Nil(err)
assert.Equal(abciResponses, loadedABCIResponses,
cmn.Fmt("ABCIResponses don't match:\ngot: %v\nexpected: %v\n",
loadedABCIResponses, abciResponses))
}
// TestResultsSaveLoad tests saving and loading abci results.
func TestABCIResponsesSaveLoad2(t *testing.T) {
tearDown, stateDB, _ := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
cases := [...]struct {
// height is implied index+2
// as block 1 is created from genesis
added []*abci.ResponseDeliverTx
expected types.ABCIResults
}{
0: {
nil,
nil,
},
1: {
[]*abci.ResponseDeliverTx{
{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
},
types.ABCIResults{
{32, []byte("Hello")},
}},
2: {
[]*abci.ResponseDeliverTx{
{Code: 383},
{Data: []byte("Gotcha!"),
Tags: []cmn.KVPair{
cmn.KVPair{[]byte("a"), []byte("1")},
cmn.KVPair{[]byte("build"), []byte("stuff")},
}},
},
types.ABCIResults{
{383, nil},
{0, []byte("Gotcha!")},
}},
3: {
nil,
nil,
},
}
// query all before, should return error
for i := range cases {
h := int64(i + 1)
res, err := LoadABCIResponses(stateDB, h)
assert.Error(err, "%d: %#v", i, res)
}
// add all cases
for i, tc := range cases {
h := int64(i + 1) // last block height, one below what we save
responses := &ABCIResponses{
DeliverTx: tc.added,
EndBlock: &abci.ResponseEndBlock{},
}
saveABCIResponses(stateDB, h, responses)
}
// query all before, should return expected value
for i, tc := range cases {
h := int64(i + 1)
res, err := LoadABCIResponses(stateDB, h)
assert.NoError(err, "%d", i)
assert.Equal(tc.expected.Hash(), res.ResultsHash(), "%d", i)
}
}
// TestValidatorSimpleSaveLoad tests saving and loading validators.
func TestValidatorSimpleSaveLoad(t *testing.T) {
tearDown, stateDB, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
assert := assert.New(t)
// can't load anything for height 0
v, err := LoadValidators(stateDB, 0)
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0")
// should be able to load for height 1
v, err = LoadValidators(stateDB, 1)
assert.Nil(err, "expected no err at height 1")
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
// increment height, save; should be able to load for next height
state.LastBlockHeight++
nextHeight := state.LastBlockHeight + 1
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
v, err = LoadValidators(stateDB, nextHeight)
assert.Nil(err, "expected no err")
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
// increment height, save; should be able to load for next height
state.LastBlockHeight += 10
nextHeight = state.LastBlockHeight + 1
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
v, err = LoadValidators(stateDB, nextHeight)
assert.Nil(err, "expected no err")
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
// should be able to load for next next height
_, err = LoadValidators(stateDB, state.LastBlockHeight+2)
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height")
}
// TestValidatorChangesSaveLoad tests saving and loading a validator set with changes.
func TestOneValidatorChangesSaveLoad(t *testing.T) {
tearDown, stateDB, state := setupTestCase(t)
defer tearDown(t)
// change vals at these heights
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
N := len(changeHeights)
// build the validator history by running updateState
// with the right validator set for each height
highestHeight := changeHeights[N-1] + 5
changeIndex := 0
_, val := state.Validators.GetByIndex(0)
power := val.VotingPower
var err error
for i := int64(1); i < highestHeight; i++ {
// when we get to a change height,
// use the next pubkey
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] {
changeIndex++
power++
}
header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, i, power)
state, err = updateState(state, blockID, header, responses)
assert.Nil(t, err)
nextHeight := state.LastBlockHeight + 1
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
}
// on each change height, increment the power by one.
testCases := make([]int64, highestHeight)
changeIndex = 0
power = val.VotingPower
for i := int64(1); i < highestHeight+1; i++ {
// we we get to the height after a change height
// use the next pubkey (note our counter starts at 0 this time)
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex]+1 {
changeIndex++
power++
}
testCases[i-1] = power
}
for i, power := range testCases {
v, err := LoadValidators(stateDB, int64(i+1))
assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", i))
assert.Equal(t, v.Size(), 1, "validator set size is greater than 1: %d", v.Size())
_, val := v.GetByIndex(0)
assert.Equal(t, val.VotingPower, power, fmt.Sprintf(`unexpected powerat
height %d`, i))
}
}
// TestValidatorChangesSaveLoad tests saving and loading a validator set with
// changes.
func TestManyValidatorChangesSaveLoad(t *testing.T) {
const valSetSize = 7
tearDown, stateDB, state := setupTestCase(t)
state.Validators = genValSet(valSetSize)
SaveState(stateDB, state)
defer tearDown(t)
const height = 1
pubkey := crypto.GenPrivKeyEd25519().PubKey()
// swap the first validator with a new one ^^^ (validator set size stays the same)
header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey)
var err error
state, err = updateState(state, blockID, header, responses)
require.Nil(t, err)
nextHeight := state.LastBlockHeight + 1
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
v, err := LoadValidators(stateDB, height+1)
assert.Nil(t, err)
assert.Equal(t, valSetSize, v.Size())
index, val := v.GetByAddress(pubkey.Address())
assert.NotNil(t, val)
if index < 0 {
t.Fatal("expected to find newly added validator")
}
}
func genValSet(size int) *types.ValidatorSet {
vals := make([]*types.Validator, size)
for i := 0; i < size; i++ {
vals[i] = types.NewValidator(crypto.GenPrivKeyEd25519().PubKey(), 10)
}
return types.NewValidatorSet(vals)
}
// TestConsensusParamsChangesSaveLoad tests saving and loading consensus params
// with changes.
func TestConsensusParamsChangesSaveLoad(t *testing.T) {
tearDown, stateDB, state := setupTestCase(t)
defer tearDown(t)
// change vals at these heights
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
N := len(changeHeights)
// each valset is just one validator
// create list of them
params := make([]types.ConsensusParams, N+1)
params[0] = state.ConsensusParams
for i := 1; i < N+1; i++ {
params[i] = *types.DefaultConsensusParams()
params[i].BlockSize.MaxBytes += i
}
// build the params history by running updateState
// with the right params set for each height
highestHeight := changeHeights[N-1] + 5
changeIndex := 0
cp := params[changeIndex]
var err error
for i := int64(1); i < highestHeight; i++ {
// when we get to a change height,
// use the next params
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] {
changeIndex++
cp = params[changeIndex]
}
header, blockID, responses := makeHeaderPartsResponsesParams(state, i, cp)
state, err = updateState(state, blockID, header, responses)
require.Nil(t, err)
nextHeight := state.LastBlockHeight + 1
saveConsensusParamsInfo(stateDB, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams)
}
// make all the test cases by using the same params until after the change
testCases := make([]paramsChangeTestCase, highestHeight)
changeIndex = 0
cp = params[changeIndex]
for i := int64(1); i < highestHeight+1; i++ {
// we we get to the height after a change height
// use the next pubkey (note our counter starts at 0 this time)
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex]+1 {
changeIndex++
cp = params[changeIndex]
}
testCases[i-1] = paramsChangeTestCase{i, cp}
}
for _, testCase := range testCases {
p, err := LoadConsensusParams(stateDB, testCase.height)
assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", testCase.height))
assert.Equal(t, testCase.params, p, fmt.Sprintf(`unexpected consensus params at
height %d`, testCase.height))
}
}
func makeParams(blockBytes, blockTx, blockGas, txBytes,
txGas, partSize int) types.ConsensusParams {
return types.ConsensusParams{
BlockSize: types.BlockSize{
MaxBytes: blockBytes,
MaxTxs: blockTx,
MaxGas: int64(blockGas),
},
TxSize: types.TxSize{
MaxBytes: txBytes,
MaxGas: int64(txGas),
},
BlockGossip: types.BlockGossip{
BlockPartSizeBytes: partSize,
},
}
}
func pk() []byte {
return crypto.GenPrivKeyEd25519().PubKey().Bytes()
}
func TestApplyUpdates(t *testing.T) {
initParams := makeParams(1, 2, 3, 4, 5, 6)
cases := [...]struct {
init types.ConsensusParams
updates abci.ConsensusParams
expected types.ConsensusParams
}{
0: {initParams, abci.ConsensusParams{}, initParams},
1: {initParams, abci.ConsensusParams{}, initParams},
2: {initParams,
abci.ConsensusParams{
TxSize: &abci.TxSize{
MaxBytes: 123,
},
},
makeParams(1, 2, 3, 123, 5, 6)},
3: {initParams,
abci.ConsensusParams{
BlockSize: &abci.BlockSize{
MaxTxs: 44,
MaxGas: 55,
},
},
makeParams(1, 44, 55, 4, 5, 6)},
4: {initParams,
abci.ConsensusParams{
BlockSize: &abci.BlockSize{
MaxTxs: 789,
},
TxSize: &abci.TxSize{
MaxGas: 888,
},
BlockGossip: &abci.BlockGossip{
BlockPartSizeBytes: 2002,
},
},
makeParams(1, 789, 3, 4, 888, 2002)},
}
for i, tc := range cases {
res := tc.init.Update(&(tc.updates))
assert.Equal(t, tc.expected, res, "case %d", i)
}
}
func makeHeaderPartsResponsesValPubKeyChange(state State, height int64,
pubkey crypto.PubKey) (*types.Header, types.BlockID, *ABCIResponses) {
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
}
// if the pubkey is new, remove the old and add the new
_, val := state.Validators.GetByIndex(0)
if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) {
abciResponses.EndBlock = &abci.ResponseEndBlock{
ValidatorUpdates: []abci.Validator{
types.TM2PB.ValidatorFromPubKeyAndPower(val.PubKey, 0),
types.TM2PB.ValidatorFromPubKeyAndPower(pubkey, 10),
},
}
}
return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses
}
func makeHeaderPartsResponsesValPowerChange(state State, height int64,
power int64) (*types.Header, types.BlockID, *ABCIResponses) {
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
}
// if the pubkey is new, remove the old and add the new
_, val := state.Validators.GetByIndex(0)
if val.VotingPower != power {
abciResponses.EndBlock = &abci.ResponseEndBlock{
ValidatorUpdates: []abci.Validator{
types.TM2PB.ValidatorFromPubKeyAndPower(val.PubKey, power),
},
}
}
return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses
}
func makeHeaderPartsResponsesParams(state State, height int64,
params types.ConsensusParams) (*types.Header, types.BlockID, *ABCIResponses) {
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(&params)},
}
return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses
}
type paramsChangeTestCase struct {
height int64
params types.ConsensusParams
}
func makeHeaderPartsResults(state State, height int64,
results []*abci.ResponseDeliverTx) (*types.Header, types.BlockID, *ABCIResponses) {
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
DeliverTx: results,
EndBlock: &abci.ResponseEndBlock{},
}
return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses
}
+282
View File
@@ -0,0 +1,282 @@
package state
import (
"fmt"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
)
//------------------------------------------------------------------------
func calcValidatorsKey(height int64) []byte {
return []byte(cmn.Fmt("validatorsKey:%v", height))
}
func calcConsensusParamsKey(height int64) []byte {
return []byte(cmn.Fmt("consensusParamsKey:%v", height))
}
func calcABCIResponsesKey(height int64) []byte {
return []byte(cmn.Fmt("abciResponsesKey:%v", height))
}
// LoadStateFromDBOrGenesisFile loads the most recent state from the database,
// or creates a new one from the given genesisFilePath and persists the result
// to the database.
func LoadStateFromDBOrGenesisFile(stateDB dbm.DB, genesisFilePath string) (State, error) {
state := LoadState(stateDB)
if state.IsEmpty() {
var err error
state, err = MakeGenesisStateFromFile(genesisFilePath)
if err != nil {
return state, err
}
SaveState(stateDB, state)
}
return state, nil
}
// LoadStateFromDBOrGenesisDoc loads the most recent state from the database,
// or creates a new one from the given genesisDoc and persists the result
// to the database.
func LoadStateFromDBOrGenesisDoc(stateDB dbm.DB, genesisDoc *types.GenesisDoc) (State, error) {
state := LoadState(stateDB)
if state.IsEmpty() {
var err error
state, err = MakeGenesisState(genesisDoc)
if err != nil {
return state, err
}
SaveState(stateDB, state)
}
return state, nil
}
// LoadState loads the State from the database.
func LoadState(db dbm.DB) State {
return loadState(db, stateKey)
}
func loadState(db dbm.DB, key []byte) (state State) {
buf := db.Get(key)
if len(buf) == 0 {
return state
}
err := cdc.UnmarshalBinaryBare(buf, &state)
if err != nil {
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
cmn.Exit(cmn.Fmt(`LoadState: Data has been corrupted or its spec has changed:
%v\n`, err))
}
// TODO: ensure that buf is completely read.
return state
}
// SaveState persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database.
func SaveState(db dbm.DB, state State) {
saveState(db, state, stateKey)
}
func saveState(db dbm.DB, state State, key []byte) {
nextHeight := state.LastBlockHeight + 1
saveValidatorsInfo(db, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
saveConsensusParamsInfo(db, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams)
db.SetSync(stateKey, state.Bytes())
}
//------------------------------------------------------------------------
// ABCIResponses retains the responses
// of the various ABCI calls during block processing.
// It is persisted to disk for each height before calling Commit.
type ABCIResponses struct {
DeliverTx []*abci.ResponseDeliverTx
EndBlock *abci.ResponseEndBlock
}
// NewABCIResponses returns a new ABCIResponses
func NewABCIResponses(block *types.Block) *ABCIResponses {
resDeliverTxs := make([]*abci.ResponseDeliverTx, block.NumTxs)
if block.NumTxs == 0 {
// This makes Amino encoding/decoding consistent.
resDeliverTxs = nil
}
return &ABCIResponses{
DeliverTx: resDeliverTxs,
}
}
// Bytes serializes the ABCIResponse using go-amino.
func (arz *ABCIResponses) Bytes() []byte {
return cdc.MustMarshalBinaryBare(arz)
}
func (arz *ABCIResponses) ResultsHash() []byte {
results := types.NewResults(arz.DeliverTx)
return results.Hash()
}
// LoadABCIResponses loads the ABCIResponses for the given height from the database.
// This is useful for recovering from crashes where we called app.Commit and before we called
// s.Save(). It can also be used to produce Merkle proofs of the result of txs.
func LoadABCIResponses(db dbm.DB, height int64) (*ABCIResponses, error) {
buf := db.Get(calcABCIResponsesKey(height))
if len(buf) == 0 {
return nil, ErrNoABCIResponsesForHeight{height}
}
abciResponses := new(ABCIResponses)
err := cdc.UnmarshalBinaryBare(buf, abciResponses)
if err != nil {
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
cmn.Exit(cmn.Fmt(`LoadABCIResponses: Data has been corrupted or its spec has
changed: %v\n`, err))
}
// TODO: ensure that buf is completely read.
return abciResponses, nil
}
// SaveABCIResponses persists the ABCIResponses to the database.
// This is useful in case we crash after app.Commit and before s.Save().
// Responses are indexed by height so they can also be loaded later to produce Merkle proofs.
func saveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) {
db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes())
}
//-----------------------------------------------------------------------------
// ValidatorsInfo represents the latest validator set, or the last height it changed
type ValidatorsInfo struct {
ValidatorSet *types.ValidatorSet
LastHeightChanged int64
}
// Bytes serializes the ValidatorsInfo using go-amino.
func (valInfo *ValidatorsInfo) Bytes() []byte {
return cdc.MustMarshalBinaryBare(valInfo)
}
// LoadValidators loads the ValidatorSet for a given height.
// Returns ErrNoValSetForHeight if the validator set can't be found for this height.
func LoadValidators(db dbm.DB, height int64) (*types.ValidatorSet, error) {
valInfo := loadValidatorsInfo(db, height)
if valInfo == nil {
return nil, ErrNoValSetForHeight{height}
}
if valInfo.ValidatorSet == nil {
valInfo2 := loadValidatorsInfo(db, valInfo.LastHeightChanged)
if valInfo2 == nil {
cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at height %d as
last changed from height %d`, valInfo.LastHeightChanged, height))
}
valInfo = valInfo2
}
return valInfo.ValidatorSet, nil
}
func loadValidatorsInfo(db dbm.DB, height int64) *ValidatorsInfo {
buf := db.Get(calcValidatorsKey(height))
if len(buf) == 0 {
return nil
}
v := new(ValidatorsInfo)
err := cdc.UnmarshalBinaryBare(buf, v)
if err != nil {
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
cmn.Exit(cmn.Fmt(`LoadValidators: Data has been corrupted or its spec has changed:
%v\n`, err))
}
// TODO: ensure that buf is completely read.
return v
}
// saveValidatorsInfo persists the validator set for the next block to disk.
// It should be called from s.Save(), right before the state itself is persisted.
// If the validator set did not change after processing the latest block,
// only the last height for which the validators changed is persisted.
func saveValidatorsInfo(db dbm.DB, nextHeight, changeHeight int64, valSet *types.ValidatorSet) {
valInfo := &ValidatorsInfo{
LastHeightChanged: changeHeight,
}
if changeHeight == nextHeight {
valInfo.ValidatorSet = valSet
}
db.SetSync(calcValidatorsKey(nextHeight), valInfo.Bytes())
}
//-----------------------------------------------------------------------------
// ConsensusParamsInfo represents the latest consensus params, or the last height it changed
type ConsensusParamsInfo struct {
ConsensusParams types.ConsensusParams
LastHeightChanged int64
}
// Bytes serializes the ConsensusParamsInfo using go-amino.
func (params ConsensusParamsInfo) Bytes() []byte {
return cdc.MustMarshalBinaryBare(params)
}
// LoadConsensusParams loads the ConsensusParams for a given height.
func LoadConsensusParams(db dbm.DB, height int64) (types.ConsensusParams, error) {
empty := types.ConsensusParams{}
paramsInfo := loadConsensusParamsInfo(db, height)
if paramsInfo == nil {
return empty, ErrNoConsensusParamsForHeight{height}
}
if paramsInfo.ConsensusParams == empty {
paramsInfo = loadConsensusParamsInfo(db, paramsInfo.LastHeightChanged)
if paramsInfo == nil {
cmn.PanicSanity(fmt.Sprintf(`Couldn't find consensus params at height %d as
last changed from height %d`, paramsInfo.LastHeightChanged, height))
}
}
return paramsInfo.ConsensusParams, nil
}
func loadConsensusParamsInfo(db dbm.DB, height int64) *ConsensusParamsInfo {
buf := db.Get(calcConsensusParamsKey(height))
if len(buf) == 0 {
return nil
}
paramsInfo := new(ConsensusParamsInfo)
err := cdc.UnmarshalBinaryBare(buf, paramsInfo)
if err != nil {
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
cmn.Exit(cmn.Fmt(`LoadConsensusParams: Data has been corrupted or its spec has changed:
%v\n`, err))
}
// TODO: ensure that buf is completely read.
return paramsInfo
}
// saveConsensusParamsInfo persists the consensus params for the next block to disk.
// It should be called from s.Save(), right before the state itself is persisted.
// If the consensus params did not change after processing the latest block,
// only the last height for which they changed is persisted.
func saveConsensusParamsInfo(db dbm.DB, nextHeight, changeHeight int64, params types.ConsensusParams) {
paramsInfo := &ConsensusParamsInfo{
LastHeightChanged: changeHeight,
}
if changeHeight == nextHeight {
paramsInfo.ConsensusParams = params
}
db.SetSync(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes())
}
+58
View File
@@ -0,0 +1,58 @@
package txindex
import (
"errors"
"github.com/tendermint/tendermint/libs/pubsub/query"
"github.com/tendermint/tendermint/types"
)
// TxIndexer interface defines methods to index and search transactions.
type TxIndexer interface {
// AddBatch analyzes, indexes and stores a batch of transactions.
AddBatch(b *Batch) error
// Index analyzes, indexes and stores a single transaction.
Index(result *types.TxResult) error
// Get returns the transaction specified by hash or nil if the transaction is not indexed
// or stored.
Get(hash []byte) (*types.TxResult, error)
// Search allows you to query for transactions.
Search(q *query.Query) ([]*types.TxResult, error)
}
//----------------------------------------------------
// Txs are written as a batch
// Batch groups together multiple Index operations to be performed at the same time.
// NOTE: Batch is NOT thread-safe and must not be modified after starting its execution.
type Batch struct {
Ops []*types.TxResult
}
// NewBatch creates a new Batch.
func NewBatch(n int64) *Batch {
return &Batch{
Ops: make([]*types.TxResult, n),
}
}
// Add or update an entry for the given result.Index.
func (b *Batch) Add(result *types.TxResult) error {
b.Ops[result.Index] = result
return nil
}
// Size returns the total number of operations inside the batch.
func (b *Batch) Size() int {
return len(b.Ops)
}
//----------------------------------------------------
// Errors
// ErrorEmptyHash indicates empty hash
var ErrorEmptyHash = errors.New("Transaction hash cannot be empty")
+73
View File
@@ -0,0 +1,73 @@
package txindex
import (
"context"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tendermint/types"
)
const (
subscriber = "IndexerService"
)
// IndexerService connects event bus and transaction indexer together in order
// to index transactions coming from event bus.
type IndexerService struct {
cmn.BaseService
idr TxIndexer
eventBus *types.EventBus
}
// NewIndexerService returns a new service instance.
func NewIndexerService(idr TxIndexer, eventBus *types.EventBus) *IndexerService {
is := &IndexerService{idr: idr, eventBus: eventBus}
is.BaseService = *cmn.NewBaseService(nil, "IndexerService", is)
return is
}
// OnStart implements cmn.Service by subscribing for all transactions
// and indexing them by tags.
func (is *IndexerService) OnStart() error {
blockHeadersCh := make(chan interface{})
if err := is.eventBus.Subscribe(context.Background(), subscriber, types.EventQueryNewBlockHeader, blockHeadersCh); err != nil {
return err
}
txsCh := make(chan interface{})
if err := is.eventBus.Subscribe(context.Background(), subscriber, types.EventQueryTx, txsCh); err != nil {
return err
}
go func() {
for {
e, ok := <-blockHeadersCh
if !ok {
return
}
header := e.(types.EventDataNewBlockHeader).Header
batch := NewBatch(header.NumTxs)
for i := int64(0); i < header.NumTxs; i++ {
e, ok := <-txsCh
if !ok {
is.Logger.Error("Failed to index all transactions due to closed transactions channel", "height", header.Height, "numTxs", header.NumTxs, "numProcessed", i)
return
}
txResult := e.(types.EventDataTx).TxResult
batch.Add(&txResult)
}
is.idr.AddBatch(batch)
is.Logger.Info("Indexed block", "height", header.Height)
}
}()
return nil
}
// OnStop implements cmn.Service by unsubscribing from all transactions.
func (is *IndexerService) OnStop() {
if is.eventBus.IsRunning() {
_ = is.eventBus.UnsubscribeAll(context.Background(), subscriber)
}
}
+437
View File
@@ -0,0 +1,437 @@
package kv
import (
"bytes"
"encoding/hex"
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tendermint/libs/pubsub/query"
"github.com/tendermint/tendermint/state/txindex"
"github.com/tendermint/tendermint/types"
)
const (
tagKeySeparator = "/"
)
var _ txindex.TxIndexer = (*TxIndex)(nil)
// TxIndex is the simplest possible indexer, backed by key-value storage (levelDB).
type TxIndex struct {
store dbm.DB
tagsToIndex []string
indexAllTags bool
}
// NewTxIndex creates new KV indexer.
func NewTxIndex(store dbm.DB, options ...func(*TxIndex)) *TxIndex {
txi := &TxIndex{store: store, tagsToIndex: make([]string, 0), indexAllTags: false}
for _, o := range options {
o(txi)
}
return txi
}
// IndexTags is an option for setting which tags to index.
func IndexTags(tags []string) func(*TxIndex) {
return func(txi *TxIndex) {
txi.tagsToIndex = tags
}
}
// IndexAllTags is an option for indexing all tags.
func IndexAllTags() func(*TxIndex) {
return func(txi *TxIndex) {
txi.indexAllTags = true
}
}
// Get gets transaction from the TxIndex storage and returns it or nil if the
// transaction is not found.
func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) {
if len(hash) == 0 {
return nil, txindex.ErrorEmptyHash
}
rawBytes := txi.store.Get(hash)
if rawBytes == nil {
return nil, nil
}
txResult := new(types.TxResult)
err := cdc.UnmarshalBinaryBare(rawBytes, &txResult)
if err != nil {
return nil, fmt.Errorf("Error reading TxResult: %v", err)
}
return txResult, nil
}
// AddBatch indexes a batch of transactions using the given list of tags.
func (txi *TxIndex) AddBatch(b *txindex.Batch) error {
storeBatch := txi.store.NewBatch()
for _, result := range b.Ops {
hash := result.Tx.Hash()
// index tx by tags
for _, tag := range result.Result.Tags {
if txi.indexAllTags || cmn.StringInSlice(string(tag.Key), txi.tagsToIndex) {
storeBatch.Set(keyForTag(tag, result), hash)
}
}
// index tx by hash
rawBytes, err := cdc.MarshalBinaryBare(result)
if err != nil {
return err
}
storeBatch.Set(hash, rawBytes)
}
storeBatch.Write()
return nil
}
// Index indexes a single transaction using the given list of tags.
func (txi *TxIndex) Index(result *types.TxResult) error {
b := txi.store.NewBatch()
hash := result.Tx.Hash()
// index tx by tags
for _, tag := range result.Result.Tags {
if txi.indexAllTags || cmn.StringInSlice(string(tag.Key), txi.tagsToIndex) {
b.Set(keyForTag(tag, result), hash)
}
}
// index tx by hash
rawBytes, err := cdc.MarshalBinaryBare(result)
if err != nil {
return err
}
b.Set(hash, rawBytes)
b.Write()
return nil
}
// Search performs a search using the given query. It breaks the query into
// conditions (like "tx.height > 5"). For each condition, it queries the DB
// index. One special use cases here: (1) if "tx.hash" is found, it returns tx
// result for it (2) for range queries it is better for the client to provide
// both lower and upper bounds, so we are not performing a full scan. Results
// from querying indexes are then intersected and returned to the caller.
func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) {
var hashes [][]byte
var hashesInitialized bool
// get a list of conditions (like "tx.height > 5")
conditions := q.Conditions()
// if there is a hash condition, return the result immediately
hash, err, ok := lookForHash(conditions)
if err != nil {
return nil, errors.Wrap(err, "error during searching for a hash in the query")
} else if ok {
res, err := txi.Get(hash)
if res == nil {
return []*types.TxResult{}, nil
}
return []*types.TxResult{res}, errors.Wrap(err, "error while retrieving the result")
}
// conditions to skip because they're handled before "everything else"
skipIndexes := make([]int, 0)
// if there is a height condition ("tx.height=3"), extract it for faster lookups
height, heightIndex := lookForHeight(conditions)
if heightIndex >= 0 {
skipIndexes = append(skipIndexes, heightIndex)
}
// extract ranges
// if both upper and lower bounds exist, it's better to get them in order not
// no iterate over kvs that are not within range.
ranges, rangeIndexes := lookForRanges(conditions)
if len(ranges) > 0 {
skipIndexes = append(skipIndexes, rangeIndexes...)
for _, r := range ranges {
if !hashesInitialized {
hashes = txi.matchRange(r, []byte(r.key))
hashesInitialized = true
} else {
hashes = intersect(hashes, txi.matchRange(r, []byte(r.key)))
}
}
}
// for all other conditions
for i, c := range conditions {
if cmn.IntInSlice(i, skipIndexes) {
continue
}
if !hashesInitialized {
hashes = txi.match(c, startKey(c, height))
hashesInitialized = true
} else {
hashes = intersect(hashes, txi.match(c, startKey(c, height)))
}
}
results := make([]*types.TxResult, len(hashes))
i := 0
for _, h := range hashes {
results[i], err = txi.Get(h)
if err != nil {
return nil, errors.Wrapf(err, "failed to get Tx{%X}", h)
}
i++
}
// sort by height by default
sort.Slice(results, func(i, j int) bool {
return results[i].Height < results[j].Height
})
return results, nil
}
func lookForHash(conditions []query.Condition) (hash []byte, err error, ok bool) {
for _, c := range conditions {
if c.Tag == types.TxHashKey {
decoded, err := hex.DecodeString(c.Operand.(string))
return decoded, err, true
}
}
return
}
func lookForHeight(conditions []query.Condition) (height int64, index int) {
for i, c := range conditions {
if c.Tag == types.TxHeightKey {
return c.Operand.(int64), i
}
}
return 0, -1
}
// special map to hold range conditions
// Example: account.number => queryRange{lowerBound: 1, upperBound: 5}
type queryRanges map[string]queryRange
type queryRange struct {
key string
lowerBound interface{} // int || time.Time
includeLowerBound bool
upperBound interface{} // int || time.Time
includeUpperBound bool
}
func (r queryRange) lowerBoundValue() interface{} {
if r.lowerBound == nil {
return nil
}
if r.includeLowerBound {
return r.lowerBound
} else {
switch t := r.lowerBound.(type) {
case int64:
return t + 1
case time.Time:
return t.Unix() + 1
default:
panic("not implemented")
}
}
}
func (r queryRange) AnyBound() interface{} {
if r.lowerBound != nil {
return r.lowerBound
} else {
return r.upperBound
}
}
func (r queryRange) upperBoundValue() interface{} {
if r.upperBound == nil {
return nil
}
if r.includeUpperBound {
return r.upperBound
} else {
switch t := r.upperBound.(type) {
case int64:
return t - 1
case time.Time:
return t.Unix() - 1
default:
panic("not implemented")
}
}
}
func lookForRanges(conditions []query.Condition) (ranges queryRanges, indexes []int) {
ranges = make(queryRanges)
for i, c := range conditions {
if isRangeOperation(c.Op) {
r, ok := ranges[c.Tag]
if !ok {
r = queryRange{key: c.Tag}
}
switch c.Op {
case query.OpGreater:
r.lowerBound = c.Operand
case query.OpGreaterEqual:
r.includeLowerBound = true
r.lowerBound = c.Operand
case query.OpLess:
r.upperBound = c.Operand
case query.OpLessEqual:
r.includeUpperBound = true
r.upperBound = c.Operand
}
ranges[c.Tag] = r
indexes = append(indexes, i)
}
}
return ranges, indexes
}
func isRangeOperation(op query.Operator) bool {
switch op {
case query.OpGreater, query.OpGreaterEqual, query.OpLess, query.OpLessEqual:
return true
default:
return false
}
}
func (txi *TxIndex) match(c query.Condition, startKey []byte) (hashes [][]byte) {
if c.Op == query.OpEqual {
it := dbm.IteratePrefix(txi.store, startKey)
defer it.Close()
for ; it.Valid(); it.Next() {
hashes = append(hashes, it.Value())
}
} else if c.Op == query.OpContains {
// XXX: doing full scan because startKey does not apply here
// For example, if startKey = "account.owner=an" and search query = "accoutn.owner CONSISTS an"
// we can't iterate with prefix "account.owner=an" because we might miss keys like "account.owner=Ulan"
it := txi.store.Iterator(nil, nil)
defer it.Close()
for ; it.Valid(); it.Next() {
if !isTagKey(it.Key()) {
continue
}
if strings.Contains(extractValueFromKey(it.Key()), c.Operand.(string)) {
hashes = append(hashes, it.Value())
}
}
} else {
panic("other operators should be handled already")
}
return
}
func (txi *TxIndex) matchRange(r queryRange, prefix []byte) (hashes [][]byte) {
// create a map to prevent duplicates
hashesMap := make(map[string][]byte)
lowerBound := r.lowerBoundValue()
upperBound := r.upperBoundValue()
it := dbm.IteratePrefix(txi.store, prefix)
defer it.Close()
LOOP:
for ; it.Valid(); it.Next() {
if !isTagKey(it.Key()) {
continue
}
switch r.AnyBound().(type) {
case int64:
v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
if err != nil {
continue LOOP
}
include := true
if lowerBound != nil && v < lowerBound.(int64) {
include = false
}
if upperBound != nil && v > upperBound.(int64) {
include = false
}
if include {
hashesMap[fmt.Sprintf("%X", it.Value())] = it.Value()
}
// XXX: passing time in a ABCI Tags is not yet implemented
// case time.Time:
// v := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64)
// if v == r.upperBound {
// break
// }
}
}
hashes = make([][]byte, len(hashesMap))
i := 0
for _, h := range hashesMap {
hashes[i] = h
i++
}
return
}
///////////////////////////////////////////////////////////////////////////////
// Keys
func startKey(c query.Condition, height int64) []byte {
var key string
if height > 0 {
key = fmt.Sprintf("%s/%v/%d", c.Tag, c.Operand, height)
} else {
key = fmt.Sprintf("%s/%v", c.Tag, c.Operand)
}
return []byte(key)
}
func isTagKey(key []byte) bool {
return strings.Count(string(key), tagKeySeparator) == 3
}
func extractValueFromKey(key []byte) string {
parts := strings.SplitN(string(key), tagKeySeparator, 3)
return parts[1]
}
func keyForTag(tag cmn.KVPair, result *types.TxResult) []byte {
return []byte(fmt.Sprintf("%s/%s/%d/%d", tag.Key, tag.Value, result.Height, result.Index))
}
///////////////////////////////////////////////////////////////////////////////
// Utils
func intersect(as, bs [][]byte) [][]byte {
i := make([][]byte, 0, cmn.MinInt(len(as), len(bs)))
for _, a := range as {
for _, b := range bs {
if bytes.Equal(a, b) {
i = append(i, a)
}
}
}
return i
}
+239
View File
@@ -0,0 +1,239 @@
package kv
import (
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
db "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tendermint/libs/pubsub/query"
"github.com/tendermint/tendermint/state/txindex"
"github.com/tendermint/tendermint/types"
)
func TestTxIndex(t *testing.T) {
indexer := NewTxIndex(db.NewMemDB())
tx := types.Tx("HELLO WORLD")
txResult := &types.TxResult{1, 0, tx, abci.ResponseDeliverTx{Data: []byte{0}, Code: abci.CodeTypeOK, Log: "", Tags: nil}}
hash := tx.Hash()
batch := txindex.NewBatch(1)
if err := batch.Add(txResult); err != nil {
t.Error(err)
}
err := indexer.AddBatch(batch)
require.NoError(t, err)
loadedTxResult, err := indexer.Get(hash)
require.NoError(t, err)
assert.Equal(t, txResult, loadedTxResult)
tx2 := types.Tx("BYE BYE WORLD")
txResult2 := &types.TxResult{1, 0, tx2, abci.ResponseDeliverTx{Data: []byte{0}, Code: abci.CodeTypeOK, Log: "", Tags: nil}}
hash2 := tx2.Hash()
err = indexer.Index(txResult2)
require.NoError(t, err)
loadedTxResult2, err := indexer.Get(hash2)
require.NoError(t, err)
assert.Equal(t, txResult2, loadedTxResult2)
}
func TestTxSearch(t *testing.T) {
allowedTags := []string{"account.number", "account.owner", "account.date"}
indexer := NewTxIndex(db.NewMemDB(), IndexTags(allowedTags))
txResult := txResultWithTags([]cmn.KVPair{
{Key: []byte("account.number"), Value: []byte("1")},
{Key: []byte("account.owner"), Value: []byte("Ivan")},
{Key: []byte("not_allowed"), Value: []byte("Vlad")},
})
hash := txResult.Tx.Hash()
err := indexer.Index(txResult)
require.NoError(t, err)
testCases := []struct {
q string
resultsLength int
}{
// search by hash
{fmt.Sprintf("tx.hash = '%X'", hash), 1},
// search by exact match (one tag)
{"account.number = 1", 1},
// search by exact match (two tags)
{"account.number = 1 AND account.owner = 'Ivan'", 1},
// search by exact match (two tags)
{"account.number = 1 AND account.owner = 'Vlad'", 0},
// search by range
{"account.number >= 1 AND account.number <= 5", 1},
// search by range (lower bound)
{"account.number >= 1", 1},
// search by range (upper bound)
{"account.number <= 5", 1},
// search using not allowed tag
{"not_allowed = 'boom'", 0},
// search for not existing tx result
{"account.number >= 2 AND account.number <= 5", 0},
// search using not existing tag
{"account.date >= TIME 2013-05-03T14:45:00Z", 0},
// search using CONTAINS
{"account.owner CONTAINS 'an'", 1},
// search using CONTAINS
{"account.owner CONTAINS 'Vlad'", 0},
}
for _, tc := range testCases {
t.Run(tc.q, func(t *testing.T) {
results, err := indexer.Search(query.MustParse(tc.q))
assert.NoError(t, err)
assert.Len(t, results, tc.resultsLength)
if tc.resultsLength > 0 {
assert.Equal(t, []*types.TxResult{txResult}, results)
}
})
}
}
func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) {
allowedTags := []string{"account.number"}
indexer := NewTxIndex(db.NewMemDB(), IndexTags(allowedTags))
txResult := txResultWithTags([]cmn.KVPair{
{Key: []byte("account.number"), Value: []byte("1")},
{Key: []byte("account.number"), Value: []byte("2")},
})
err := indexer.Index(txResult)
require.NoError(t, err)
results, err := indexer.Search(query.MustParse("account.number >= 1"))
assert.NoError(t, err)
assert.Len(t, results, 1)
assert.Equal(t, []*types.TxResult{txResult}, results)
}
func TestTxSearchMultipleTxs(t *testing.T) {
allowedTags := []string{"account.number"}
indexer := NewTxIndex(db.NewMemDB(), IndexTags(allowedTags))
// indexed first, but bigger height (to test the order of transactions)
txResult := txResultWithTags([]cmn.KVPair{
{Key: []byte("account.number"), Value: []byte("1")},
})
txResult.Tx = types.Tx("Bob's account")
txResult.Height = 2
err := indexer.Index(txResult)
require.NoError(t, err)
// indexed second, but smaller height (to test the order of transactions)
txResult2 := txResultWithTags([]cmn.KVPair{
{Key: []byte("account.number"), Value: []byte("2")},
})
txResult2.Tx = types.Tx("Alice's account")
txResult2.Height = 1
err = indexer.Index(txResult2)
require.NoError(t, err)
results, err := indexer.Search(query.MustParse("account.number >= 1"))
assert.NoError(t, err)
require.Len(t, results, 2)
assert.Equal(t, []*types.TxResult{txResult2, txResult}, results)
}
func TestIndexAllTags(t *testing.T) {
indexer := NewTxIndex(db.NewMemDB(), IndexAllTags())
txResult := txResultWithTags([]cmn.KVPair{
cmn.KVPair{[]byte("account.owner"), []byte("Ivan")},
cmn.KVPair{[]byte("account.number"), []byte("1")},
})
err := indexer.Index(txResult)
require.NoError(t, err)
results, err := indexer.Search(query.MustParse("account.number >= 1"))
assert.NoError(t, err)
assert.Len(t, results, 1)
assert.Equal(t, []*types.TxResult{txResult}, results)
results, err = indexer.Search(query.MustParse("account.owner = 'Ivan'"))
assert.NoError(t, err)
assert.Len(t, results, 1)
assert.Equal(t, []*types.TxResult{txResult}, results)
}
func txResultWithTags(tags []cmn.KVPair) *types.TxResult {
tx := types.Tx("HELLO WORLD")
return &types.TxResult{
Height: 1,
Index: 0,
Tx: tx,
Result: abci.ResponseDeliverTx{
Data: []byte{0},
Code: abci.CodeTypeOK,
Log: "",
Tags: tags,
Fee: cmn.KI64Pair{Key: nil, Value: 0},
},
}
}
func benchmarkTxIndex(txsCount int64, b *testing.B) {
tx := types.Tx("HELLO WORLD")
txResult := &types.TxResult{
Height: 1,
Index: 0,
Tx: tx,
Result: abci.ResponseDeliverTx{
Data: []byte{0},
Code: abci.CodeTypeOK,
Log: "",
Tags: []cmn.KVPair{},
Fee: cmn.KI64Pair{Key: []uint8{}, Value: 0},
},
}
dir, err := ioutil.TempDir("", "tx_index_db")
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(dir) // nolint: errcheck
store := db.NewDB("tx_index", "leveldb", dir)
indexer := NewTxIndex(store)
batch := txindex.NewBatch(txsCount)
for i := int64(0); i < txsCount; i++ {
if err := batch.Add(txResult); err != nil {
b.Fatal(err)
}
txResult.Index++
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
err = indexer.AddBatch(batch)
}
if err != nil {
b.Fatal(err)
}
}
func BenchmarkTxIndex1(b *testing.B) { benchmarkTxIndex(1, b) }
func BenchmarkTxIndex500(b *testing.B) { benchmarkTxIndex(500, b) }
func BenchmarkTxIndex1000(b *testing.B) { benchmarkTxIndex(1000, b) }
func BenchmarkTxIndex2000(b *testing.B) { benchmarkTxIndex(2000, b) }
func BenchmarkTxIndex10000(b *testing.B) { benchmarkTxIndex(10000, b) }
+10
View File
@@ -0,0 +1,10 @@
package kv
import (
"github.com/tendermint/go-amino"
)
var cdc = amino.NewCodec()
func init() {
}
+33
View File
@@ -0,0 +1,33 @@
package null
import (
"errors"
"github.com/tendermint/tendermint/libs/pubsub/query"
"github.com/tendermint/tendermint/state/txindex"
"github.com/tendermint/tendermint/types"
)
var _ txindex.TxIndexer = (*TxIndex)(nil)
// TxIndex acts as a /dev/null.
type TxIndex struct{}
// Get on a TxIndex is disabled and panics when invoked.
func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) {
return nil, errors.New(`Indexing is disabled (set 'tx_index = "kv"' in config)`)
}
// AddBatch is a noop and always returns nil.
func (txi *TxIndex) AddBatch(batch *txindex.Batch) error {
return nil
}
// Index is a noop and always returns nil.
func (txi *TxIndex) Index(result *types.TxResult) error {
return nil
}
func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) {
return []*types.TxResult{}, nil
}
+125
View File
@@ -0,0 +1,125 @@
package state
import (
"bytes"
"errors"
"fmt"
"github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tmlibs/db"
)
//-----------------------------------------------------
// Validate block
func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
// validate internal consistency
if err := block.ValidateBasic(); err != nil {
return err
}
// validate basic info
if block.ChainID != state.ChainID {
return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", state.ChainID, block.ChainID)
}
if block.Height != state.LastBlockHeight+1 {
return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", state.LastBlockHeight+1, block.Height)
}
/* TODO: Determine bounds for Time
See blockchain/reactor "stopSyncingDurationMinutes"
if !block.Time.After(lastBlockTime) {
return errors.New("Invalid Block.Header.Time")
}
*/
// validate prev block info
if !block.LastBlockID.Equals(state.LastBlockID) {
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", state.LastBlockID, block.LastBlockID)
}
newTxs := int64(len(block.Data.Txs))
if block.TotalTxs != state.LastBlockTotalTx+newTxs {
return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", state.LastBlockTotalTx+newTxs, block.TotalTxs)
}
// validate app info
if !bytes.Equal(block.AppHash, state.AppHash) {
return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", state.AppHash, block.AppHash)
}
if !bytes.Equal(block.ConsensusHash, state.ConsensusParams.Hash()) {
return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", state.ConsensusParams.Hash(), block.ConsensusHash)
}
if !bytes.Equal(block.LastResultsHash, state.LastResultsHash) {
return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", state.LastResultsHash, block.LastResultsHash)
}
if !bytes.Equal(block.ValidatorsHash, state.Validators.Hash()) {
return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", state.Validators.Hash(), block.ValidatorsHash)
}
// Validate block LastCommit.
if block.Height == 1 {
if len(block.LastCommit.Precommits) != 0 {
return errors.New("Block at height 1 (first block) should have no LastCommit precommits")
}
} else {
if len(block.LastCommit.Precommits) != state.LastValidators.Size() {
return fmt.Errorf("Invalid block commit size. Expected %v, got %v",
state.LastValidators.Size(), len(block.LastCommit.Precommits))
}
err := state.LastValidators.VerifyCommit(
state.ChainID, state.LastBlockID, block.Height-1, block.LastCommit)
if err != nil {
return err
}
}
// TODO: Each check requires loading an old validator set.
// We should cap the amount of evidence per block
// to prevent potential proposer DoS.
for _, ev := range block.Evidence.Evidence {
if err := VerifyEvidence(stateDB, state, ev); err != nil {
return types.NewEvidenceInvalidErr(ev, err)
}
}
return nil
}
// VerifyEvidence verifies the evidence fully by checking:
// - it is sufficiently recent (MaxAge)
// - it is from a key who was a validator at the given height
// - it is internally consistent
// - it was properly signed by the alleged equivocator
func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error {
height := state.LastBlockHeight
evidenceAge := height - evidence.Height()
maxAge := state.ConsensusParams.EvidenceParams.MaxAge
if evidenceAge > maxAge {
return fmt.Errorf("Evidence from height %d is too old. Min height is %d",
evidence.Height(), height-maxAge)
}
valset, err := LoadValidators(stateDB, evidence.Height())
if err != nil {
// TODO: if err is just that we cant find it cuz we pruned, ignore.
// TODO: if its actually bad evidence, punish peer
return err
}
// The address must have been an active validator at the height.
// NOTE: we will ignore evidence from H if the key was not a validator
// at H, even if it is a validator at some nearby H'
ev := evidence
height, addr := ev.Height(), ev.Address()
_, val := valset.GetByAddress(addr)
if val == nil {
return fmt.Errorf("Address %X was not a validator at height %d", addr, height)
}
if err := evidence.Verify(state.ChainID, val.PubKey); err != nil {
return err
}
return nil
}
+68
View File
@@ -0,0 +1,68 @@
package state
import (
"testing"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
)
func TestValidateBlock(t *testing.T) {
state, _ := state(1, 1)
blockExec := NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nil, nil, nil)
// proper block must pass
block := makeBlock(state, 1)
err := blockExec.ValidateBlock(state, block)
require.NoError(t, err)
// wrong chain fails
block = makeBlock(state, 1)
block.ChainID = "not-the-real-one"
err = blockExec.ValidateBlock(state, block)
require.Error(t, err)
// wrong height fails
block = makeBlock(state, 1)
block.Height += 10
err = blockExec.ValidateBlock(state, block)
require.Error(t, err)
// wrong total tx fails
block = makeBlock(state, 1)
block.TotalTxs += 10
err = blockExec.ValidateBlock(state, block)
require.Error(t, err)
// wrong blockid fails
block = makeBlock(state, 1)
block.LastBlockID.PartsHeader.Total += 10
err = blockExec.ValidateBlock(state, block)
require.Error(t, err)
// wrong app hash fails
block = makeBlock(state, 1)
block.AppHash = []byte("wrong app hash")
err = blockExec.ValidateBlock(state, block)
require.Error(t, err)
// wrong consensus hash fails
block = makeBlock(state, 1)
block.ConsensusHash = []byte("wrong consensus hash")
err = blockExec.ValidateBlock(state, block)
require.Error(t, err)
// wrong results hash fails
block = makeBlock(state, 1)
block.LastResultsHash = []byte("wrong results hash")
err = blockExec.ValidateBlock(state, block)
require.Error(t, err)
// wrong validators hash fails
block = makeBlock(state, 1)
block.ValidatorsHash = []byte("wrong validators hash")
err = blockExec.ValidateBlock(state, block)
require.Error(t, err)
}
+12
View File
@@ -0,0 +1,12 @@
package state
import (
"github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/crypto"
)
var cdc = amino.NewCodec()
func init() {
crypto.RegisterAmino(cdc)
}