Skip to content

Commit

Permalink
refactor: backport remove genesis persistence from state
Browse files Browse the repository at this point in the history
This avoids saving the genesisDoc to database.
When using goleveldb and ~4GiB+ genesis files, it causes a panic
during snappy encoding (panic: snappy: decoded block is too large).

refs akash-network/support#280

backports:
- cometbft#1017
- cometbft#1293

Signed-off-by: Artur Troian <troian.ap@gmail.com>
  • Loading branch information
troian committed Feb 6, 2025
1 parent 8bcde65 commit b6d56ca
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 68 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ jobs:
- name: Split pkgs into 4 files
run: split -d -n l/4 pkgs.txt pkgs.txt.part.
# cache multiple
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: "${{ github.sha }}-00"
path: ./pkgs.txt.part.00
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: "${{ github.sha }}-01"
path: ./pkgs.txt.part.01
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: "${{ github.sha }}-02"
path: ./pkgs.txt.part.02
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: "${{ github.sha }}-03"
path: ./pkgs.txt.part.03
Expand Down Expand Up @@ -83,7 +83,7 @@ jobs:
run: |
cat pkgs.txt.part.${{ matrix.part }} | xargs go test -mod=readonly -timeout 8m -race -coverprofile=${{ matrix.part }}profile.out -covermode=atomic
if: env.GIT_DIFF
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: "${{ github.sha }}-${{ matrix.part }}-coverage"
path: ./${{ matrix.part }}profile.out
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/fuzz-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ jobs:
continue-on-error: true

- name: Archive crashers
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: crashers
path: test/fuzz/**/crashers
retention-days: 1

- name: Archive suppressions
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: suppressions
path: test/fuzz/**/suppressions
Expand Down
10 changes: 6 additions & 4 deletions consensus/byzantine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
"github.com/tendermint/tendermint/types"
)

//----------------------------------------------
// ----------------------------------------------
// byzantine failures

// Byzantine node sends two different prevotes (nil and blockID) to the same validator
Expand All @@ -54,7 +54,9 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
stateStore := sm.NewStore(stateDB, sm.StoreOptions{
DiscardABCIResponses: false,
})
state, _ := stateStore.LoadFromDBOrGenesisDoc(genDoc)
state, err := sm.MakeGenesisState(genDoc)
require.NoError(t, err)
require.NoError(t, stateStore.Save(state))
thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i))
defer os.RemoveAll(thisConfig.RootDir)
ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
Expand Down Expand Up @@ -462,7 +464,7 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) {
}
}

//-------------------------------
// -------------------------------
// byzantine consensus functions

func byzantineDecideProposalFunc(t *testing.T, height int64, round int32, cs *State, sw *p2p.Switch) {
Expand Down Expand Up @@ -556,7 +558,7 @@ func sendProposalAndParts(
}, cs.Logger)
}

//----------------------------------------
// ----------------------------------------
// byzantine consensus reactor

type ByzantineReactor struct {
Expand Down
21 changes: 9 additions & 12 deletions consensus/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func ResetConfig(name string) *cfg.Config {
return cfg.ResetTestRoot(name)
}

//-------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
// validator stub (a kvstore consensus peer we control)

type validatorStub struct {
Expand Down Expand Up @@ -190,7 +190,7 @@ func (vss ValidatorStubsByPower) Swap(i, j int) {
vss[j].Index = int32(j)
}

//-------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
// Functions for transitioning the consensus state

func startTestRound(cs *State, height int64, round int32) {
Expand Down Expand Up @@ -361,7 +361,7 @@ func subscribeToVoter(cs *State, addr []byte) <-chan cmtpubsub.Message {
return ch
}

//-------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
// consensus states

func newState(state sm.State, pv types.PrivValidator, app abci.Application) *State {
Expand Down Expand Up @@ -475,7 +475,7 @@ func randState(nValidators int) (*State, []*validatorStub) {
return cs, vss
}

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

func ensureNoNewEvent(ch <-chan cmtpubsub.Message, timeout time.Duration,
errorMessage string,
Expand Down Expand Up @@ -697,7 +697,7 @@ func ensureNewEventOnChannel(ch <-chan cmtpubsub.Message) {
}
}

//-------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
// consensus nets

// consensusLogger is a TestingLogger which uses a different
Expand All @@ -722,10 +722,7 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou
configRootDirs := make([]string, 0, nValidators)
for i := 0; i < nValidators; i++ {
stateDB := dbm.NewMemDB() // each state needs its own db
stateStore := sm.NewStore(stateDB, sm.StoreOptions{
DiscardABCIResponses: false,
})
state, _ := stateStore.LoadFromDBOrGenesisDoc(genDoc)
state, _ := sm.MakeGenesisState(genDoc)
thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i))
configRootDirs = append(configRootDirs, thisConfig.RootDir)
for _, opt := range configOpts {
Expand Down Expand Up @@ -817,7 +814,7 @@ func getSwitchIndex(switches []*p2p.Switch, peer p2p.Peer) int {
panic("didnt find peer in switches")
}

//-------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
// genesis

func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) {
Expand Down Expand Up @@ -847,7 +844,7 @@ func randGenesisState(numValidators int, randPower bool, minPower int64) (sm.Sta
return s0, privValidators
}

//------------------------------------
// ------------------------------------
// mock ticker

func newMockTickerFunc(onlyOnce bool) func() TimeoutTicker {
Expand Down Expand Up @@ -895,7 +892,7 @@ func (m *mockTicker) Chan() <-chan timeoutInfo {

func (*mockTicker) SetLogger(log.Logger) {}

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

func newCounter() abci.Application {
return counter.NewApplication(true)
Expand Down
24 changes: 10 additions & 14 deletions consensus/replay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,17 @@ func TestMain(m *testing.M) {
// the `Handshake Tests` are for failures in applying the block.
// With the help of the WAL, we can recover from it all!

//------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// WAL Tests

// TODO: It would be better to verify explicitly which states we can recover from without the wal
// and which ones we need the wal for - then we'd also be able to only flush the
// wal writer when we need to, instead of with every message.

func startNewStateAndWaitForBlock(t *testing.T, consensusReplayConfig *cfg.Config,
lastBlockHeight int64, blockDB dbm.DB, stateStore sm.Store,
) {
func startNewStateAndWaitForBlock(t *testing.T, consensusReplayConfig *cfg.Config, blockDB dbm.DB) {
logger := log.TestingLogger()
state, _ := stateStore.LoadFromDBOrGenesisFile(consensusReplayConfig.GenesisFile())
state, err := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile())
require.NoError(t, err)
privValidator := loadPrivValidator(consensusReplayConfig)
cs := newStateWithConfigAndBlockStore(
consensusReplayConfig,
Expand All @@ -81,7 +80,7 @@ func startNewStateAndWaitForBlock(t *testing.T, consensusReplayConfig *cfg.Confi
bytes, _ := os.ReadFile(cs.config.WalFile())
t.Logf("====== WAL: \n\r%X\n", bytes)

err := cs.Start()
err = cs.Start()
require.NoError(t, err)
defer func() {
if err := cs.Stop(); err != nil {
Expand Down Expand Up @@ -164,9 +163,6 @@ LOOP:
logger := log.NewNopLogger()
blockDB := dbm.NewMemDB()
stateDB := blockDB
stateStore := sm.NewStore(stateDB, sm.StoreOptions{
DiscardABCIResponses: false,
})
state, err := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile())
require.NoError(t, err)
privValidator := loadPrivValidator(consensusReplayConfig)
Expand Down Expand Up @@ -207,7 +203,7 @@ LOOP:
t.Logf("WAL panicked: %v", err)

// make sure we can make blocks after a crash
startNewStateAndWaitForBlock(t, consensusReplayConfig, cs.Height, blockDB, stateStore)
startNewStateAndWaitForBlock(t, consensusReplayConfig, blockDB)

// stop consensus state and transactions sender (initFn)
cs.Stop() //nolint:errcheck // Logging this error causes failure
Expand Down Expand Up @@ -318,7 +314,7 @@ var (
sim testSim
)

//---------------------------------------
// ---------------------------------------
// Test handshake/replay

// 0 - all synced up
Expand Down Expand Up @@ -1030,7 +1026,7 @@ func (app *badApp) Commit() abci.ResponseCommit {
panic("either allHashesAreWrong or onlyLastHashIsWrong must be set")
}

//--------------------------
// --------------------------
// utils for making blocks

func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) {
Expand Down Expand Up @@ -1176,7 +1172,7 @@ func stateAndStore(
return stateDB, state, store
}

//----------------------------------
// ----------------------------------
// mock block store

type mockBlockStore struct {
Expand Down Expand Up @@ -1231,7 +1227,7 @@ func (bs *mockBlockStore) PruneBlocks(height int64) (uint64, error) {
return pruned, nil
}

//---------------------------------------
// ---------------------------------------
// Test handshake/init chain

func TestHandshakeUpdatesValidators(t *testing.T) {
Expand Down
29 changes: 28 additions & 1 deletion crypto/tmhash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package tmhash

import (
"crypto/sha256"
"errors"
"fmt"
"hash"
"regexp"
)

const (
Expand All @@ -21,7 +24,7 @@ func Sum(bz []byte) []byte {
return h[:]
}

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

const (
TruncatedSize = 20
Expand Down Expand Up @@ -63,3 +66,27 @@ func SumTruncated(bz []byte) []byte {
hash := sha256.Sum256(bz)
return hash[:TruncatedSize]
}

// ValidateSHA256 checks if the given string is a syntactically valid SHA256 hash.
// A valid SHA256 hash is a hex-encoded 64-character string.
// If the hash isn't valid, it returns an error explaining why.
func ValidateSHA256(hashStr string) error {
const sha256Pattern = `^[a-fA-F0-9]{64}$`

if len(hashStr) != 64 {
return fmt.Errorf("expected 64 characters, but have %d", len(hashStr))
}

match, err := regexp.MatchString(sha256Pattern, hashStr)
if err != nil {
// if this happens, there is a bug in the regex or some internal regexp
// package error.
return fmt.Errorf("can't run regex %q: %s", sha256Pattern, err)
}

if !match {
return errors.New("contains non-hexadecimal characters")
}

return nil
}
60 changes: 60 additions & 0 deletions node/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package node

import (
"errors"
"fmt"
)

var (
// ErrNonEmptyBlockStore is returned when the blockstore is not empty and the node is trying to initialize non empty state.
ErrNonEmptyBlockStore = errors.New("blockstore not empty, trying to initialize non empty state")
// ErrNonEmptyState is returned when the state is not empty and the node is trying to initialize non empty state.
ErrNonEmptyState = errors.New("state not empty, trying to initialize non empty state")
// ErrSwitchStateSync is returned when the blocksync reactor does not support switching from state sync.
ErrSwitchStateSync = errors.New("this blocksync reactor does not support switching from state sync")
// ErrGenesisHashDecode is returned when the genesis hash provided by the operator cannot be decoded.
ErrGenesisHashDecode = errors.New("genesis hash provided by operator cannot be decoded")
// ErrPassedGenesisHashMismatch is returned when the genesis doc hash in the database does not match the passed --genesis_hash value.
ErrPassedGenesisHashMismatch = errors.New("genesis doc hash in db does not match passed --genesis_hash value")
// ErrLoadedGenesisDocHashMismatch is returned when the genesis doc hash in the database does not match the loaded genesis doc.
ErrLoadedGenesisDocHashMismatch = errors.New("genesis doc hash in db does not match loaded genesis doc")
)

// ErrGenesisDoc is returned when the node fails to load the genesis doc.
type ErrGenesisDoc struct {
Err error
}

func (e ErrGenesisDoc) Error() string {
return fmt.Sprintf("error in genesis doc: %v", e.Err)
}

func (e ErrGenesisDoc) Unwrap() error {
return e.Err
}

// ErrRetrieveGenesisDocHash is returned when the node fails to retrieve the genesis doc hash from the database.
type ErrRetrieveGenesisDocHash struct {
Err error
}

func (e ErrRetrieveGenesisDocHash) Error() string {
return fmt.Sprintf("error retrieving genesis doc hash: %v", e.Err)
}

func (e ErrRetrieveGenesisDocHash) Unwrap() error {
return e.Err
}

// ErrSaveGenesisDocHash is returned when the node fails to save the genesis doc hash to the database.
type ErrSaveGenesisDocHash struct {
Err error
}

func (e ErrSaveGenesisDocHash) Error() string {
return fmt.Sprintf("failed to save genesis doc hash to db: %v", e.Err)
}

func (e ErrSaveGenesisDocHash) Unwrap() error {
return e.Err
}
Loading

0 comments on commit b6d56ca

Please sign in to comment.