Skip to content

Commit

Permalink
statesync: assert app version matches (backport #7856) (#7885)
Browse files Browse the repository at this point in the history
  • Loading branch information
tnasu committed Feb 28, 2022
1 parent 5b5933d commit 43a356c
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 30 deletions.
2 changes: 1 addition & 1 deletion statesync/reactor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func makeTestStateAndCommit(appHash string, height int64) (sm.State, *types.Comm
Version: tmstate.Version{
Consensus: tmversion.Consensus{
Block: version.BlockProtocol,
App: 0,
App: 9,
},

Software: version.OCCoreSemVer,
Expand Down
5 changes: 5 additions & 0 deletions statesync/stateprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
rpchttp "github.com/line/ostracon/rpc/client/http"
sm "github.com/line/ostracon/state"
"github.com/line/ostracon/types"
"github.com/line/ostracon/version"
"github.com/line/tm-db/v2/memdb"
)

Expand Down Expand Up @@ -171,6 +172,10 @@ func (s *lightClientStateProvider) State(ctx context.Context, height uint64) (sm
return sm.State{}, err
}

state.Version = tmstate.Version{
Consensus: currentLightBlock.Version,
Software: version.OCCoreSemVer,
}
state.LastBlockHeight = lastLightBlock.Height
state.LastBlockTime = lastLightBlock.Time
state.LastBlockID = lastLightBlock.Commit.BlockID
Expand Down
36 changes: 22 additions & 14 deletions statesync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,13 +314,10 @@ func (s *syncer) Sync(snapshot *snapshot, chunks *chunkQueue) (sm.State, sm.Stat
return sm.State{}, sm.State{}, nil, err
}

// Verify app and update app version
appVersion, err := s.verifyApp(snapshot)
if err != nil {
// Verify app and app version
if err := s.verifyApp(snapshot, state.Version.Consensus.App); err != nil {
return sm.State{}, sm.State{}, nil, err
}
state.Version.Consensus.App = appVersion
previousState.Version.Consensus.App = appVersion

// Done! 🎉
s.logger.Info("Snapshot restored", "height", snapshot.Height, "format", snapshot.Format,
Expand Down Expand Up @@ -490,25 +487,36 @@ func (s *syncer) requestChunk(snapshot *snapshot, chunk uint32) {
}))
}

// verifyApp verifies the sync, checking the app hash and last block height. It returns the
// app version, which should be returned as part of the initial state.
func (s *syncer) verifyApp(snapshot *snapshot) (uint64, error) {
// verifyApp verifies the sync, checking the app hash, last block height and app version
func (s *syncer) verifyApp(snapshot *snapshot, appVersion uint64) error {
resp, err := s.connQuery.InfoSync(proxy.RequestInfo)
if err != nil {
return 0, fmt.Errorf("failed to query ABCI app for appHash: %w", err)
return fmt.Errorf("failed to query ABCI app for appHash: %w", err)
}

// sanity check that the app version in the block matches the application's own record
// of its version
if resp.AppVersion != appVersion {
// An error here most likely means that the app hasn't inplemented state sync
// or the Info call correctly
return fmt.Errorf("app version mismatch. Expected: %d, got: %d",
appVersion, resp.AppVersion)
}
if !bytes.Equal(snapshot.trustedAppHash, resp.LastBlockAppHash) {
s.logger.Error("appHash verification failed",
"expected", snapshot.trustedAppHash,
"actual", resp.LastBlockAppHash)
return 0, errVerifyFailed
return errVerifyFailed
}
if uint64(resp.LastBlockHeight) != snapshot.Height {
s.logger.Error("ABCI app reported unexpected last block height",
"expected", snapshot.Height, "actual", resp.LastBlockHeight)
return 0, errVerifyFailed
s.logger.Error(
"ABCI app reported unexpected last block height",
"expected", snapshot.Height,
"actual", resp.LastBlockHeight,
)
return errVerifyFailed
}

s.logger.Info("Verified ABCI app", "height", snapshot.Height, "appHash", snapshot.trustedAppHash)
return resp.AppVersion, nil
return nil
}
30 changes: 16 additions & 14 deletions statesync/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
"github.com/line/ostracon/version"
)

const testAppVersion = 9

// Sets up a basic syncer that can be used to test OfferSnapshot requests
func setupOfferSyncer(t *testing.T) (*syncer, *proxymocks.AppConnSnapshot) {
connQuery := &proxymocks.AppConnQuery{}
Expand All @@ -53,7 +55,7 @@ func TestSyncer_SyncAny(t *testing.T) {
Version: tmstate.Version{
Consensus: tmversion.Consensus{
Block: version.BlockProtocol,
App: 0,
App: testAppVersion,
},

Software: version.OCCoreSemVer,
Expand Down Expand Up @@ -189,7 +191,7 @@ func TestSyncer_SyncAny(t *testing.T) {
Index: 2, Chunk: []byte{1, 1, 2},
}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
connQuery.On("InfoSync", proxy.RequestInfo).Return(&abci.ResponseInfo{
AppVersion: 9,
AppVersion: testAppVersion,
LastBlockHeight: 1,
LastBlockAppHash: []byte("app_hash"),
}, nil)
Expand All @@ -203,12 +205,8 @@ func TestSyncer_SyncAny(t *testing.T) {
assert.Equal(t, map[uint32]int{0: 1, 1: 2, 2: 1}, chunkRequests)
chunkRequestsMtx.Unlock()

// The syncer should have updated the state app version from the ABCI info response.
expectState := state
expectState.Version.Consensus.App = 9

expectPreviousState := sm.State{}
expectPreviousState.Version.Consensus.App = expectState.Version.Consensus.App

assert.Equal(t, expectState, newState)
assert.Equal(t, expectPreviousState, previousState)
Expand Down Expand Up @@ -622,6 +620,8 @@ func TestSyncer_applyChunks_RejectSenders(t *testing.T) {

func TestSyncer_verifyApp(t *testing.T) {
boom := errors.New("boom")
const appVersion = 9
appVersionMismatchErr := errors.New("app version mismatch. Expected: 9, got: 2")
s := &snapshot{Height: 3, Format: 1, Chunks: 5, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")}

testcases := map[string]struct {
Expand All @@ -632,17 +632,22 @@ func TestSyncer_verifyApp(t *testing.T) {
"verified": {&abci.ResponseInfo{
LastBlockHeight: 3,
LastBlockAppHash: []byte("app_hash"),
AppVersion: 9,
AppVersion: appVersion,
}, nil, nil},
"invalid app version": {&abci.ResponseInfo{
LastBlockHeight: 3,
LastBlockAppHash: []byte("app_hash"),
AppVersion: 2,
}, nil, appVersionMismatchErr},
"invalid height": {&abci.ResponseInfo{
LastBlockHeight: 5,
LastBlockAppHash: []byte("app_hash"),
AppVersion: 9,
AppVersion: appVersion,
}, nil, errVerifyFailed},
"invalid hash": {&abci.ResponseInfo{
LastBlockHeight: 3,
LastBlockAppHash: []byte("xxx"),
AppVersion: 9,
AppVersion: appVersion,
}, nil, errVerifyFailed},
"error": {nil, boom, boom},
}
Expand All @@ -657,15 +662,12 @@ func TestSyncer_verifyApp(t *testing.T) {
syncer := newSyncer(*cfg, log.NewNopLogger(), connSnapshot, connQuery, stateProvider, "")

connQuery.On("InfoSync", proxy.RequestInfo).Return(tc.response, tc.err)
version, err := syncer.verifyApp(s)
err := syncer.verifyApp(s, appVersion)
unwrapped := errors.Unwrap(err)
if unwrapped != nil {
err = unwrapped
}
assert.Equal(t, tc.expectErr, err)
if err == nil {
assert.Equal(t, tc.response.AppVersion, version)
}
require.Equal(t, tc.expectErr, err)
})
}
}
Expand Down
5 changes: 4 additions & 1 deletion test/e2e/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import (
"github.com/line/ostracon/version"
)

const appVersion = 1

// Application is an ABCI application for use by end-to-end tests. It is a
// simple key/value store for strings, storing data in memory and persisting
// to disk as JSON, taking state sync snapshots if requested.

type Application struct {
abci.BaseApplication
logger log.Logger
Expand Down Expand Up @@ -102,7 +105,7 @@ func NewApplication(cfg *Config) (*Application, error) {
func (app *Application) Info(req abci.RequestInfo) abci.ResponseInfo {
return abci.ResponseInfo{
Version: version.ABCIVersion,
AppVersion: 1,
AppVersion: appVersion,
LastBlockHeight: int64(app.state.Height),
LastBlockAppHash: app.state.Hash,
}
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/runner/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) {
ConsensusParams: types.DefaultConsensusParams(),
InitialHeight: testnet.InitialHeight,
}
// set the app version to 1
genesis.ConsensusParams.Version.AppVersion = 1
for validator, power := range testnet.Validators {
genesis.Validators = append(genesis.Validators, types.GenesisValidator{
Name: validator.Name,
Expand Down

0 comments on commit 43a356c

Please sign in to comment.