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

Prevent storing genDoc to db #3

Merged
merged 2 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 28 additions & 40 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 @@ -1371,7 +1372,7 @@ func makeNodeInfo(

//------------------------------------------------------------------------------

var genesisDocKey = []byte("genesisDoc")
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 +1381,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
Loading