From f870a49f4208cd888f0e25575d977d512059128a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Dec 2017 15:21:02 +0100 Subject: [PATCH] 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. --- state/state.go | 69 +++++++++++++++++++++++++++++++++++++++++++-- state/state_test.go | 34 ++++++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/state/state.go b/state/state.go index 0bed708a6..99a022d7b 100644 --- a/state/state.go +++ b/state/state.go @@ -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 diff --git a/state/state_test.go b/state/state_test.go index b46996155..d15753fef 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -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 {