Skip to content

Commit

Permalink
Prevent storing genDoc to db
Browse files Browse the repository at this point in the history
Changes are from cometbft#1295
  • Loading branch information
DracoLi committed Dec 7, 2023
1 parent fe45483 commit 59710e9
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 39 deletions.
67 changes: 28 additions & 39 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
cfg "github.com/cometbft/cometbft/config"
cs "github.com/cometbft/cometbft/consensus"
"github.com/cometbft/cometbft/crypto"
"github.com/cometbft/cometbft/crypto/tmhash"
"github.com/cometbft/cometbft/evidence"

cmtjson "github.com/cometbft/cometbft/libs/json"
Expand Down Expand Up @@ -1372,6 +1373,7 @@ func makeNodeInfo(
//------------------------------------------------------------------------------

var genesisDocKey = []byte("genesisDoc")

Check failure on line 1375 in node/node.go

View workflow job for this annotation

GitHub Actions / golangci-lint

var `genesisDocKey` is unused (unused)
var genesisDocHashKey = []byte("genesisDocHash")

// LoadStateFromDBOrGenesisDocProvider attempts to load the state from the
// database, or creates one using the given genesisDocProvider. On success this also
Expand All @@ -1380,57 +1382,44 @@ func LoadStateFromDBOrGenesisDocProvider(
stateDB dbm.DB,
genesisDocProvider GenesisDocProvider,
) (sm.State, *types.GenesisDoc, error) {
// Get genesis doc
genDoc, err := loadGenesisDoc(stateDB)
// Get genesis doc hash
genDocHash, err := stateDB.Get(genesisDocHashKey)
if err != nil {
genDoc, err = genesisDocProvider()
if err != nil {
return sm.State{}, nil, err
}
// save genesis doc to prevent a certain class of user errors (e.g. when it
// was changed, accidentally or not). Also good for audit trail.
if err := saveGenesisDoc(stateDB, genDoc); err != nil {
return sm.State{}, nil, err
}
return sm.State{}, nil, fmt.Errorf("error retrieving genesis doc hash: %w", err)
}
stateStore := sm.NewStore(stateDB, sm.StoreOptions{
DiscardABCIResponses: false,
})
state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc)
genDoc, err := genesisDocProvider()
if err != nil {
return sm.State{}, nil, err
}
return state, genDoc, nil
}

// panics if failed to unmarshal bytes
func loadGenesisDoc(db dbm.DB) (*types.GenesisDoc, error) {
b, err := db.Get(genesisDocKey)
if err != nil {
panic(err)
}
if len(b) == 0 {
return nil, errors.New("genesis doc not found")
if err := genDoc.ValidateAndComplete(); err != nil {
return sm.State{}, nil, fmt.Errorf("error in genesis doc: %w", err)
}
var genDoc *types.GenesisDoc
err = cmtjson.Unmarshal(b, &genDoc)

genDocBytes, err := cmtjson.Marshal(genDoc)
if err != nil {
panic(fmt.Sprintf("Failed to load genesis doc due to unmarshaling error: %v (bytes: %X)", err, b))
return sm.State{}, nil, fmt.Errorf("failed to save genesis doc hash due to marshaling error: %w", err)
}
return genDoc, nil
}

// panics if failed to marshal the given genesis document
func saveGenesisDoc(db dbm.DB, genDoc *types.GenesisDoc) error {
b, err := cmtjson.Marshal(genDoc)
if err != nil {
return fmt.Errorf("failed to save genesis doc due to marshaling error: %w", err)
incomingGenDocHash := tmhash.Sum(genDocBytes)
if len(genDocHash) == 0 {
// Save the genDoc hash in the store if it doesn't already exist for future verification
if err := stateDB.SetSync(genesisDocHashKey, incomingGenDocHash); err != nil {
return sm.State{}, nil, fmt.Errorf("failed to save genesis doc hash to db: %w", err)
}
} else {
if !bytes.Equal(genDocHash, incomingGenDocHash) {
return sm.State{}, nil, fmt.Errorf("genesis doc hash in db does not match loaded genesis doc")
}
}
if err := db.SetSync(genesisDocKey, b); err != nil {
return err
stateStore := sm.NewStore(stateDB, sm.StoreOptions{
DiscardABCIResponses: false,
})
state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc)
if err != nil {
return sm.State{}, nil, err
}

return nil
return state, genDoc, nil
}

func createAndStartPrivValidatorSocketClient(
Expand Down
68 changes: 68 additions & 0 deletions node/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ import (
"github.com/cometbft/cometbft/abci/example/kvstore"
cfg "github.com/cometbft/cometbft/config"
"github.com/cometbft/cometbft/crypto/ed25519"
"github.com/cometbft/cometbft/crypto/tmhash"
"github.com/cometbft/cometbft/evidence"
cmtjson "github.com/cometbft/cometbft/libs/json"
"github.com/cometbft/cometbft/libs/log"
cmtos "github.com/cometbft/cometbft/libs/os"
cmtrand "github.com/cometbft/cometbft/libs/rand"
mempl "github.com/cometbft/cometbft/mempool"
mempoolv0 "github.com/cometbft/cometbft/mempool/v0"
Expand Down Expand Up @@ -452,6 +455,71 @@ func TestNodeNewNodeCustomReactors(t *testing.T) {
assert.Contains(t, channels, cr.Channels[0].ID)
}

func TestNodeNewNodeGenesisHashMismatch(t *testing.T) {
config := cfg.ResetTestRoot("node_new_node_genesis_hash")
defer os.RemoveAll(config.RootDir)

// Use goleveldb so we can reuse the same db for the second NewNode()
config.DBBackend = string(dbm.GoLevelDBBackend)

nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
require.NoError(t, err)

n, err := NewNode(
config,
privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()),
nodeKey,
proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()),
DefaultGenesisDocProviderFunc(config),
DefaultDBProvider,
DefaultMetricsProvider(config.Instrumentation),
log.TestingLogger(),
)
require.NoError(t, err)

// Start and stop to close the db for later reading
err = n.Start()
require.NoError(t, err)

err = n.Stop()
require.NoError(t, err)

// Ensure the genesis doc hash is saved to db
stateDB, err := DefaultDBProvider(&DBContext{ID: "state", Config: config})
require.NoError(t, err)

genDocHash, err := stateDB.Get(genesisDocHashKey)
require.NoError(t, err)
require.NotNil(t, genDocHash, "genesis doc hash should be saved in db")
require.Len(t, genDocHash, tmhash.Size)

err = stateDB.Close()
require.NoError(t, err)

// Modify the genesis file chain ID to get a different hash
genBytes := cmtos.MustReadFile(config.GenesisFile())
var genesisDoc types.GenesisDoc
err = cmtjson.Unmarshal(genBytes, &genesisDoc)
require.NoError(t, err)

genesisDoc.ChainID = "different-chain-id"
err = genesisDoc.SaveAs(config.GenesisFile())
require.NoError(t, err)

_, err = NewNode(
config,
privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()),
nodeKey,
proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()),
DefaultGenesisDocProviderFunc(config),
DefaultDBProvider,
DefaultMetricsProvider(config.Instrumentation),
log.TestingLogger(),
)
require.Error(t, err, "NewNode should error when genesisDoc is changed")
require.Equal(t, "genesis doc hash in db does not match loaded genesis doc", err.Error())
}

func state(nVals int, height int64) (sm.State, dbm.DB, []types.PrivValidator) {
privVals := make([]types.PrivValidator, nVals)
vals := make([]types.GenesisValidator, nVals)
Expand Down

0 comments on commit 59710e9

Please sign in to comment.