Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: invalid txs_results returned for legacy ABCI responses #3031

Merged
merged 61 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
e83d3b2
refactor logic to retrieve legacy ABCI responses
andynog May 6, 2024
0740151
return AppHash if not nil
andynog May 6, 2024
85353d1
just adding AppHash to the response
andynog May 6, 2024
617a788
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 7, 2024
d769a1f
fixing tests (#3002)
andynog May 7, 2024
2d22c60
added changelog (#3002)
andynog May 7, 2024
49d708e
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 8, 2024
e4c4133
error handling update
andynog May 8, 2024
1796bac
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 8, 2024
29a63b4
refactoring errors added (#3002)
andynog May 8, 2024
b4fb5c7
introducing a test for converting messages using different api (proto…
andynog May 8, 2024
057a842
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 9, 2024
e2762de
adding binary test file (#3002)
andynog May 10, 2024
b664792
adding more tests (#3002)
andynog May 13, 2024
dfb2a50
updating test file (#3002)
andynog May 13, 2024
1d3ba09
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 13, 2024
0d991d7
Update .changelog/unreleased/bug-fixes/3002-invalid-txs-results.md
andynog May 14, 2024
5ac46a9
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 14, 2024
22830bf
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 14, 2024
62b6d99
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 15, 2024
4cb5a80
move test to state, no binary test (#3002)
andynog May 20, 2024
c20435d
added one more test (#3002)
andynog May 20, 2024
64b6702
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 21, 2024
a4eab7b
fixing tests, use ABCIResponses (#3002)
andynog May 21, 2024
a7715cc
Merge branch 'andy/3002-invalid-txs-results' of github.com:cometbft/c…
andynog May 21, 2024
fad89e7
adding more tests (#3002)
andynog May 21, 2024
57d70fc
adding use case that conversion from v1beta2 to v1 does not error (#3…
andynog May 23, 2024
7641dbf
moving tests to own file (#3002)
andynog May 23, 2024
1a6f328
changing the logic to accomodate both error and apphash nil use case …
andynog May 23, 2024
df88eda
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 23, 2024
8cab048
Merge branch 'main' into andy/3002-invalid-txs-results
andynog May 24, 2024
b6a8932
Update state/compatibility_test.go
andynog May 28, 2024
0f32ced
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 6, 2024
5e28c0e
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 7, 2024
c7f100a
adding mock store to support 'legacy' methods and unit test to use th…
andynog Jun 7, 2024
d54f2a0
added test with null fields and revert fixes to prove test doesn't pa…
andynog Jun 7, 2024
0e4d8c1
Use only one store; more exhaustive checks
sergio-mena Jun 7, 2024
74f8351
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 12, 2024
5051932
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 13, 2024
ff34cca
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 17, 2024
09a0d28
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 18, 2024
009f425
adding fix to txs results and better logic to handle nil and add attr…
andynog Jun 18, 2024
49b3984
Merge branch 'andy/3002-invalid-txs-results' of github.com:cometbft/c…
andynog Jun 18, 2024
6e47324
Update state/store.go
andynog Jun 18, 2024
c7e2fc0
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 25, 2024
9701da4
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 26, 2024
2071d2c
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 27, 2024
c87ebc3
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jun 28, 2024
728dbf6
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jul 1, 2024
4ce283e
Update state/store.go
melekes Jul 2, 2024
6768a58
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jul 2, 2024
653c375
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jul 3, 2024
ee78b7c
adding test from v1beta3 to v1 (#3002)
andynog Jul 3, 2024
ebbe640
couple more tests (#3002)
andynog Jul 3, 2024
07a2600
added more coverage to tests (#3002)
andynog Jul 3, 2024
6b53cf1
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jul 4, 2024
0a8dc20
Update state/compatibility_test.go
andynog Jul 5, 2024
519475a
Merge branch 'main' into andy/3002-invalid-txs-results
andynog Jul 5, 2024
d78f8f7
adding some extra equality tests (#3031)
andynog Jul 5, 2024
6c9333a
re-adding equality test to match number expected (#3031)
andynog Jul 5, 2024
2f3678c
improving error logging and return (#3031)
andynog Jul 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/unreleased/bug-fixes/3002-invalid-txs-results.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- `[rpc]` Fix an issue where a legacy ABCI response, created on `v0.37` or before, is not returned properly in `v0.38` and up
on the `/block_results` RPC endpoint.
([\#3002](https://github.com/cometbft/cometbft/issues/3002))
1 change: 1 addition & 0 deletions rpc/core/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ func (env *Environment) BlockResults(_ *rpctypes.Context, heightPtr *int64) (*ct
FinalizeBlockEvents: results.Events,
ValidatorUpdates: results.ValidatorUpdates,
ConsensusParamUpdates: results.ConsensusParamUpdates,
AppHash: results.AppHash,
andynog marked this conversation as resolved.
Show resolved Hide resolved
}, nil
}

Expand Down
2 changes: 2 additions & 0 deletions rpc/core/blocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func TestBlockResults(t *testing.T) {
{Code: 0, Data: []byte{0x02}, Log: "ok"},
{Code: 1, Log: "not ok"},
},
AppHash: make([]byte, 1),
}

env := &Environment{}
Expand Down Expand Up @@ -100,6 +101,7 @@ func TestBlockResults(t *testing.T) {
FinalizeBlockEvents: results.Events,
ValidatorUpdates: results.ValidatorUpdates,
ConsensusParamUpdates: results.ConsensusParamUpdates,
AppHash: make([]byte, 1),
}},
}

Expand Down
645 changes: 645 additions & 0 deletions state/compatibility_test.go

Large diffs are not rendered by default.

28 changes: 22 additions & 6 deletions state/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import (
"fmt"
)

var (
ErrFinalizeBlockResponsesNotPersisted = errors.New("node is not persisting finalize block responses")
ErrPrunerCannotLowerRetainHeight = errors.New("cannot set a height lower than previously requested - heights might have already been pruned")
ErrInvalidRetainHeight = errors.New("retain height cannot be less or equal than 0")
)

type (
ErrInvalidBlock error
ErrProxyAppConn error
Expand Down Expand Up @@ -74,6 +80,14 @@ type (
ErrCannotLoadState struct {
Err error
}

ErrABCIResponseResponseUnmarshalForHeight struct {
Height int64
}

ErrABCIResponseCorruptedOrSpecChangeForHeight struct {
Height int64
}
)

func (e ErrUnknownBlock) Error() string {
Expand Down Expand Up @@ -126,6 +140,14 @@ func (e ErrNoABCIResponsesForHeight) Error() string {
return fmt.Sprintf("could not find results for height #%d", e.Height)
}

func (e ErrABCIResponseResponseUnmarshalForHeight) Error() string {
return fmt.Sprintf("could not decode results for height %d", e.Height)
}

func (e ErrABCIResponseCorruptedOrSpecChangeForHeight) Error() string {
return fmt.Sprintf("could not retrieve results for height %d", e.Height)
}

func (e ErrPrunerFailedToGetRetainHeight) Error() string {
return fmt.Sprintf("pruner failed to get existing %s retain height: %s", e.Which, e.Err.Error())
}
Expand Down Expand Up @@ -158,12 +180,6 @@ func (e ErrFailedToPruneStates) Unwrap() error {
return e.Err
}

var (
ErrFinalizeBlockResponsesNotPersisted = errors.New("node is not persisting finalize block responses")
ErrPrunerCannotLowerRetainHeight = errors.New("cannot set a height lower than previously requested - heights might have already been pruned")
ErrInvalidRetainHeight = errors.New("retain height cannot be less or equal than 0")
)

func (e ErrCannotLoadState) Error() string {
return fmt.Sprintf("cannot load state: %v", e.Err)
}
Expand Down
19 changes: 14 additions & 5 deletions state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ import (

// setupTestCase does setup common to all test cases.
func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, sm.State) {
t.Helper()
tearDown, stateDB, state, _ := setupTestCaseWithStore(t)
return tearDown, stateDB, state
}

// setupTestCase does setup common to all test cases.
func setupTestCaseWithStore(t *testing.T) (func(t *testing.T), dbm.DB, sm.State, sm.Store) {
t.Helper()
config := test.ResetTestRoot("state_")
dbType := dbm.BackendType(config.DBBackend)
Expand All @@ -41,7 +48,7 @@ func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, sm.State) {
os.RemoveAll(config.RootDir)
}

return tearDown, stateDB, state
return tearDown, stateDB, state, stateStore
}

// TestStateCopy tests the correct copying behavior of State.
Expand Down Expand Up @@ -121,6 +128,8 @@ func TestFinalizeBlockResponsesSaveLoad1(t *testing.T) {
abci.NewValidatorUpdate(ed25519.GenPrivKey().PubKey(), 10),
}

abciResponses.AppHash = make([]byte, 1)

err := stateStore.SaveFinalizeBlockResponse(block.Height, abciResponses)
require.NoError(t, err)
loadedABCIResponses, err := stateStore.LoadFinalizeBlockResponse(block.Height)
Expand Down Expand Up @@ -524,7 +533,7 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
_, updatedVal2 := updatedState3.NextValidators.GetByAddress(val2PubKey.Address())

// 2. Scale
// old prios: v1(10):-38, v2(1):39
// old prios: cryptov1(10):-38, v2(1):39
wantVal1Prio = prevVal1.ProposerPriority
wantVal2Prio = prevVal2.ProposerPriority
// scale to diffMax = 22 = 2 * tvp, diff=39-(-38)=77
Expand All @@ -533,14 +542,14 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
dist := wantVal2Prio - wantVal1Prio
// ratio := (dist + 2*totalPower - 1) / 2*totalPower = 98/22 = 4
ratio := (dist + 2*totalPower - 1) / (2 * totalPower)
// v1(10):-38/4, v2(1):39/4
// cryptov1(10):-38/4, v2(1):39/4
wantVal1Prio /= ratio // -9
wantVal2Prio /= ratio // 9

// 3. Center - noop
// 4. IncrementProposerPriority() ->
// v1(10):-9+10, v2(1):9+1 -> v2 proposer so subsract tvp(11)
// v1(10):1, v2(1):-1
// cryptov1(10):-9+10, v2(1):9+1 -> v2 proposer so subsract tvp(11)
// cryptov1(10):1, v2(1):-1
wantVal2Prio += updatedVal2.VotingPower // 10 -> prop
wantVal1Prio += updatedVal1.VotingPower // 1
wantVal2Prio -= totalPower // -1
Expand Down
75 changes: 61 additions & 14 deletions state/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ type Store interface {
LoadValidators(height int64) (*types.ValidatorSet, error)
// LoadFinalizeBlockResponse loads the abciResponse for a given height
LoadFinalizeBlockResponse(height int64) (*abci.FinalizeBlockResponse, error)
// LoadLastABCIResponse loads the last abciResponse for a given height
// LoadLastFinalizeBlockResponse loads the last abciResponse for a given height
LoadLastFinalizeBlockResponse(height int64) (*abci.FinalizeBlockResponse, error)
// LoadConsensusParams loads the consensus params for a given height
LoadConsensusParams(height int64) (types.ConsensusParams, error)
Expand Down Expand Up @@ -653,14 +653,22 @@ func (store dbStore) LoadFinalizeBlockResponse(height int64) (*abci.FinalizeBloc

resp := new(abci.FinalizeBlockResponse)
err = resp.Unmarshal(buf)
if err != nil {
// Check for an error or if the resp.AppHash is nil if so
// this means the unmarshalling should be a LegacyABCIResponses
// Depending on a source message content (serialized as ABCIResponses)
// there are instances where it can be deserialized as a FinalizeBlockResponse
// without causing an error. But the values will not be deserialized properly
// and, it will contain zero values, and one of them is an AppHash == nil
// This can be verified in the /state/compatibility_test.go file
if err != nil || resp.AppHash == nil {
// The data might be of the legacy ABCI response type, so
// we try to unmarshal that
legacyResp := new(cmtstate.LegacyABCIResponses)
rerr := legacyResp.Unmarshal(buf)
if rerr != nil {
cmtos.Exit(fmt.Sprintf(`LoadFinalizeBlockResponse: Data has been corrupted or its spec has
changed: %v\n`, err))
if err := legacyResp.Unmarshal(buf); err != nil {
// only return an error, this method is only invoked through the `/block_results` not for state logic and
// some tests, so no need to exit cometbft if there's an error, just return it.
store.Logger.Debug("failed to unmarshall FinalizeBlockResponse (also tried as legacy ABCI response)", "error", err)
return nil, ErrABCIResponseCorruptedOrSpecChangeForHeight{Height: height}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if we don't break any API (not sure) it'd be good to include the error inside this error, so the caller can print it if needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added unwrapped error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also logging as Error() not Debug()

}
// The state store contains the old format. Migrate to
// the new FinalizeBlockResponse format. Note that the
Expand All @@ -670,10 +678,11 @@ func (store dbStore) LoadFinalizeBlockResponse(height int64) (*abci.FinalizeBloc

// TODO: ensure that buf is completely read.

// Otherwise return the FinalizeBlockResponse
return resp, nil
}

// LoadLastFinalizeBlockResponses loads the FinalizeBlockResponses from the most recent height.
// LoadLastFinalizeBlockResponse loads the FinalizeBlockResponses from the most recent height.
// The height parameter is used to ensure that the response corresponds to the latest height.
// If not, an error is returned.
//
Expand Down Expand Up @@ -1088,14 +1097,52 @@ func min(a int64, b int64) int64 {
// responseFinalizeBlockFromLegacy is a convenience function that takes the old abci responses and morphs
// it to the finalize block response. Note that the app hash is missing.
func responseFinalizeBlockFromLegacy(legacyResp *cmtstate.LegacyABCIResponses) *abci.FinalizeBlockResponse {
return &abci.FinalizeBlockResponse{
TxResults: legacyResp.DeliverTxs,
ValidatorUpdates: legacyResp.EndBlock.ValidatorUpdates,
ConsensusParamUpdates: legacyResp.EndBlock.ConsensusParamUpdates,
Events: append(legacyResp.BeginBlock.Events, legacyResp.EndBlock.Events...),
// NOTE: AppHash is missing in the response but will
// be caught and filled in consensus/replay.go
var response abci.FinalizeBlockResponse
events := make([]abci.Event, 0)

if legacyResp.DeliverTxs != nil {
response.TxResults = legacyResp.DeliverTxs
}

// Check for begin block and end block and only append events or assign values if they are not nil
if legacyResp.BeginBlock != nil {
if legacyResp.BeginBlock.Events != nil {
// Add BeginBlock attribute to BeginBlock events
for idx := range legacyResp.BeginBlock.Events {
legacyResp.BeginBlock.Events[idx].Attributes = append(legacyResp.BeginBlock.Events[idx].Attributes, abci.EventAttribute{
Key: "mode",
Value: "BeginBlock",
Index: false,
})
}
events = append(events, legacyResp.BeginBlock.Events...)
}
}
if legacyResp.EndBlock != nil {
if legacyResp.EndBlock.ValidatorUpdates != nil {
response.ValidatorUpdates = legacyResp.EndBlock.ValidatorUpdates
}
if legacyResp.EndBlock.ConsensusParamUpdates != nil {
response.ConsensusParamUpdates = legacyResp.EndBlock.ConsensusParamUpdates
}
if legacyResp.EndBlock.Events != nil {
// Add EndBlock attribute to BeginBlock events
for idx := range legacyResp.EndBlock.Events {
legacyResp.EndBlock.Events[idx].Attributes = append(legacyResp.EndBlock.Events[idx].Attributes, abci.EventAttribute{
Key: "mode",
Value: "EndBlock",
Index: false,
})
}
events = append(events, legacyResp.EndBlock.Events...)
}
}

response.Events = events

// NOTE: AppHash is missing in the response but will
// be caught and filled in consensus/replay.go
return &response
}

// ----- Util.
Expand Down
6 changes: 5 additions & 1 deletion state/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ func TestPruneStates(t *testing.T) {
{Data: []byte{2}},
{Data: []byte{3}},
},
AppHash: make([]byte, 1),
})
require.NoError(t, err)
}
Expand Down Expand Up @@ -375,6 +376,7 @@ func TestABCIResPruningStandalone(t *testing.T) {
TxResults: []*abci.ExecTxResult{
{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
},
AppHash: make([]byte, 1),
}
_, bs, txIndexer, blockIndexer, callbackF, stateStore := makeStateAndBlockStoreAndIndexers()
defer callbackF()
Expand Down Expand Up @@ -468,6 +470,7 @@ func TestFinalizeBlockResponsePruning(t *testing.T) {
TxResults: []*abci.ExecTxResult{
{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
},
AppHash: make([]byte, 1),
}
state, bs, txIndexer, blockIndexer, callbackF, stateStore := makeStateAndBlockStoreAndIndexers()
defer callbackF()
Expand Down Expand Up @@ -526,6 +529,7 @@ func TestLastFinalizeBlockResponses(t *testing.T) {
TxResults: []*abci.ExecTxResult{
{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
},
AppHash: make([]byte, 1),
}

stateDB = dbm.NewMemDB()
Expand Down Expand Up @@ -622,7 +626,7 @@ func TestFinalizeBlockRecoveryUsingLegacyABCIResponses(t *testing.T) {
resp, err := stateStore.LoadLastFinalizeBlockResponse(height)
require.NoError(t, err)
require.Equal(t, resp.ConsensusParamUpdates, &cp)
require.Equal(t, resp.Events, legacyResp.LegacyAbciResponses.BeginBlock.Events)
require.Equal(t, len(resp.Events), len(legacyResp.LegacyAbciResponses.BeginBlock.Events))
require.Equal(t, resp.TxResults[0], legacyResp.LegacyAbciResponses.DeliverTxs[0])
}

Expand Down
Loading