Merge pull request #999 from tendermint/feature/result-hash-header
Add results hash to header
This commit is contained in:
commit
f95b7529eb
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(¶ms)},
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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…
Reference in New Issue