Add ABCIResults with Hash and Proof to State

State maintains LastResultsHash
Verify that we can produce unique hashes for each result,
and provide valid proofs from the root hash.
This commit is contained in:
Ethan Frey 2017-12-22 15:21:02 +01:00 committed by Ethan Buchman
parent d844799b3b
commit f870a49f42
2 changed files with 101 additions and 2 deletions

View File

@ -8,10 +8,13 @@ import (
"time"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-wire/data"
"golang.org/x/crypto/ripemd160"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/tmlibs/merkle"
wire "github.com/tendermint/go-wire"
@ -71,6 +74,10 @@ type State struct {
LastConsensusParams types.ConsensusParams
LastHeightConsensusParamsChanged int64
// Store LastABCIResults along with hash
LastResults ABCIResults
LastResultHash []byte
// The latest AppHash we've received from calling abci.Commit()
AppHash []byte
@ -346,14 +353,16 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ
types.BlockID{header.Hash(), blockPartsHeader},
header.Time,
nextValSet,
nextParams)
nextParams,
NewResults(abciResponses.DeliverTx))
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,
results ABCIResults) {
s.LastBlockHeight = height
s.LastBlockTotalTx += newTxs
@ -365,6 +374,9 @@ func (s *State) setBlockAndValidators(height int64,
s.LastConsensusParams = s.ConsensusParams
s.ConsensusParams = params
s.LastResults = results
s.LastResultHash = results.Hash()
}
// GetValidators returns the last and current validator sets.
@ -401,6 +413,59 @@ func (a *ABCIResponses) Bytes() []byte {
//-----------------------------------------------------------------------------
// ABCIResult is just the essential info to prove
// success/failure of a DeliverTx
type ABCIResult struct {
Code uint32 `json:"code"`
Data data.Bytes `json:"data"`
}
// Hash creates a canonical json hash of the ABCIResult
func (a ABCIResult) Hash() []byte {
// stupid canonical json output, easy to check in any language
bs := fmt.Sprintf(`{"code":%d,"data":"%s"}`, a.Code, a.Data)
var hasher = ripemd160.New()
hasher.Write([]byte(bs))
return hasher.Sum(nil)
}
// 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] = ABCIResult{
Code: d.Code,
Data: d.Data,
}
}
return res
}
// 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
}
//-----------------------------------------------------------------------------
// ValidatorsInfo represents the latest validator set, or the last height it changed
type ValidatorsInfo struct {
ValidatorSet *types.ValidatorSet

View File

@ -279,6 +279,40 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
}
}
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)
}
}
func makeParams(blockBytes, blockTx, blockGas, txBytes,
txGas, partSize int) types.ConsensusParams {