From f870a49f4208cd888f0e25575d977d512059128a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Dec 2017 15:21:02 +0100 Subject: [PATCH 01/12] 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 { From 632cc918b43abd7c5693dd349da29122b3832b92 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Dec 2017 16:04:29 +0100 Subject: [PATCH 02/12] Save/Load Results for every height Add some tests. Behaves like saving validator set, except it always saves at each height instead of a reference to last changed. --- state/errors.go | 8 +++++ state/state.go | 45 +++++++++++++++++++++++++- state/state_test.go | 79 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/state/errors.go b/state/errors.go index 98f44281a..1c0b76ab4 100644 --- a/state/errors.go +++ b/state/errors.go @@ -41,6 +41,10 @@ type ( ErrNoConsensusParamsForHeight struct { Height int64 } + + ErrNoResultsForHeight 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 ErrNoResultsForHeight) Error() string { + return cmn.Fmt("Could not find results for height #%d", e.Height) +} diff --git a/state/state.go b/state/state.go index 99a022d7b..316a3161e 100644 --- a/state/state.go +++ b/state/state.go @@ -35,6 +35,10 @@ func calcConsensusParamsKey(height int64) []byte { return []byte(cmn.Fmt("consensusParamsKey:%v", height)) } +func calcResultsKey(height int64) []byte { + return []byte(cmn.Fmt("resultsKey:%v", height)) +} + //----------------------------------------------------------------------------- // State is a short description of the latest committed block of the Tendermint consensus. @@ -75,7 +79,7 @@ type State struct { LastHeightConsensusParamsChanged int64 // Store LastABCIResults along with hash - LastResults ABCIResults + LastResults ABCIResults // TODO: remove?? LastResultHash []byte // The latest AppHash we've received from calling abci.Commit() @@ -163,6 +167,7 @@ func (s *State) Save() { s.saveValidatorsInfo() s.saveConsensusParamsInfo() + s.saveResults() s.db.SetSync(stateKey, s.Bytes()) } @@ -302,6 +307,39 @@ func (s *State) saveConsensusParamsInfo() { s.db.SetSync(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes()) } +// LoadResults loads the ABCIResults for a given height. +func (s *State) LoadResults(height int64) (ABCIResults, error) { + resInfo := s.loadResults(height) + if resInfo == nil { + return nil, ErrNoResultsForHeight{height} + } + return resInfo, nil +} + +func (s *State) loadResults(height int64) ABCIResults { + buf := s.db.Get(calcResultsKey(height)) + if len(buf) == 0 { + return nil + } + + v := new(ABCIResults) + err := wire.ReadBinaryBytes(buf, v) + if err != nil { + // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED + cmn.Exit(cmn.Fmt(`LoadResults: Data has been corrupted or its spec has changed: + %v\n`, err)) + } + return *v +} + +// saveResults persists the results for the last block to disk. +// It should be called from s.Save(), right before the state itself is persisted. +func (s *State) saveResults() { + nextHeight := s.LastBlockHeight + 1 + results := s.LastResults + s.db.SetSync(calcResultsKey(nextHeight), results.Bytes()) +} + // Equals returns true if the States are identical. func (s *State) Equals(s2 *State) bool { return bytes.Equal(s.Bytes(), s2.Bytes()) @@ -444,6 +482,11 @@ func NewResults(del []*abci.ResponseDeliverTx) ABCIResults { return res } +// 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()) diff --git a/state/state_test.go b/state/state_test.go index d15753fef..84b8fb9d9 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -313,6 +313,73 @@ func TestABCIResults(t *testing.T) { } } +// TestResultsSaveLoad tests saving and loading abci results. +func TestResultsSaveLoad(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 ABCIResults + }{ + 0: { + []*abci.ResponseDeliverTx{}, + ABCIResults{}, + }, + 1: { + []*abci.ResponseDeliverTx{ + {Code: 32, Data: []byte("Hello"), Log: "Huh?"}, + }, + ABCIResults{ + {32, []byte("Hello")}, + }}, + 2: { + []*abci.ResponseDeliverTx{ + {Code: 383}, + {Data: []byte("Gotcha!"), + Tags: []*abci.KVPair{ + abci.KVPairInt("a", 1), + abci.KVPairString("build", "stuff"), + }}, + }, + ABCIResults{ + {383, []byte{}}, + {0, []byte("Gotcha!")}, + }}, + 3: { + nil, + ABCIResults{}, + }, + } + + // query all before, should return error + for i := range cases { + h := int64(i + 2) + res, err := state.LoadResults(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 + header, parts, responses := makeHeaderPartsResults(state, h, tc.added) + state.SetBlockAndValidators(header, parts, responses) + state.Save() + } + + // query all before, should return expected value + for i, tc := range cases { + h := int64(i + 2) + res, err := state.LoadResults(h) + assert.NoError(err, "%d", i) + assert.Equal(tc.expected, res, "%d", i) + } +} + func makeParams(blockBytes, blockTx, blockGas, txBytes, txGas, partSize int) types.ConsensusParams { @@ -510,3 +577,15 @@ 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{ + Height: height, + DeliverTx: results, + EndBlock: &abci.ResponseEndBlock{}, + } + return block.Header, types.PartSetHeader{}, abciResponses +} From 58c5df729b81f7a761b273876c3a1cb06f7ae6ff Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Dec 2017 16:43:45 +0100 Subject: [PATCH 03/12] Add ResultHash to header --- lite/dynamic_test.go | 6 +++--- lite/files/commit_test.go | 2 +- lite/files/provider_test.go | 2 +- lite/helpers.go | 11 ++++++----- lite/inquirer_test.go | 9 ++++++--- lite/performance_test.go | 5 +++-- lite/provider_test.go | 4 ++-- lite/static_test.go | 2 +- state/execution.go | 4 ++++ state/execution_test.go | 6 ++++++ state/state.go | 8 ++++++-- types/block.go | 6 +++++- 12 files changed, 44 insertions(+), 21 deletions(-) diff --git a/lite/dynamic_test.go b/lite/dynamic_test.go index acbd1e651..c45371acd 100644 --- a/lite/dynamic_test.go +++ b/lite/dynamic_test.go @@ -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) diff --git a/lite/files/commit_test.go b/lite/files/commit_test.go index f6cb2a734..e0235ba29 100644 --- a/lite/files/commit_test.go +++ b/lite/files/commit_test.go @@ -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()) diff --git a/lite/files/provider_test.go b/lite/files/provider_test.go index e50d34614..5deebb1a2 100644 --- a/lite/files/provider_test.go +++ b/lite/files/provider_test.go @@ -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) } diff --git a/lite/helpers.go b/lite/helpers.go index 394cd666d..01ed4a84a 100644 --- a/lite/helpers.go +++ b/lite/helpers.go @@ -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, @@ -124,14 +124,15 @@ func genHeader(chainID string, height int64, txs types.Txs, DataHash: txs.Hash(), AppHash: appHash, ConsensusHash: consHash, + ResultsHash: 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), diff --git a/lite/inquirer_test.go b/lite/inquirer_test.go index eb45eb3cb..ce4317543 100644 --- a/lite/inquirer_test.go +++ b/lite/inquirer_test.go @@ -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 diff --git a/lite/performance_test.go b/lite/performance_test.go index af6eacc33..835e52f91 100644 --- a/lite/performance_test.go +++ b/lite/performance_test.go @@ -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 { diff --git a/lite/provider_test.go b/lite/provider_test.go index 09ad0aa8a..b2529b550 100644 --- a/lite/provider_test.go +++ b/lite/provider_test.go @@ -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) } diff --git a/lite/static_test.go b/lite/static_test.go index d0a1e6859..3e4d59271 100644 --- a/lite/static_test.go +++ b/lite/static_test.go @@ -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) diff --git a/state/execution.go b/state/execution.go index d626ee0fc..786cc36d2 100644 --- a/state/execution.go +++ b/state/execution.go @@ -241,6 +241,7 @@ func (s *State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (* block.ValidatorsHash = s.Validators.Hash() block.AppHash = s.AppHash block.ConsensusHash = s.LastConsensusParams.Hash() + block.ResultsHash = s.LastResultHash return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes) } @@ -279,6 +280,9 @@ func (s *State) validateBlock(b *types.Block) error { 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.ResultsHash, s.LastResultHash) { + return fmt.Errorf("Wrong Block.Header.ResultsHash. Expected %X, got %v", s.LastResultHash, b.ResultsHash) + } // Validate block LastCommit. if b.Height == 1 { diff --git a/state/execution_test.go b/state/execution_test.go index a639d39a5..5cddfc7f8 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -67,6 +67,12 @@ 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.ResultsHash = []byte("wrong results hash") + err = state.ValidateBlock(block) + require.Error(t, err) } func TestApplyBlock(t *testing.T) { diff --git a/state/state.go b/state/state.go index 316a3161e..9ef734fc0 100644 --- a/state/state.go +++ b/state/state.go @@ -79,8 +79,9 @@ type State struct { LastHeightConsensusParamsChanged int64 // Store LastABCIResults along with hash - LastResults ABCIResults // TODO: remove?? - LastResultHash []byte + LastResults ABCIResults // TODO: remove?? + LastResultHash []byte // this is the one for the next block to propose + LastLastResultHash []byte // this verifies the last block? // The latest AppHash we've received from calling abci.Commit() AppHash []byte @@ -156,6 +157,9 @@ func (s *State) Copy() *State { AppHash: s.AppHash, + LastResults: s.LastResults, + LastResultHash: s.LastResultHash, + logger: s.logger, } } diff --git a/types/block.go b/types/block.go index 7a1ed04e9..ee0f54ea9 100644 --- a/types/block.go +++ b/types/block.go @@ -153,6 +153,7 @@ type Header struct { 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 + ResultsHash data.Bytes `json:"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.ResultsHash, }) } @@ -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.ResultsHash, indent, h.Hash()) } From d65234ed519f7347dbd9adc993cbeaed232a4116 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 22 Dec 2017 17:59:52 +0100 Subject: [PATCH 04/12] Add /block_results?height=H as rpc endpoint Expose it in rpc client Move ABCIResults into tendermint/types from tendermint/state --- rpc/client/httpclient.go | 9 +++++ rpc/client/interface.go | 1 + rpc/client/localclient.go | 4 ++ rpc/client/rpc_test.go | 10 ++++- rpc/core/blocks.go | 63 +++++++++++++++++++++++++++++ rpc/core/routes.go | 1 + rpc/core/types/responses.go | 5 +++ state/state.go | 79 +++++-------------------------------- state/state_test.go | 44 +++------------------ types/results.go | 70 ++++++++++++++++++++++++++++++++ types/results_test.go | 41 +++++++++++++++++++ 11 files changed, 217 insertions(+), 110 deletions(-) create mode 100644 types/results.go create mode 100644 types/results_test.go diff --git a/rpc/client/httpclient.go b/rpc/client/httpclient.go index 2ecfa7958..838fa5249 100644 --- a/rpc/client/httpclient.go +++ b/rpc/client/httpclient.go @@ -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) diff --git a/rpc/client/interface.go b/rpc/client/interface.go index 063d50e19..70cb4d951 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -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) diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index 18c6759de..71f25ef23 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -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) } diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index c32d08bd6..7d6bc8c38 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -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,6 +167,15 @@ 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(&apph) + require.Nil(err, "%d: %+v", i, err) + assert.Equal(apph, blockResults.Height) + if assert.Equal(1, len(blockResults.Results)) { + // check success code + assert.EqualValues(0, blockResults.Results[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??? diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 9d4098459..14d9e9fc3 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -312,3 +312,66 @@ 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 the latest block. +// +// Results are for the tx of the last block with the same index. +// Thus response.results[5] is the results of executing +// getBlock(h-1).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": 88, +// "results": [ +// { +// "code": 0, +// "data": "CAFE00F00D" +// }, +// { +// "code": 102, +// "data": "" +// } +// ] +// } +// ``` +func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) { + var height int64 + if heightPtr != 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") + } + } else { + height = blockStore.Height() + } + + // load the results + state := consensusState.GetState() + results, err := state.LoadResults(height) + if err != nil { + return nil, err + } + + res := &ctypes.ResultBlockResults{ + Height: height, + Results: results, + } + return res, nil +} diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 111c010a2..fb5a1fd36 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -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"), diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 3d1e7a218..31d86f94d 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -33,6 +33,11 @@ type ResultCommit struct { CanonicalCommit bool `json:"canonical"` } +type ResultBlockResults struct { + Height int64 `json:"height"` + Results types.ABCIResults `json:"results"` +} + // NewResultCommit is a helper to initialize the ResultCommit with // the embedded struct func NewResultCommit(header *types.Header, commit *types.Commit, diff --git a/state/state.go b/state/state.go index 9ef734fc0..be9c641c2 100644 --- a/state/state.go +++ b/state/state.go @@ -8,13 +8,10 @@ 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" @@ -79,9 +76,9 @@ type State struct { LastHeightConsensusParamsChanged int64 // Store LastABCIResults along with hash - LastResults ABCIResults // TODO: remove?? - LastResultHash []byte // this is the one for the next block to propose - LastLastResultHash []byte // this verifies the last block? + LastResults types.ABCIResults // TODO: remove?? + LastResultHash []byte // this is the one for the next block to propose + LastLastResultHash []byte // this verifies the last block? // The latest AppHash we've received from calling abci.Commit() AppHash []byte @@ -311,8 +308,8 @@ func (s *State) saveConsensusParamsInfo() { s.db.SetSync(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes()) } -// LoadResults loads the ABCIResults for a given height. -func (s *State) LoadResults(height int64) (ABCIResults, error) { +// LoadResults loads the types.ABCIResults for a given height. +func (s *State) LoadResults(height int64) (types.ABCIResults, error) { resInfo := s.loadResults(height) if resInfo == nil { return nil, ErrNoResultsForHeight{height} @@ -320,13 +317,13 @@ func (s *State) LoadResults(height int64) (ABCIResults, error) { return resInfo, nil } -func (s *State) loadResults(height int64) ABCIResults { +func (s *State) loadResults(height int64) types.ABCIResults { buf := s.db.Get(calcResultsKey(height)) if len(buf) == 0 { return nil } - v := new(ABCIResults) + v := new(types.ABCIResults) err := wire.ReadBinaryBytes(buf, v) if err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED @@ -396,7 +393,7 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ header.Time, nextValSet, nextParams, - NewResults(abciResponses.DeliverTx)) + types.NewResults(abciResponses.DeliverTx)) return nil } @@ -404,7 +401,7 @@ func (s *State) setBlockAndValidators(height int64, newTxs int64, blockID types.BlockID, blockTime time.Time, valSet *types.ValidatorSet, params types.ConsensusParams, - results ABCIResults) { + results types.ABCIResults) { s.LastBlockHeight = height s.LastBlockTotalTx += newTxs @@ -455,64 +452,6 @@ 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 -} - -// 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 -} - -//----------------------------------------------------------------------------- - // 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 84b8fb9d9..df4a2e1c6 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -279,40 +279,6 @@ 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) - } -} - // TestResultsSaveLoad tests saving and loading abci results. func TestResultsSaveLoad(t *testing.T) { tearDown, _, state := setupTestCase(t) @@ -324,17 +290,17 @@ func TestResultsSaveLoad(t *testing.T) { // height is implied index+2 // as block 1 is created from genesis added []*abci.ResponseDeliverTx - expected ABCIResults + expected types.ABCIResults }{ 0: { []*abci.ResponseDeliverTx{}, - ABCIResults{}, + types.ABCIResults{}, }, 1: { []*abci.ResponseDeliverTx{ {Code: 32, Data: []byte("Hello"), Log: "Huh?"}, }, - ABCIResults{ + types.ABCIResults{ {32, []byte("Hello")}, }}, 2: { @@ -346,13 +312,13 @@ func TestResultsSaveLoad(t *testing.T) { abci.KVPairString("build", "stuff"), }}, }, - ABCIResults{ + types.ABCIResults{ {383, []byte{}}, {0, []byte("Gotcha!")}, }}, 3: { nil, - ABCIResults{}, + types.ABCIResults{}, }, } diff --git a/types/results.go b/types/results.go new file mode 100644 index 000000000..208e26b39 --- /dev/null +++ b/types/results.go @@ -0,0 +1,70 @@ +package types + +import ( + "fmt" + + "golang.org/x/crypto/ripemd160" + + 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 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 +} + +// 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 +} diff --git a/types/results_test.go b/types/results_test.go new file mode 100644 index 000000000..88624e8c8 --- /dev/null +++ b/types/results_test.go @@ -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) + } +} From 73fb1c3a17fefe88b07408931eee020121301a55 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 25 Dec 2017 13:47:16 -0500 Subject: [PATCH 05/12] consolidate saveResults/SaveABCIResponses --- state/errors.go | 4 +- state/execution.go | 18 +++--- state/state.go | 86 ++++++++------------------ state/state_test.go | 147 ++++++++++++++++++++++---------------------- types/block.go | 2 +- types/results.go | 17 ++--- 6 files changed, 120 insertions(+), 154 deletions(-) diff --git a/state/errors.go b/state/errors.go index 1c0b76ab4..afb5737d7 100644 --- a/state/errors.go +++ b/state/errors.go @@ -42,7 +42,7 @@ type ( Height int64 } - ErrNoResultsForHeight struct { + ErrNoABCIResponsesForHeight struct { Height int64 } ) @@ -74,6 +74,6 @@ func (e ErrNoConsensusParamsForHeight) Error() string { return cmn.Fmt("Could not find consensus params for height #%d", e.Height) } -func (e ErrNoResultsForHeight) Error() string { +func (e ErrNoABCIResponsesForHeight) Error() string { return cmn.Fmt("Could not find results for height #%d", e.Height) } diff --git a/state/execution.go b/state/execution.go index 786cc36d2..024c3f469 100644 --- a/state/execution.go +++ b/state/execution.go @@ -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(), @@ -322,7 +324,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 diff --git a/state/state.go b/state/state.go index be9c641c2..f0f92715e 100644 --- a/state/state.go +++ b/state/state.go @@ -20,8 +20,7 @@ import ( // database keys var ( - stateKey = []byte("stateKey") - abciResponsesKey = []byte("abciResponsesKey") + stateKey = []byte("stateKey") ) func calcValidatorsKey(height int64) []byte { @@ -32,8 +31,8 @@ func calcConsensusParamsKey(height int64) []byte { return []byte(cmn.Fmt("consensusParamsKey:%v", height)) } -func calcResultsKey(height int64) []byte { - return []byte(cmn.Fmt("resultsKey:%v", height)) +func calcABCIResponsesKey(height int64) []byte { + return []byte(cmn.Fmt("abciResponsesKey:%v", height)) } //----------------------------------------------------------------------------- @@ -75,10 +74,8 @@ type State struct { LastConsensusParams types.ConsensusParams LastHeightConsensusParamsChanged int64 - // Store LastABCIResults along with hash - LastResults types.ABCIResults // TODO: remove?? - LastResultHash []byte // this is the one for the next block to propose - LastLastResultHash []byte // this verifies the last block? + // Merkle root of the results from executing prev block + LastResultHash []byte // The latest AppHash we've received from calling abci.Commit() AppHash []byte @@ -154,7 +151,6 @@ func (s *State) Copy() *State { AppHash: s.AppHash, - LastResults: s.LastResults, LastResultHash: s.LastResultHash, logger: s.logger, @@ -168,23 +164,23 @@ func (s *State) Save() { s.saveValidatorsInfo() s.saveConsensusParamsInfo() - s.saveResults() s.db.SetSync(stateKey, s.Bytes()) } // 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) @@ -197,7 +193,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. @@ -308,39 +304,6 @@ func (s *State) saveConsensusParamsInfo() { s.db.SetSync(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes()) } -// LoadResults loads the types.ABCIResults for a given height. -func (s *State) LoadResults(height int64) (types.ABCIResults, error) { - resInfo := s.loadResults(height) - if resInfo == nil { - return nil, ErrNoResultsForHeight{height} - } - return resInfo, nil -} - -func (s *State) loadResults(height int64) types.ABCIResults { - buf := s.db.Get(calcResultsKey(height)) - if len(buf) == 0 { - return nil - } - - v := new(types.ABCIResults) - err := wire.ReadBinaryBytes(buf, v) - if err != nil { - // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED - cmn.Exit(cmn.Fmt(`LoadResults: Data has been corrupted or its spec has changed: - %v\n`, err)) - } - return *v -} - -// saveResults persists the results for the last block to disk. -// It should be called from s.Save(), right before the state itself is persisted. -func (s *State) saveResults() { - nextHeight := s.LastBlockHeight + 1 - results := s.LastResults - s.db.SetSync(calcResultsKey(nextHeight), results.Bytes()) -} - // Equals returns true if the States are identical. func (s *State) Equals(s2 *State) bool { return bytes.Equal(s.Bytes(), s2.Bytes()) @@ -393,7 +356,7 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ header.Time, nextValSet, nextParams, - types.NewResults(abciResponses.DeliverTx)) + abciResponses.ResultsHash()) return nil } @@ -401,7 +364,7 @@ func (s *State) setBlockAndValidators(height int64, newTxs int64, blockID types.BlockID, blockTime time.Time, valSet *types.ValidatorSet, params types.ConsensusParams, - results types.ABCIResults) { + resultsHash []byte) { s.LastBlockHeight = height s.LastBlockTotalTx += newTxs @@ -414,8 +377,7 @@ func (s *State) setBlockAndValidators(height int64, s.LastConsensusParams = s.ConsensusParams s.ConsensusParams = params - s.LastResults = results - s.LastResultHash = results.Hash() + s.LastResultHash = resultsHash } // GetValidators returns the last and current validator sets. @@ -425,23 +387,18 @@ func (s *State) GetValidators() (last *types.ValidatorSet, current *types.Valida //------------------------------------------------------------------------ -// ABCIResponses retains the responses of the various ABCI calls during block processing. +// ABCIResponses retains the deterministic components of the responses +// of the various ABCI calls during block processing. // It is persisted to disk 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, } } @@ -450,6 +407,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 diff --git a/state/state_test.go b/state/state_test.go index df4a2e1c6..bcd2ba119 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -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) @@ -279,73 +348,6 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { } } -// TestResultsSaveLoad tests saving and loading abci results. -func TestResultsSaveLoad(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 + 2) - res, err := state.LoadResults(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 - header, parts, responses := makeHeaderPartsResults(state, h, tc.added) - state.SetBlockAndValidators(header, parts, responses) - state.Save() - } - - // query all before, should return expected value - for i, tc := range cases { - h := int64(i + 2) - res, err := state.LoadResults(h) - assert.NoError(err, "%d", i) - assert.Equal(tc.expected, res, "%d", i) - } -} - func makeParams(blockBytes, blockTx, blockGas, txBytes, txGas, partSize int) types.ConsensusParams { @@ -488,7 +490,6 @@ func makeHeaderPartsResponsesValPubKeyChange(state *State, height int64, block := makeBlock(state, height) abciResponses := &ABCIResponses{ - Height: height, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{}}, } @@ -533,7 +534,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 @@ -549,7 +549,6 @@ func makeHeaderPartsResults(state *State, height int64, block := makeBlock(state, height) abciResponses := &ABCIResponses{ - Height: height, DeliverTx: results, EndBlock: &abci.ResponseEndBlock{}, } diff --git a/types/block.go b/types/block.go index ee0f54ea9..f0ae57c3b 100644 --- a/types/block.go +++ b/types/block.go @@ -149,7 +149,7 @@ 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 + // 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 diff --git a/types/results.go b/types/results.go index 208e26b39..264234515 100644 --- a/types/results.go +++ b/types/results.go @@ -13,14 +13,13 @@ import ( //----------------------------------------------------------------------------- -// ABCIResult is just the essential info to prove -// success/failure of a DeliverTx +// ABCIResult is the deterministic component of a ResponseDeliverTx. type ABCIResult struct { Code uint32 `json:"code"` Data data.Bytes `json:"data"` } -// Hash creates a canonical json hash of the ABCIResult +// Hash returns the 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) @@ -36,14 +35,18 @@ type ABCIResults []ABCIResult 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, - } + 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) From 4171bd3bae9d655e3212ef0786ce1827580ac459 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 25 Dec 2017 13:54:19 -0500 Subject: [PATCH 06/12] fixes --- consensus/replay.go | 5 ++++- rpc/client/rpc_test.go | 10 ++++------ rpc/core/blocks.go | 10 +++++----- rpc/core/types/responses.go | 5 +++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/consensus/replay.go b/consensus/replay.go index 16d12e343..57a9dd4cc 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -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) diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 7d6bc8c38..9b956803e 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -168,17 +168,15 @@ func TestAppCalls(t *testing.T) { assert.EqualValues(apph, block.BlockMeta.Header.Height) // now check the results - blockResults, err := c.BlockResults(&apph) + blockResults, err := c.BlockResults(&txh) require.Nil(err, "%d: %+v", i, err) - assert.Equal(apph, blockResults.Height) - if assert.Equal(1, len(blockResults.Results)) { + assert.Equal(txh, blockResults.Height) + if assert.Equal(1, len(blockResults.Results.DeliverTx)) { // check success code - assert.EqualValues(0, blockResults.Results[0].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) diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 14d9e9fc3..81d9469e9 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -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' @@ -314,11 +315,10 @@ func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) { } // BlockResults gets ABCIResults at a given height. -// If no height is provided, it will fetch the latest block. +// If no height is provided, it will fetch results for the latest block. // -// Results are for the tx of the last block with the same index. -// Thus response.results[5] is the results of executing -// getBlock(h-1).Txs[5] +// 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' @@ -364,7 +364,7 @@ func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) { // load the results state := consensusState.GetState() - results, err := state.LoadResults(height) + results, err := state.LoadABCIResponses(height) if err != nil { return nil, err } diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 31d86f94d..dae7c0046 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -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" ) @@ -34,8 +35,8 @@ type ResultCommit struct { } type ResultBlockResults struct { - Height int64 `json:"height"` - Results types.ABCIResults `json:"results"` + Height int64 `json:"height"` + Results *state.ABCIResponses `json:"results"` } // NewResultCommit is a helper to initialize the ResultCommit with From 801e3dfacf152e7fee5bbf4cb554a5145b267e60 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 26 Dec 2017 19:37:42 -0500 Subject: [PATCH 07/12] rpc: getHeight helper function --- rpc/core/blocks.go | 66 +++++++++++++++++-------------------------- rpc/core/consensus.go | 7 ++--- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 81d9469e9..16d0612d4 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -193,19 +193,9 @@ 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") + height, _, err := getHeight(blockStore, heightPtr) + if err != nil { + return nil, err } blockMeta := blockStore.LoadBlockMeta(height) @@ -284,20 +274,9 @@ 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, storeHeight, err := getHeight(blockStore, heightPtr) + if err != nil { + return nil, err } header := blockStore.LoadBlockMeta(height).Header @@ -334,7 +313,7 @@ func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) { // // ```json // { -// "height": 88, +// "height": 10, // "results": [ // { // "code": 0, @@ -348,18 +327,9 @@ func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) { // } // ``` func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) { - var height int64 - if heightPtr != 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") - } - } else { - height = blockStore.Height() + height, _, err := getHeight(blockStore, heightPtr) + if err != nil { + return nil, err } // load the results @@ -375,3 +345,19 @@ func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) { } return res, nil } + +func getHeight(blockStore types.BlockStore, heightPtr *int64) (reqHeight int64, storeHeight int64, err error) { + storeHeight = blockStore.Height() + if heightPtr != nil { + reqHeight = *heightPtr + if reqHeight <= 0 { + return 0, 0, fmt.Errorf("Height must be greater than 0") + } + if reqHeight > storeHeight { + return 0, 0, fmt.Errorf("Height must be less than or equal to the current blockchain height") + } + } else { + reqHeight = blockStore.Height() + } + return reqHeight, storeHeight, nil +} diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 755f15894..26b9606de 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -43,12 +43,11 @@ import ( // } // ``` func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { - if heightPtr == nil { - blockHeight, validators := consensusState.GetValidators() - return &ctypes.ResultValidators{blockHeight, validators}, nil + height, _, err := getHeight(blockStore, heightPtr) + if err != nil { + return nil, err } - height := *heightPtr state := consensusState.GetState() validators, err := state.LoadValidators(height) if err != nil { From 028ee58580e056c4b6ffa79af152dcb890254f9c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 26 Dec 2017 19:53:26 -0500 Subject: [PATCH 08/12] call it LastResultsHash --- lite/helpers.go | 10 +++++----- state/execution.go | 6 +++--- state/execution_test.go | 2 +- state/state.go | 10 +++++----- state/state_test.go | 2 -- types/block.go | 12 ++++++------ types/results.go | 13 +++---------- 7 files changed, 23 insertions(+), 32 deletions(-) diff --git a/lite/helpers.go b/lite/helpers.go index 01ed4a84a..9c015a08e 100644 --- a/lite/helpers.go +++ b/lite/helpers.go @@ -120,11 +120,11 @@ 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, - ResultsHash: resHash, + ValidatorsHash: vals.Hash(), + DataHash: txs.Hash(), + AppHash: appHash, + ConsensusHash: consHash, + LastResultsHash: resHash, } } diff --git a/state/execution.go b/state/execution.go index 024c3f469..cba139963 100644 --- a/state/execution.go +++ b/state/execution.go @@ -243,7 +243,7 @@ func (s *State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (* block.ValidatorsHash = s.Validators.Hash() block.AppHash = s.AppHash block.ConsensusHash = s.LastConsensusParams.Hash() - block.ResultsHash = s.LastResultHash + block.LastResultsHash = s.LastResultsHash return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes) } @@ -282,8 +282,8 @@ func (s *State) validateBlock(b *types.Block) error { 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.ResultsHash, s.LastResultHash) { - return fmt.Errorf("Wrong Block.Header.ResultsHash. Expected %X, got %v", s.LastResultHash, b.ResultsHash) + if !bytes.Equal(b.LastResultsHash, s.LastResultsHash) { + return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", s.LastResultsHash, b.LastResultsHash) } // Validate block LastCommit. diff --git a/state/execution_test.go b/state/execution_test.go index 5cddfc7f8..be072bd6c 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -70,7 +70,7 @@ func TestValidateBlock(t *testing.T) { // wrong results hash fails block = makeBlock(state, 1) - block.ResultsHash = []byte("wrong results hash") + block.LastResultsHash = []byte("wrong results hash") err = state.ValidateBlock(block) require.Error(t, err) } diff --git a/state/state.go b/state/state.go index f0f92715e..164718226 100644 --- a/state/state.go +++ b/state/state.go @@ -75,7 +75,7 @@ type State struct { LastHeightConsensusParamsChanged int64 // Merkle root of the results from executing prev block - LastResultHash []byte + LastResultsHash []byte // The latest AppHash we've received from calling abci.Commit() AppHash []byte @@ -151,7 +151,7 @@ func (s *State) Copy() *State { AppHash: s.AppHash, - LastResultHash: s.LastResultHash, + LastResultsHash: s.LastResultsHash, logger: s.logger, } @@ -377,7 +377,7 @@ func (s *State) setBlockAndValidators(height int64, s.LastConsensusParams = s.ConsensusParams s.ConsensusParams = params - s.LastResultHash = resultsHash + s.LastResultsHash = resultsHash } // GetValidators returns the last and current validator sets. @@ -387,9 +387,9 @@ func (s *State) GetValidators() (last *types.ValidatorSet, current *types.Valida //------------------------------------------------------------------------ -// ABCIResponses retains the deterministic components of the responses +// ABCIResponses retains the responses // of the various ABCI calls during block processing. -// It is persisted to disk before calling Commit. +// It is persisted to disk for each height before calling Commit. type ABCIResponses struct { DeliverTx []*abci.ResponseDeliverTx EndBlock *abci.ResponseEndBlock diff --git a/state/state_test.go b/state/state_test.go index bcd2ba119..b1adc0d02 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -422,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) @@ -512,7 +511,6 @@ func makeHeaderPartsResponsesValPowerChange(state *State, height int64, block := makeBlock(state, height) abciResponses := &ABCIResponses{ - Height: height, EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{}}, } diff --git a/types/block.go b/types/block.go index f0ae57c3b..29775466e 100644 --- a/types/block.go +++ b/types/block.go @@ -150,10 +150,10 @@ type Header struct { DataHash data.Bytes `json:"data_hash"` // transactions // 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 - ResultsHash data.Bytes `json:"results_hash"` // root hash of all results from the txs from the previous 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. @@ -174,7 +174,7 @@ func (h *Header) Hash() data.Bytes { "Validators": h.ValidatorsHash, "App": h.AppHash, "Consensus": h.ConsensusHash, - "Results": h.ResultsHash, + "Results": h.LastResultsHash, }) } @@ -208,7 +208,7 @@ func (h *Header) StringIndented(indent string) string { indent, h.ValidatorsHash, indent, h.AppHash, indent, h.ConsensusHash, - indent, h.ResultsHash, + indent, h.LastResultsHash, indent, h.Hash()) } diff --git a/types/results.go b/types/results.go index 264234515..29420fbc0 100644 --- a/types/results.go +++ b/types/results.go @@ -1,10 +1,6 @@ package types import ( - "fmt" - - "golang.org/x/crypto/ripemd160" - abci "github.com/tendermint/abci/types" wire "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" @@ -14,18 +10,15 @@ import ( //----------------------------------------------------------------------------- // 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 json hash of the ABCIResult +// Hash returns the canonical 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) + return wire.BinaryRipemd160(a) } // ABCIResults wraps the deliver tx results to return a proof From 051c2701ab9e2c315224d093effb4992557273ef Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 26 Dec 2017 19:56:39 -0500 Subject: [PATCH 09/12] remove LastConsensusParams --- state/execution.go | 6 +++--- state/state.go | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/state/execution.go b/state/execution.go index cba139963..b989e0291 100644 --- a/state/execution.go +++ b/state/execution.go @@ -242,7 +242,7 @@ 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) @@ -279,8 +279,8 @@ func (s *State) validateBlock(b *types.Block) error { 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) diff --git a/state/state.go b/state/state.go index 164718226..0dd105cbe 100644 --- a/state/state.go +++ b/state/state.go @@ -71,7 +71,6 @@ 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 @@ -146,7 +145,6 @@ func (s *State) Copy() *State { LastHeightValidatorsChanged: s.LastHeightValidatorsChanged, ConsensusParams: s.ConsensusParams, - LastConsensusParams: s.LastConsensusParams, LastHeightConsensusParamsChanged: s.LastHeightConsensusParamsChanged, AppHash: s.AppHash, @@ -374,7 +372,6 @@ func (s *State) setBlockAndValidators(height int64, s.LastValidators = s.Validators.Copy() s.Validators = valSet - s.LastConsensusParams = s.ConsensusParams s.ConsensusParams = params s.LastResultsHash = resultsHash @@ -501,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, From bfcb40bf6b04607700bd143ca1856f3dd58339fc Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 26 Dec 2017 20:00:45 -0500 Subject: [PATCH 10/12] validate block.ValidatorsHash --- state/execution.go | 14 ++++++++++---- state/execution_test.go | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/state/execution.go b/state/execution.go index b989e0291..dc38ac988 100644 --- a/state/execution.go +++ b/state/execution.go @@ -249,11 +249,12 @@ func (s *State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (* } 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) } @@ -268,14 +269,16 @@ 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) } @@ -285,6 +288,9 @@ func (s *State) validateBlock(b *types.Block) error { 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. if b.Height == 1 { diff --git a/state/execution_test.go b/state/execution_test.go index be072bd6c..25d32cbbc 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -73,6 +73,12 @@ func TestValidateBlock(t *testing.T) { 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) { From 336c2f4fe1ed0f7b946cb088562cdf642b3c7d27 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 26 Dec 2017 20:08:25 -0500 Subject: [PATCH 11/12] rpc: fix getHeight --- rpc/core/blocks.go | 27 ++++++++++++++------------- rpc/core/consensus.go | 3 ++- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 16d0612d4..43edcd356 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -193,7 +193,8 @@ func BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, e // } // ``` func Block(heightPtr *int64) (*ctypes.ResultBlock, error) { - height, _, err := getHeight(blockStore, heightPtr) + storeHeight := blockStore.Height() + height, err := getHeight(storeHeight, heightPtr) if err != nil { return nil, err } @@ -274,7 +275,8 @@ func Block(heightPtr *int64) (*ctypes.ResultBlock, error) { // } // ``` func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) { - height, storeHeight, err := getHeight(blockStore, heightPtr) + storeHeight := blockStore.Height() + height, err := getHeight(storeHeight, heightPtr) if err != nil { return nil, err } @@ -327,7 +329,8 @@ func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) { // } // ``` func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) { - height, _, err := getHeight(blockStore, heightPtr) + storeHeight := blockStore.Height() + height, err := getHeight(storeHeight, heightPtr) if err != nil { return nil, err } @@ -346,18 +349,16 @@ func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) { return res, nil } -func getHeight(blockStore types.BlockStore, heightPtr *int64) (reqHeight int64, storeHeight int64, err error) { - storeHeight = blockStore.Height() +func getHeight(storeHeight int64, heightPtr *int64) (int64, error) { if heightPtr != nil { - reqHeight = *heightPtr - if reqHeight <= 0 { - return 0, 0, fmt.Errorf("Height must be greater than 0") + height := *heightPtr + if height <= 0 { + return 0, fmt.Errorf("Height must be greater than 0") } - if reqHeight > storeHeight { - return 0, 0, fmt.Errorf("Height must be less than or equal to the current blockchain height") + if height > storeHeight { + return 0, fmt.Errorf("Height must be less than or equal to the current blockchain height") } - } else { - reqHeight = blockStore.Height() + return height, nil } - return reqHeight, storeHeight, nil + return storeHeight, nil } diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 26b9606de..e358c4874 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -43,7 +43,8 @@ import ( // } // ``` func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { - height, _, err := getHeight(blockStore, heightPtr) + storeHeight := blockStore.Height() + height, err := getHeight(storeHeight, heightPtr) if err != nil { return nil, err } From f80f6445a60fffc146eae16abf686ce3ae5546b0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 26 Dec 2017 20:15:07 -0500 Subject: [PATCH 12/12] fix test --- consensus/replay_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index fbab03d20..43c2a4699 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -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()