Browse Source

Merge pull request #999 from tendermint/feature/result-hash-header

Add results hash to header
pull/1016/head
Ethan Buchman 7 years ago
committed by GitHub
parent
commit
f95b7529eb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 428 additions and 114 deletions
  1. +4
    -1
      consensus/replay.go
  2. +7
    -6
      consensus/replay_test.go
  3. +3
    -3
      lite/dynamic_test.go
  4. +1
    -1
      lite/files/commit_test.go
  5. +1
    -1
      lite/files/provider_test.go
  6. +10
    -9
      lite/helpers.go
  7. +6
    -3
      lite/inquirer_test.go
  8. +3
    -2
      lite/performance_test.go
  9. +2
    -2
      lite/provider_test.go
  10. +1
    -1
      lite/static_test.go
  11. +9
    -0
      rpc/client/httpclient.go
  12. +1
    -0
      rpc/client/interface.go
  13. +4
    -0
      rpc/client/localclient.go
  14. +9
    -3
      rpc/client/rpc_test.go
  15. +76
    -26
      rpc/core/blocks.go
  16. +4
    -4
      rpc/core/consensus.go
  17. +1
    -0
      rpc/core/routes.go
  18. +6
    -0
      rpc/core/types/responses.go
  19. +8
    -0
      state/errors.go
  20. +27
    -15
      state/execution.go
  21. +12
    -0
      state/execution_test.go
  22. +33
    -24
      state/state.go
  23. +84
    -8
      state/state_test.go
  24. +9
    -5
      types/block.go
  25. +66
    -0
      types/results.go
  26. +41
    -0
      types/results_test.go

+ 4
- 1
consensus/replay.go View File

@ -301,7 +301,10 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp
} else if appBlockHeight == storeBlockHeight {
// We ran Commit, but didn't save the state, so replayBlock with mock app
abciResponses := h.state.LoadABCIResponses()
abciResponses, err := h.state.LoadABCIResponses(storeBlockHeight)
if err != nil {
return nil, err
}
mockApp := newMockProxyApp(appHash, abciResponses)
h.logger.Info("Replay last block using mock app")
return h.replayBlock(storeBlockHeight, mockApp)


+ 7
- 6
consensus/replay_test.go View File

@ -107,14 +107,15 @@ func TestWALCrash(t *testing.T) {
{"block with a smaller part size",
func(cs *ConsensusState, ctx context.Context) {
// XXX: is there a better way to change BlockPartSizeBytes?
params := cs.state.ConsensusParams
params.BlockPartSizeBytes = 512
cs.state.ConsensusParams = params
sendTxs(cs, ctx)
cs.state.ConsensusParams.BlockPartSizeBytes = 512
cs.state.Save()
go sendTxs(cs, ctx)
},
1},
{"many non-empty blocks",
sendTxs,
func(cs *ConsensusState, ctx context.Context) {
go sendTxs(cs, ctx)
},
3},
}
@ -147,7 +148,7 @@ LOOP:
// start sending transactions
ctx, cancel := context.WithCancel(context.Background())
go initFn(cs, ctx)
initFn(cs, ctx)
// clean up WAL file from the previous iteration
walFile := cs.config.WalFile()


+ 3
- 3
lite/dynamic_test.go View File

@ -46,7 +46,7 @@ func TestDynamicCert(t *testing.T) {
for _, tc := range cases {
check := tc.keys.GenCommit(chainID, tc.height, nil, tc.vals,
[]byte("bar"), []byte("params"), tc.first, tc.last)
[]byte("bar"), []byte("params"), []byte("results"), tc.first, tc.last)
err := cert.Certify(check)
if tc.proper {
assert.Nil(err, "%+v", err)
@ -71,7 +71,7 @@ func TestDynamicUpdate(t *testing.T) {
// one valid block to give us a sense of time
h := int64(100)
good := keys.GenCommit(chainID, h, nil, vals, []byte("foo"), []byte("params"), 0, len(keys))
good := keys.GenCommit(chainID, h, nil, vals, []byte("foo"), []byte("params"), []byte("results"), 0, len(keys))
err := cert.Certify(good)
require.Nil(err, "%+v", err)
@ -109,7 +109,7 @@ func TestDynamicUpdate(t *testing.T) {
for _, tc := range cases {
fc := tc.keys.GenFullCommit(chainID, tc.height, nil, tc.vals,
[]byte("bar"), []byte("params"), tc.first, tc.last)
[]byte("bar"), []byte("params"), []byte("results"), tc.first, tc.last)
err := cert.Update(fc)
if tc.proper {
assert.Nil(err, "%d: %+v", tc.height, err)


+ 1
- 1
lite/files/commit_test.go View File

@ -29,7 +29,7 @@ func TestSerializeFullCommits(t *testing.T) {
// build a fc
keys := lite.GenValKeys(5)
vals := keys.ToValidators(10, 0)
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, 5)
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
require.Equal(h, fc.Height())
require.Equal(vals.Hash(), fc.ValidatorsHash())


+ 1
- 1
lite/files/provider_test.go View File

@ -46,7 +46,7 @@ func TestFileProvider(t *testing.T) {
// (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ...
vals := keys.ToValidators(10, int64(count/2))
h := int64(20 + 10*i)
check := keys.GenCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, 5)
check := keys.GenCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
seeds[i] = lite.NewFullCommit(check, vals)
}


+ 10
- 9
lite/helpers.go View File

@ -110,7 +110,7 @@ func makeVote(header *types.Header, vals *types.ValidatorSet, key crypto.PrivKey
// Silences warning that vals can also be merkle.Hashable
// nolint: interfacer
func genHeader(chainID string, height int64, txs types.Txs,
vals *types.ValidatorSet, appHash, consHash []byte) *types.Header {
vals *types.ValidatorSet, appHash, consHash, resHash []byte) *types.Header {
return &types.Header{
ChainID: chainID,
@ -120,18 +120,19 @@ func genHeader(chainID string, height int64, txs types.Txs,
TotalTxs: int64(len(txs)),
// LastBlockID
// LastCommitHash
ValidatorsHash: vals.Hash(),
DataHash: txs.Hash(),
AppHash: appHash,
ConsensusHash: consHash,
ValidatorsHash: vals.Hash(),
DataHash: txs.Hash(),
AppHash: appHash,
ConsensusHash: consHash,
LastResultsHash: resHash,
}
}
// GenCommit calls genHeader and signHeader and combines them into a Commit.
func (v ValKeys) GenCommit(chainID string, height int64, txs types.Txs,
vals *types.ValidatorSet, appHash, consHash []byte, first, last int) Commit {
vals *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int) Commit {
header := genHeader(chainID, height, txs, vals, appHash, consHash)
header := genHeader(chainID, height, txs, vals, appHash, consHash, resHash)
check := Commit{
Header: header,
Commit: v.signHeader(header, first, last),
@ -141,9 +142,9 @@ func (v ValKeys) GenCommit(chainID string, height int64, txs types.Txs,
// GenFullCommit calls genHeader and signHeader and combines them into a Commit.
func (v ValKeys) GenFullCommit(chainID string, height int64, txs types.Txs,
vals *types.ValidatorSet, appHash, consHash []byte, first, last int) FullCommit {
vals *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int) FullCommit {
header := genHeader(chainID, height, txs, vals, appHash, consHash)
header := genHeader(chainID, height, txs, vals, appHash, consHash, resHash)
commit := Commit{
Header: header,
Commit: v.signHeader(header, first, last),


+ 6
- 3
lite/inquirer_test.go View File

@ -23,6 +23,7 @@ func TestInquirerValidPath(t *testing.T) {
// construct a bunch of commits, each with one more height than the last
chainID := "inquiry-test"
consHash := []byte("params")
resHash := []byte("results")
count := 50
commits := make([]lite.FullCommit, count)
for i := 0; i < count; i++ {
@ -31,7 +32,7 @@ func TestInquirerValidPath(t *testing.T) {
vals := keys.ToValidators(vote, 0)
h := int64(20 + 10*i)
appHash := []byte(fmt.Sprintf("h=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, 0, len(keys))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys))
}
// initialize a certifier with the initial state
@ -79,7 +80,8 @@ func TestInquirerMinimalPath(t *testing.T) {
vals := keys.ToValidators(vote, 0)
h := int64(5 + 10*i)
appHash := []byte(fmt.Sprintf("h=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, 0, len(keys))
resHash := []byte(fmt.Sprintf("res=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys))
}
// initialize a certifier with the initial state
@ -127,7 +129,8 @@ func TestInquirerVerifyHistorical(t *testing.T) {
vals := keys.ToValidators(vote, 0)
h := int64(20 + 10*i)
appHash := []byte(fmt.Sprintf("h=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, 0, len(keys))
resHash := []byte(fmt.Sprintf("res=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys))
}
// initialize a certifier with the initial state


+ 3
- 2
lite/performance_test.go View File

@ -33,7 +33,8 @@ func benchmarkGenCommit(b *testing.B, keys lite.ValKeys) {
for i := 0; i < b.N; i++ {
h := int64(1 + i)
appHash := []byte(fmt.Sprintf("h=%d", h))
keys.GenCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, len(keys))
resHash := []byte(fmt.Sprintf("res=%d", h))
keys.GenCommit(chainID, h, nil, vals, appHash, []byte("params"), resHash, 0, len(keys))
}
}
@ -105,7 +106,7 @@ func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) {
chainID := "bench-certify"
vals := keys.ToValidators(20, 10)
cert := lite.NewStatic(chainID, vals)
check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), 0, len(keys))
check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), []byte("res"), 0, len(keys))
for i := 0; i < b.N; i++ {
err := cert.Certify(check)
if err != nil {


+ 2
- 2
lite/provider_test.go View File

@ -58,7 +58,7 @@ func checkProvider(t *testing.T, p lite.Provider, chainID, app string) {
// (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ...
vals := keys.ToValidators(10, int64(count/2))
h := int64(20 + 10*i)
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, 5)
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
}
// check provider is empty
@ -129,7 +129,7 @@ func TestCacheGetsBestHeight(t *testing.T) {
for i := 0; i < count; i++ {
vals := keys.ToValidators(10, int64(count/2))
h := int64(10 * (i + 1))
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, 5)
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
err := p2.StoreCommit(fc)
require.NoError(err)
}


+ 1
- 1
lite/static_test.go View File

@ -44,7 +44,7 @@ func TestStaticCert(t *testing.T) {
for _, tc := range cases {
check := tc.keys.GenCommit(chainID, tc.height, nil, tc.vals,
[]byte("foo"), []byte("params"), tc.first, tc.last)
[]byte("foo"), []byte("params"), []byte("results"), tc.first, tc.last)
err := cert.Certify(check)
if tc.proper {
assert.Nil(err, "%+v", err)


+ 9
- 0
rpc/client/httpclient.go View File

@ -152,6 +152,15 @@ func (c *HTTP) Block(height *int64) (*ctypes.ResultBlock, error) {
return result, nil
}
func (c *HTTP) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) {
result := new(ctypes.ResultBlockResults)
_, err := c.rpc.Call("block_results", map[string]interface{}{"height": height}, result)
if err != nil {
return nil, errors.Wrap(err, "Block Result")
}
return result, nil
}
func (c *HTTP) Commit(height *int64) (*ctypes.ResultCommit, error) {
result := new(ctypes.ResultCommit)
_, err := c.rpc.Call("commit", map[string]interface{}{"height": height}, result)


+ 1
- 0
rpc/client/interface.go View File

@ -45,6 +45,7 @@ type ABCIClient interface {
// signatures and prove anything about the chain
type SignClient interface {
Block(height *int64) (*ctypes.ResultBlock, error)
BlockResults(height *int64) (*ctypes.ResultBlockResults, error)
Commit(height *int64) (*ctypes.ResultCommit, error)
Validators(height *int64) (*ctypes.ResultValidators, error)
Tx(hash []byte, prove bool) (*ctypes.ResultTx, error)


+ 4
- 0
rpc/client/localclient.go View File

@ -100,6 +100,10 @@ func (Local) Block(height *int64) (*ctypes.ResultBlock, error) {
return core.Block(height)
}
func (Local) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) {
return core.BlockResults(height)
}
func (Local) Commit(height *int64) (*ctypes.ResultCommit, error) {
return core.Commit(height)
}


+ 9
- 3
rpc/client/rpc_test.go View File

@ -155,7 +155,6 @@ func TestAppCalls(t *testing.T) {
}
// make sure we can lookup the tx with proof
// ptx, err := c.Tx(bres.Hash, true)
ptx, err := c.Tx(bres.Hash, true)
require.Nil(err, "%d: %+v", i, err)
assert.EqualValues(txh, ptx.Height)
@ -168,9 +167,16 @@ func TestAppCalls(t *testing.T) {
assert.True(len(appHash) > 0)
assert.EqualValues(apph, block.BlockMeta.Header.Height)
// now check the results
blockResults, err := c.BlockResults(&txh)
require.Nil(err, "%d: %+v", i, err)
assert.Equal(txh, blockResults.Height)
if assert.Equal(1, len(blockResults.Results.DeliverTx)) {
// check success code
assert.EqualValues(0, blockResults.Results.DeliverTx[0].Code)
}
// check blockchain info, now that we know there is info
// TODO: is this commented somewhere that they are returned
// in order of descending height???
info, err := c.BlockchainInfo(apph, apph)
require.Nil(err, "%d: %+v", i, err)
assert.True(info.LastHeight >= apph)


+ 76
- 26
rpc/core/blocks.go View File

@ -9,6 +9,7 @@ import (
)
// Get block headers for minHeight <= height <= maxHeight.
// Block headers are returned in descending order (highest first).
//
// ```shell
// curl 'localhost:46657/blockchain?minHeight=10&maxHeight=10'
@ -192,19 +193,10 @@ func BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, e
// }
// ```
func Block(heightPtr *int64) (*ctypes.ResultBlock, error) {
if heightPtr == nil {
height := blockStore.Height()
blockMeta := blockStore.LoadBlockMeta(height)
block := blockStore.LoadBlock(height)
return &ctypes.ResultBlock{blockMeta, block}, nil
}
height := *heightPtr
if height <= 0 {
return nil, fmt.Errorf("Height must be greater than 0")
}
if height > blockStore.Height() {
return nil, fmt.Errorf("Height must be less than the current blockchain height")
storeHeight := blockStore.Height()
height, err := getHeight(storeHeight, heightPtr)
if err != nil {
return nil, err
}
blockMeta := blockStore.LoadBlockMeta(height)
@ -283,20 +275,10 @@ func Block(heightPtr *int64) (*ctypes.ResultBlock, error) {
// }
// ```
func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) {
if heightPtr == nil {
height := blockStore.Height()
header := blockStore.LoadBlockMeta(height).Header
commit := blockStore.LoadSeenCommit(height)
return ctypes.NewResultCommit(header, commit, false), nil
}
height := *heightPtr
if height <= 0 {
return nil, fmt.Errorf("Height must be greater than 0")
}
storeHeight := blockStore.Height()
if height > storeHeight {
return nil, fmt.Errorf("Height must be less than or equal to the current blockchain height")
height, err := getHeight(storeHeight, heightPtr)
if err != nil {
return nil, err
}
header := blockStore.LoadBlockMeta(height).Header
@ -312,3 +294,71 @@ func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) {
commit := blockStore.LoadBlockCommit(height)
return ctypes.NewResultCommit(header, commit, true), nil
}
// BlockResults gets ABCIResults at a given height.
// If no height is provided, it will fetch results for the latest block.
//
// Results are for the height of the block containing the txs.
// Thus response.results[5] is the results of executing getBlock(h).Txs[5]
//
// ```shell
// curl 'localhost:46657/block_results?height=10'
// ```
//
// ```go
// client := client.NewHTTP("tcp://0.0.0.0:46657", "/websocket")
// info, err := client.BlockResults(10)
// ```
//
//
// > The above command returns JSON structured like this:
//
// ```json
// {
// "height": 10,
// "results": [
// {
// "code": 0,
// "data": "CAFE00F00D"
// },
// {
// "code": 102,
// "data": ""
// }
// ]
// }
// ```
func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) {
storeHeight := blockStore.Height()
height, err := getHeight(storeHeight, heightPtr)
if err != nil {
return nil, err
}
// load the results
state := consensusState.GetState()
results, err := state.LoadABCIResponses(height)
if err != nil {
return nil, err
}
res := &ctypes.ResultBlockResults{
Height: height,
Results: results,
}
return res, nil
}
func getHeight(storeHeight int64, heightPtr *int64) (int64, error) {
if heightPtr != nil {
height := *heightPtr
if height <= 0 {
return 0, fmt.Errorf("Height must be greater than 0")
}
if height > storeHeight {
return 0, fmt.Errorf("Height must be less than or equal to the current blockchain height")
}
return height, nil
}
return storeHeight, nil
}

+ 4
- 4
rpc/core/consensus.go View File

@ -43,12 +43,12 @@ import (
// }
// ```
func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) {
if heightPtr == nil {
blockHeight, validators := consensusState.GetValidators()
return &ctypes.ResultValidators{blockHeight, validators}, nil
storeHeight := blockStore.Height()
height, err := getHeight(storeHeight, heightPtr)
if err != nil {
return nil, err
}
height := *heightPtr
state := consensusState.GetState()
validators, err := state.LoadValidators(height)
if err != nil {


+ 1
- 0
rpc/core/routes.go View File

@ -17,6 +17,7 @@ var Routes = map[string]*rpc.RPCFunc{
"blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"),
"genesis": rpc.NewRPCFunc(Genesis, ""),
"block": rpc.NewRPCFunc(Block, "height"),
"block_results": rpc.NewRPCFunc(BlockResults, "height"),
"commit": rpc.NewRPCFunc(Commit, "height"),
"tx": rpc.NewRPCFunc(Tx, "hash,prove"),
"tx_search": rpc.NewRPCFunc(TxSearch, "query,prove"),


+ 6
- 0
rpc/core/types/responses.go View File

@ -9,6 +9,7 @@ import (
"github.com/tendermint/go-wire/data"
cstypes "github.com/tendermint/tendermint/consensus/types"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
@ -33,6 +34,11 @@ type ResultCommit struct {
CanonicalCommit bool `json:"canonical"`
}
type ResultBlockResults struct {
Height int64 `json:"height"`
Results *state.ABCIResponses `json:"results"`
}
// NewResultCommit is a helper to initialize the ResultCommit with
// the embedded struct
func NewResultCommit(header *types.Header, commit *types.Commit,


+ 8
- 0
state/errors.go View File

@ -41,6 +41,10 @@ type (
ErrNoConsensusParamsForHeight struct {
Height int64
}
ErrNoABCIResponsesForHeight struct {
Height int64
}
)
func (e ErrUnknownBlock) Error() string {
@ -69,3 +73,7 @@ func (e ErrNoValSetForHeight) Error() string {
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)
}

+ 27
- 15
state/execution.go View File

@ -52,25 +52,25 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p
// TODO: make use of res.Log
// TODO: make use of this info
// Blocks may include invalid txs.
// reqDeliverTx := req.(abci.RequestDeliverTx)
txResult := r.DeliverTx
if txResult.Code == abci.CodeTypeOK {
txRes := r.DeliverTx
if txRes.Code == abci.CodeTypeOK {
validTxs++
} else {
logger.Debug("Invalid tx", "code", txResult.Code, "log", txResult.Log)
logger.Debug("Invalid tx", "code", txRes.Code, "log", txRes.Log)
invalidTxs++
}
// NOTE: if we count we can access the tx from the block instead of
// pulling it from the req
tx := types.Tx(req.GetDeliverTx().Tx)
txEventPublisher.PublishEventTx(types.EventDataTx{types.TxResult{
Height: block.Height,
Index: uint32(txIndex),
Tx: types.Tx(req.GetDeliverTx().Tx),
Result: *txResult,
Tx: tx,
Result: *txRes,
}})
abciResponses.DeliverTx[txIndex] = txResult
abciResponses.DeliverTx[txIndex] = txRes
txIndex++
}
}
@ -84,6 +84,8 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p
}
}
// TODO: determine which validators were byzantine
// Begin block
_, err := proxyAppConn.BeginBlockSync(abci.RequestBeginBlock{
Hash: block.Hash(),
@ -240,17 +242,19 @@ func (s *State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (*
block.LastBlockID = s.LastBlockID
block.ValidatorsHash = s.Validators.Hash()
block.AppHash = s.AppHash
block.ConsensusHash = s.LastConsensusParams.Hash()
block.ConsensusHash = s.ConsensusParams.Hash()
block.LastResultsHash = s.LastResultsHash
return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes)
}
func (s *State) validateBlock(b *types.Block) error {
// Basic block validation.
// validate internal consistency
if err := b.ValidateBasic(); err != nil {
return err
}
// validate basic info
if b.ChainID != s.ChainID {
return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", s.ChainID, b.ChainID)
}
@ -265,19 +269,27 @@ func (s *State) validateBlock(b *types.Block) error {
}
*/
// validate prev block info
if !b.LastBlockID.Equals(s.LastBlockID) {
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", s.LastBlockID, b.LastBlockID)
}
newTxs := int64(len(b.Data.Txs))
if b.TotalTxs != s.LastBlockTotalTx+newTxs {
return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", s.LastBlockTotalTx+newTxs, b.TotalTxs)
}
if !b.LastBlockID.Equals(s.LastBlockID) {
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", s.LastBlockID, b.LastBlockID)
}
// validate app info
if !bytes.Equal(b.AppHash, s.AppHash) {
return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", s.AppHash, b.AppHash)
}
if !bytes.Equal(b.ConsensusHash, s.LastConsensusParams.Hash()) {
return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", s.LastConsensusParams.Hash(), b.ConsensusHash)
if !bytes.Equal(b.ConsensusHash, s.ConsensusParams.Hash()) {
return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", s.ConsensusParams.Hash(), b.ConsensusHash)
}
if !bytes.Equal(b.LastResultsHash, s.LastResultsHash) {
return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", s.LastResultsHash, b.LastResultsHash)
}
if !bytes.Equal(b.ValidatorsHash, s.Validators.Hash()) {
return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", s.Validators.Hash(), b.ValidatorsHash)
}
// Validate block LastCommit.
@ -318,7 +330,7 @@ func (s *State) ApplyBlock(txEventPublisher types.TxEventPublisher, proxyAppConn
fail.Fail() // XXX
// save the results before we commit
s.SaveABCIResponses(abciResponses)
s.SaveABCIResponses(block.Height, abciResponses)
fail.Fail() // XXX


+ 12
- 0
state/execution_test.go View File

@ -67,6 +67,18 @@ func TestValidateBlock(t *testing.T) {
block.ConsensusHash = []byte("wrong consensus hash")
err = state.ValidateBlock(block)
require.Error(t, err)
// wrong results hash fails
block = makeBlock(state, 1)
block.LastResultsHash = []byte("wrong results hash")
err = state.ValidateBlock(block)
require.Error(t, err)
// wrong validators hash fails
block = makeBlock(state, 1)
block.ValidatorsHash = []byte("wrong validators hash")
err = state.ValidateBlock(block)
require.Error(t, err)
}
func TestApplyBlock(t *testing.T) {


+ 33
- 24
state/state.go View File

@ -20,8 +20,7 @@ import (
// database keys
var (
stateKey = []byte("stateKey")
abciResponsesKey = []byte("abciResponsesKey")
stateKey = []byte("stateKey")
)
func calcValidatorsKey(height int64) []byte {
@ -32,6 +31,10 @@ func calcConsensusParamsKey(height int64) []byte {
return []byte(cmn.Fmt("consensusParamsKey:%v", height))
}
func calcABCIResponsesKey(height int64) []byte {
return []byte(cmn.Fmt("abciResponsesKey:%v", height))
}
//-----------------------------------------------------------------------------
// State is a short description of the latest committed block of the Tendermint consensus.
@ -68,9 +71,11 @@ type State struct {
// Consensus parameters used for validating blocks.
// Changes returned by EndBlock and updated after Commit.
ConsensusParams types.ConsensusParams
LastConsensusParams 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
@ -140,11 +145,12 @@ func (s *State) Copy() *State {
LastHeightValidatorsChanged: s.LastHeightValidatorsChanged,
ConsensusParams: s.ConsensusParams,
LastConsensusParams: s.LastConsensusParams,
LastHeightConsensusParamsChanged: s.LastHeightConsensusParamsChanged,
AppHash: s.AppHash,
LastResultsHash: s.LastResultsHash,
logger: s.logger,
}
}
@ -161,17 +167,18 @@ func (s *State) Save() {
// SaveABCIResponses persists the ABCIResponses to the database.
// This is useful in case we crash after app.Commit and before s.Save().
func (s *State) SaveABCIResponses(abciResponses *ABCIResponses) {
s.db.SetSync(abciResponsesKey, abciResponses.Bytes())
// Responses are indexed by height so they can also be loaded later to produce Merkle proofs.
func (s *State) SaveABCIResponses(height int64, abciResponses *ABCIResponses) {
s.db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes())
}
// LoadABCIResponses loads the ABCIResponses from the database.
// 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()
func (s *State) LoadABCIResponses() *ABCIResponses {
buf := s.db.Get(abciResponsesKey)
// s.Save(). It can also be used to produce Merkle proofs of the result of txs.
func (s *State) LoadABCIResponses(height int64) (*ABCIResponses, error) {
buf := s.db.Get(calcABCIResponsesKey(height))
if len(buf) == 0 {
return nil
return nil, ErrNoABCIResponsesForHeight{height}
}
abciResponses := new(ABCIResponses)
@ -184,7 +191,7 @@ func (s *State) LoadABCIResponses() *ABCIResponses {
}
// TODO: ensure that buf is completely read.
return abciResponses
return abciResponses, nil
}
// LoadValidators loads the ValidatorSet for a given height.
@ -346,14 +353,16 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ
types.BlockID{header.Hash(), blockPartsHeader},
header.Time,
nextValSet,
nextParams)
nextParams,
abciResponses.ResultsHash())
return nil
}
func (s *State) setBlockAndValidators(height int64,
newTxs int64, blockID types.BlockID, blockTime time.Time,
valSet *types.ValidatorSet,
params types.ConsensusParams) {
params types.ConsensusParams,
resultsHash []byte) {
s.LastBlockHeight = height
s.LastBlockTotalTx += newTxs
@ -363,8 +372,9 @@ func (s *State) setBlockAndValidators(height int64,
s.LastValidators = s.Validators.Copy()
s.Validators = valSet
s.LastConsensusParams = s.ConsensusParams
s.ConsensusParams = params
s.LastResultsHash = resultsHash
}
// GetValidators returns the last and current validator sets.
@ -374,23 +384,18 @@ func (s *State) GetValidators() (last *types.ValidatorSet, current *types.Valida
//------------------------------------------------------------------------
// ABCIResponses retains the responses of the various ABCI calls during block processing.
// It is persisted to disk before calling Commit.
// 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 {
Height int64
DeliverTx []*abci.ResponseDeliverTx
EndBlock *abci.ResponseEndBlock
txs types.Txs // reference for indexing results by hash
}
// NewABCIResponses returns a new ABCIResponses
func NewABCIResponses(block *types.Block) *ABCIResponses {
return &ABCIResponses{
Height: block.Height,
DeliverTx: make([]*abci.ResponseDeliverTx, block.NumTxs),
txs: block.Data.Txs,
}
}
@ -399,6 +404,11 @@ func (a *ABCIResponses) Bytes() []byte {
return wire.BinaryBytes(*a)
}
func (a *ABCIResponses) ResultsHash() []byte {
results := types.NewResults(a.DeliverTx)
return results.Hash()
}
//-----------------------------------------------------------------------------
// ValidatorsInfo represents the latest validator set, or the last height it changed
@ -488,7 +498,6 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) {
LastHeightValidatorsChanged: 1,
ConsensusParams: *genDoc.ConsensusParams,
LastConsensusParams: types.ConsensusParams{},
LastHeightConsensusParamsChanged: 1,
AppHash: genDoc.AppHash,


+ 84
- 8
state/state_test.go View File

@ -68,7 +68,7 @@ func TestStateSaveLoad(t *testing.T) {
}
// TestABCIResponsesSaveLoad tests saving and loading ABCIResponses.
func TestABCIResponsesSaveLoad(t *testing.T) {
func TestABCIResponsesSaveLoad1(t *testing.T) {
tearDown, _, state := setupTestCase(t)
defer tearDown(t)
// nolint: vetshadow
@ -87,15 +87,84 @@ func TestABCIResponsesSaveLoad(t *testing.T) {
Power: 10,
},
}}
abciResponses.txs = nil
state.SaveABCIResponses(abciResponses)
loadedAbciResponses := state.LoadABCIResponses()
state.SaveABCIResponses(block.Height, abciResponses)
loadedAbciResponses, err := state.LoadABCIResponses(block.Height)
assert.Nil(err)
assert.Equal(abciResponses, loadedAbciResponses,
cmn.Fmt(`ABCIResponses don't match: Got %v, Expected %v`, loadedAbciResponses,
abciResponses))
}
// TestResultsSaveLoad tests saving and loading abci results.
func TestABCIResponsesSaveLoad2(t *testing.T) {
tearDown, _, state := 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: {
[]*abci.ResponseDeliverTx{},
types.ABCIResults{},
},
1: {
[]*abci.ResponseDeliverTx{
{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
},
types.ABCIResults{
{32, []byte("Hello")},
}},
2: {
[]*abci.ResponseDeliverTx{
{Code: 383},
{Data: []byte("Gotcha!"),
Tags: []*abci.KVPair{
abci.KVPairInt("a", 1),
abci.KVPairString("build", "stuff"),
}},
},
types.ABCIResults{
{383, []byte{}},
{0, []byte("Gotcha!")},
}},
3: {
nil,
types.ABCIResults{},
},
}
// query all before, should return error
for i := range cases {
h := int64(i + 1)
res, err := state.LoadABCIResponses(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{},
}
state.SaveABCIResponses(h, responses)
}
// query all before, should return expected value
for i, tc := range cases {
h := int64(i + 1)
res, err := state.LoadABCIResponses(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, _, state := setupTestCase(t)
@ -353,7 +422,6 @@ func TestLessThanOneThirdOfVotingPowerPerBlockEnforced(t *testing.T) {
height := state.LastBlockHeight + 1
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
Height: height,
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: tc.valUpdatesFn(state.Validators)},
}
err := state.SetBlockAndValidators(block.Header, types.PartSetHeader{}, abciResponses)
@ -421,7 +489,6 @@ func makeHeaderPartsResponsesValPubKeyChange(state *State, height int64,
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
Height: height,
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{}},
}
@ -444,7 +511,6 @@ func makeHeaderPartsResponsesValPowerChange(state *State, height int64,
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
Height: height,
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{}},
}
@ -466,7 +532,6 @@ func makeHeaderPartsResponsesParams(state *State, height int64,
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
Height: height,
EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(&params)},
}
return block.Header, types.PartSetHeader{}, abciResponses
@ -476,3 +541,14 @@ type paramsChangeTestCase struct {
height int64
params types.ConsensusParams
}
func makeHeaderPartsResults(state *State, height int64,
results []*abci.ResponseDeliverTx) (*types.Header, types.PartSetHeader, *ABCIResponses) {
block := makeBlock(state, height)
abciResponses := &ABCIResponses{
DeliverTx: results,
EndBlock: &abci.ResponseEndBlock{},
}
return block.Header, types.PartSetHeader{}, abciResponses
}

+ 9
- 5
types/block.go View File

@ -149,10 +149,11 @@ type Header struct {
LastCommitHash data.Bytes `json:"last_commit_hash"` // commit from validators from the last block
DataHash data.Bytes `json:"data_hash"` // transactions
// hashes from the app
ValidatorsHash data.Bytes `json:"validators_hash"` // validators for the current block
ConsensusHash data.Bytes `json:"consensus_hash"` // consensus params for current block
AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block
// hashes from the app output from the prev block
ValidatorsHash data.Bytes `json:"validators_hash"` // validators for the current block
ConsensusHash data.Bytes `json:"consensus_hash"` // consensus params for current block
AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block
LastResultsHash data.Bytes `json:"last_results_hash"` // root hash of all results from the txs from the previous block
}
// Hash returns the hash of the header.
@ -173,6 +174,7 @@ func (h *Header) Hash() data.Bytes {
"Validators": h.ValidatorsHash,
"App": h.AppHash,
"Consensus": h.ConsensusHash,
"Results": h.LastResultsHash,
})
}
@ -192,7 +194,8 @@ func (h *Header) StringIndented(indent string) string {
%s Data: %v
%s Validators: %v
%s App: %v
%s Conensus: %v
%s Conensus: %v
%s Results: %v
%s}#%v`,
indent, h.ChainID,
indent, h.Height,
@ -205,6 +208,7 @@ func (h *Header) StringIndented(indent string) string {
indent, h.ValidatorsHash,
indent, h.AppHash,
indent, h.ConsensusHash,
indent, h.LastResultsHash,
indent, h.Hash())
}


+ 66
- 0
types/results.go View File

@ -0,0 +1,66 @@
package types
import (
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/merkle"
)
//-----------------------------------------------------------------------------
// ABCIResult is the deterministic component of a ResponseDeliverTx.
// TODO: add Tags
type ABCIResult struct {
Code uint32 `json:"code"`
Data data.Bytes `json:"data"`
}
// Hash returns the canonical hash of the ABCIResult
func (a ABCIResult) Hash() []byte {
return wire.BinaryRipemd160(a)
}
// ABCIResults wraps the deliver tx results to return a proof
type ABCIResults []ABCIResult
// NewResults creates ABCIResults from ResponseDeliverTx
func NewResults(del []*abci.ResponseDeliverTx) ABCIResults {
res := make(ABCIResults, len(del))
for i, d := range del {
res[i] = NewResultFromResponse(d)
}
return res
}
func NewResultFromResponse(response *abci.ResponseDeliverTx) ABCIResult {
return ABCIResult{
Code: response.Code,
Data: response.Data,
}
}
// Bytes serializes the ABCIResponse using go-wire
func (a ABCIResults) Bytes() []byte {
return wire.BinaryBytes(a)
}
// Hash returns a merkle hash of all results
func (a ABCIResults) Hash() []byte {
return merkle.SimpleHashFromHashables(a.toHashables())
}
// ProveResult returns a merkle proof of one result from the set
func (a ABCIResults) ProveResult(i int) merkle.SimpleProof {
_, proofs := merkle.SimpleProofsFromHashables(a.toHashables())
return *proofs[i]
}
func (a ABCIResults) toHashables() []merkle.Hashable {
l := len(a)
hashables := make([]merkle.Hashable, l)
for i := 0; i < l; i++ {
hashables[i] = a[i]
}
return hashables
}

+ 41
- 0
types/results_test.go View File

@ -0,0 +1,41 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestABCIResults(t *testing.T) {
a := ABCIResult{Code: 0, Data: nil}
b := ABCIResult{Code: 0, Data: []byte{}}
c := ABCIResult{Code: 0, Data: []byte("one")}
d := ABCIResult{Code: 14, Data: nil}
e := ABCIResult{Code: 14, Data: []byte("foo")}
f := ABCIResult{Code: 14, Data: []byte("bar")}
// nil and []byte{} should produce same hash
assert.Equal(t, a.Hash(), b.Hash())
// a and b should be the same, don't go in results
results := ABCIResults{a, c, d, e, f}
// make sure each result hashes properly
var last []byte
for i, res := range results {
h := res.Hash()
assert.NotEqual(t, last, h, "%d", i)
last = h
}
// make sure that we can get a root hash from results
// and verify proofs
root := results.Hash()
assert.NotEmpty(t, root)
for i, res := range results {
proof := results.ProveResult(i)
valid := proof.Verify(i, len(results), res.Hash(), root)
assert.True(t, valid, "%d", i)
}
}

Loading…
Cancel
Save