Skip to content

Commit

Permalink
Skip loading json metadata from file for broken file (0xPolygon#1117)
Browse files Browse the repository at this point in the history
* Add safe-guard agaist broken snapshot and metadata file in initializing snapshot store

* Add test cases for NewSnapshotValidatorStoreWrapper
  • Loading branch information
Kourin1996 authored Feb 3, 2023
1 parent 9eccd18 commit df32bb4
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 30 deletions.
34 changes: 30 additions & 4 deletions consensus/ibft/fork/storewrapper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fork

import (
"encoding/json"
"errors"
"path/filepath"

"github.com/0xPolygon/polygon-edge/consensus/ibft/signer"
Expand All @@ -11,6 +13,17 @@ import (
"github.com/hashicorp/go-hclog"
)

// isJSONSyntaxError returns bool indicating the giving error is json.SyntaxError or not
func isJSONSyntaxError(err error) bool {
var expected *json.SyntaxError

if err == nil {
return false
}

return errors.As(err, &expected)
}

// SnapshotValidatorStoreWrapper is a wrapper of store.SnapshotValidatorStore
// in order to add initialization and closer process with side effect
type SnapshotValidatorStoreWrapper struct {
Expand Down Expand Up @@ -51,13 +64,26 @@ func NewSnapshotValidatorStoreWrapper(
dirPath string,
epochSize uint64,
) (*SnapshotValidatorStoreWrapper, error) {
snapshotMeta, err := loadSnapshotMetadata(filepath.Join(dirPath, snapshotMetadataFilename))
if err != nil {
var (
snapshotMetadataPath = filepath.Join(dirPath, snapshotMetadataFilename)
snapshotsPath = filepath.Join(dirPath, snapshotSnapshotsFilename)
)

snapshotMeta, err := loadSnapshotMetadata(snapshotMetadataPath)
if isJSONSyntaxError(err) {
logger.Warn("Snapshot metadata file is broken, recover metadata from local chain", "filepath", snapshotMetadataPath)

snapshotMeta = nil
} else if err != nil {
return nil, err
}

snapshots, err := loadSnapshots(filepath.Join(dirPath, snapshotSnapshotsFilename))
if err != nil {
snapshots, err := loadSnapshots(snapshotsPath)
if isJSONSyntaxError(err) {
logger.Warn("Snapshots file is broken, recover snapshots from local chain", "filepath", snapshotsPath)

snapshots = nil
} else if err != nil {
return nil, err
}

Expand Down
190 changes: 164 additions & 26 deletions consensus/ibft/fork/storewrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,87 @@ var (
errTest = errors.New("test")
)

// a mock returning an error in UnmarshalJSON
type fakeUnmarshalerStruct struct{}

func (s *fakeUnmarshalerStruct) UnmarshalJSON(data []byte) error {
return errTest
}

type mockSigner struct {
signer.Signer

TypeFn func() validators.ValidatorType
EcrecoverFromHeaderFn func(*types.Header) (types.Address, error)
GetValidatorsFn func(*types.Header) (validators.Validators, error)
}

func (m *mockSigner) Type() validators.ValidatorType {
return m.TypeFn()
}

func (m *mockSigner) EcrecoverFromHeader(h *types.Header) (types.Address, error) {
return m.EcrecoverFromHeaderFn(h)
}

func (m *mockSigner) GetValidators(h *types.Header) (validators.Validators, error) {
return m.GetValidatorsFn(h)
}

func Test_isJSONSyntaxError(t *testing.T) {
t.Parallel()

var (
// create some special errors
snaps = []*snapshot.Snapshot{}
fakeStr = &fakeUnmarshalerStruct{}

invalidJSONErr = json.Unmarshal([]byte("foo"), &snaps)
invalidUnmarshalErr = json.Unmarshal([]byte("{}"), fakeStr)
)

tests := []struct {
name string
err error
expected bool
}{
{
name: "should return false for nil",
err: nil,
expected: false,
},
{
name: "should return false for custom error",
err: errTest,
expected: false,
},
{
name: "should return marshal for json.InvalidUnmarshalError",
err: invalidUnmarshalErr,
expected: false,
},
{
name: "should return json.SyntaxError",
err: invalidJSONErr,
expected: true,
},
}

for _, test := range tests {
test := test

t.Run(test.name, func(t *testing.T) {
t.Parallel()

assert.Equal(
t,
test.expected,
isJSONSyntaxError(test.err),
)
})
}
}

func createTestMetadataJSON(height uint64) string {
return fmt.Sprintf(`{"LastBlock": %d}`, height)
}
Expand All @@ -48,24 +129,6 @@ func TestSnapshotValidatorStoreWrapper(t *testing.T) {
epochSize uint64
err error
}{
{
name: "should return error if loading metadata fails",
storedSnapshotMetadata: `hoge`,
storedSnapshots: "",
blockchain: nil,
signer: nil,
epochSize: 0,
err: &json.SyntaxError{},
},
{
name: "should return error if loading snapshots fails",
storedSnapshotMetadata: createTestMetadataJSON(10),
storedSnapshots: `fuga`,
blockchain: nil,
signer: nil,
epochSize: 0,
err: &json.SyntaxError{},
},
{
name: "should return error if initialize fails",
storedSnapshotMetadata: createTestMetadataJSON(0),
Expand Down Expand Up @@ -100,6 +163,77 @@ func TestSnapshotValidatorStoreWrapper(t *testing.T) {
epochSize: 10,
err: nil,
},
// the below cases recover snapshots from local chain,
// but this test just makes sure constructor doesn't return an error
// because snapshot package has tests covering such cases
{
name: "should succeed and recover snapshots from headers when the files don't exist",
storedSnapshotMetadata: "",
storedSnapshots: "",
blockchain: &store.MockBlockchain{
HeaderFn: func() *types.Header {
return &types.Header{Number: 0}
},
},
signer: &mockSigner{
GetValidatorsFn: func(h *types.Header) (validators.Validators, error) {
// height of the header HeaderFn returns
assert.Equal(t, uint64(0), h.Number)

return &validators.Set{}, nil
},
},
epochSize: 10,
err: nil,
},
{
name: "should succeed and recover snapshots from headers when the metadata file is broken",
storedSnapshotMetadata: "broken data",
storedSnapshots: fmt.Sprintf("[%s]", createTestSnapshotJSON(
t,
&snapshot.Snapshot{
Number: 10,
Hash: types.BytesToHash([]byte{0x10}).String(),
Set: validators.NewECDSAValidatorSet(),
Votes: []*store.Vote{},
},
)),
blockchain: &store.MockBlockchain{
HeaderFn: func() *types.Header {
return &types.Header{Number: 0}
},
},
signer: &mockSigner{
GetValidatorsFn: func(h *types.Header) (validators.Validators, error) {
// height of the header HeaderFn returns
assert.Equal(t, uint64(0), h.Number)

return &validators.Set{}, nil
},
},
epochSize: 10,
err: nil,
},
{
name: "should succeed and recover snapshots from headers when the snapshots file is broken",
storedSnapshotMetadata: createTestMetadataJSON(0),
storedSnapshots: "broken",
blockchain: &store.MockBlockchain{
HeaderFn: func() *types.Header {
return &types.Header{Number: 0}
},
},
signer: &mockSigner{
GetValidatorsFn: func(h *types.Header) (validators.Validators, error) {
// height of the header HeaderFn returns
assert.Equal(t, uint64(0), h.Number)

return &validators.Set{}, nil
},
},
epochSize: 10,
err: nil,
},
}

for _, test := range tests {
Expand All @@ -110,15 +244,19 @@ func TestSnapshotValidatorStoreWrapper(t *testing.T) {

dirPath := createTestTempDirectory(t)

assert.NoError(
t,
os.WriteFile(path.Join(dirPath, snapshotMetadataFilename), []byte(test.storedSnapshotMetadata), os.ModePerm),
)
if len(test.storedSnapshotMetadata) != 0 {
assert.NoError(
t,
os.WriteFile(path.Join(dirPath, snapshotMetadataFilename), []byte(test.storedSnapshotMetadata), os.ModePerm),
)
}

assert.NoError(
t,
os.WriteFile(path.Join(dirPath, snapshotSnapshotsFilename), []byte(test.storedSnapshots), os.ModePerm),
)
if len(test.storedSnapshots) != 0 {
assert.NoError(
t,
os.WriteFile(path.Join(dirPath, snapshotSnapshotsFilename), []byte(test.storedSnapshots), os.ModePerm),
)
}

store, err := NewSnapshotValidatorStoreWrapper(
hclog.NewNullLogger(),
Expand Down

0 comments on commit df32bb4

Please sign in to comment.