mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-09 14:43:19 +00:00
This is just a configuration change to default to using the new stack unless explicitly disabled (e.g. `UseLegacy`) this renames the configuration value and makes the configuration logic more clear. The legacy option is good to retain as a fallback if the new stack has issues operationally, but we should make sure that most of the time we're using the new stack.
734 lines
21 KiB
Go
734 lines
21 KiB
Go
package node
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"os"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/tendermint/tendermint/abci/example/kvstore"
|
|
cfg "github.com/tendermint/tendermint/config"
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
|
consmocks "github.com/tendermint/tendermint/internal/consensus/mocks"
|
|
ssmocks "github.com/tendermint/tendermint/internal/statesync/mocks"
|
|
|
|
"github.com/tendermint/tendermint/internal/evidence"
|
|
"github.com/tendermint/tendermint/internal/mempool"
|
|
mempoolv0 "github.com/tendermint/tendermint/internal/mempool/v0"
|
|
statesync "github.com/tendermint/tendermint/internal/statesync"
|
|
"github.com/tendermint/tendermint/internal/test/factory"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
|
tmrand "github.com/tendermint/tendermint/libs/rand"
|
|
tmtime "github.com/tendermint/tendermint/libs/time"
|
|
"github.com/tendermint/tendermint/privval"
|
|
"github.com/tendermint/tendermint/proxy"
|
|
sm "github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/state/indexer"
|
|
"github.com/tendermint/tendermint/store"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
func TestNodeStartStop(t *testing.T) {
|
|
config := cfg.ResetTestRoot("node_node_test")
|
|
|
|
defer os.RemoveAll(config.RootDir)
|
|
|
|
// create & start node
|
|
ns, err := newDefaultNode(config, log.TestingLogger())
|
|
require.NoError(t, err)
|
|
require.NoError(t, ns.Start())
|
|
|
|
n, ok := ns.(*nodeImpl)
|
|
require.True(t, ok)
|
|
|
|
// wait for the node to produce a block
|
|
blocksSub, err := n.EventBus().Subscribe(context.Background(), "node_test", types.EventQueryNewBlock)
|
|
require.NoError(t, err)
|
|
select {
|
|
case <-blocksSub.Out():
|
|
case <-blocksSub.Canceled():
|
|
t.Fatal("blocksSub was canceled")
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("timed out waiting for the node to produce a block")
|
|
}
|
|
|
|
// stop the node
|
|
go func() {
|
|
err = n.Stop()
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
select {
|
|
case <-n.Quit():
|
|
case <-time.After(5 * time.Second):
|
|
pid := os.Getpid()
|
|
p, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = p.Signal(syscall.SIGABRT)
|
|
fmt.Println(err)
|
|
t.Fatal("timed out waiting for shutdown")
|
|
}
|
|
}
|
|
|
|
func getTestNode(t *testing.T, conf *cfg.Config, logger log.Logger) *nodeImpl {
|
|
t.Helper()
|
|
ns, err := newDefaultNode(conf, logger)
|
|
require.NoError(t, err)
|
|
|
|
n, ok := ns.(*nodeImpl)
|
|
require.True(t, ok)
|
|
return n
|
|
}
|
|
|
|
func TestNodeDelayedStart(t *testing.T) {
|
|
config := cfg.ResetTestRoot("node_delayed_start_test")
|
|
defer os.RemoveAll(config.RootDir)
|
|
now := tmtime.Now()
|
|
|
|
// create & start node
|
|
n := getTestNode(t, config, log.TestingLogger())
|
|
n.GenesisDoc().GenesisTime = now.Add(2 * time.Second)
|
|
|
|
require.NoError(t, n.Start())
|
|
defer n.Stop() //nolint:errcheck // ignore for tests
|
|
|
|
startTime := tmtime.Now()
|
|
assert.Equal(t, true, startTime.After(n.GenesisDoc().GenesisTime))
|
|
}
|
|
|
|
func TestNodeSetAppVersion(t *testing.T) {
|
|
config := cfg.ResetTestRoot("node_app_version_test")
|
|
defer os.RemoveAll(config.RootDir)
|
|
|
|
// create node
|
|
n := getTestNode(t, config, log.TestingLogger())
|
|
|
|
// default config uses the kvstore app
|
|
var appVersion uint64 = kvstore.ProtocolVersion
|
|
|
|
// check version is set in state
|
|
state, err := n.stateStore.Load()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, state.Version.Consensus.App, appVersion)
|
|
|
|
// check version is set in node info
|
|
assert.Equal(t, n.nodeInfo.ProtocolVersion.App, appVersion)
|
|
}
|
|
|
|
func TestNodeSetPrivValTCP(t *testing.T) {
|
|
addr := "tcp://" + testFreeAddr(t)
|
|
|
|
config := cfg.ResetTestRoot("node_priv_val_tcp_test")
|
|
defer os.RemoveAll(config.RootDir)
|
|
config.PrivValidator.ListenAddr = addr
|
|
|
|
dialer := privval.DialTCPFn(addr, 100*time.Millisecond, ed25519.GenPrivKey())
|
|
dialerEndpoint := privval.NewSignerDialerEndpoint(
|
|
log.TestingLogger(),
|
|
dialer,
|
|
)
|
|
privval.SignerDialerEndpointTimeoutReadWrite(100 * time.Millisecond)(dialerEndpoint)
|
|
|
|
signerServer := privval.NewSignerServer(
|
|
dialerEndpoint,
|
|
config.ChainID(),
|
|
types.NewMockPV(),
|
|
)
|
|
|
|
go func() {
|
|
err := signerServer.Start()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
defer signerServer.Stop() //nolint:errcheck // ignore for tests
|
|
|
|
n := getTestNode(t, config, log.TestingLogger())
|
|
assert.IsType(t, &privval.RetrySignerClient{}, n.PrivValidator())
|
|
}
|
|
|
|
// address without a protocol must result in error
|
|
func TestPrivValidatorListenAddrNoProtocol(t *testing.T) {
|
|
addrNoPrefix := testFreeAddr(t)
|
|
|
|
config := cfg.ResetTestRoot("node_priv_val_tcp_test")
|
|
defer os.RemoveAll(config.RootDir)
|
|
config.PrivValidator.ListenAddr = addrNoPrefix
|
|
|
|
_, err := newDefaultNode(config, log.TestingLogger())
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestNodeSetPrivValIPC(t *testing.T) {
|
|
tmpfile := "/tmp/kms." + tmrand.Str(6) + ".sock"
|
|
defer os.Remove(tmpfile) // clean up
|
|
|
|
config := cfg.ResetTestRoot("node_priv_val_tcp_test")
|
|
defer os.RemoveAll(config.RootDir)
|
|
config.PrivValidator.ListenAddr = "unix://" + tmpfile
|
|
|
|
dialer := privval.DialUnixFn(tmpfile)
|
|
dialerEndpoint := privval.NewSignerDialerEndpoint(
|
|
log.TestingLogger(),
|
|
dialer,
|
|
)
|
|
privval.SignerDialerEndpointTimeoutReadWrite(100 * time.Millisecond)(dialerEndpoint)
|
|
|
|
pvsc := privval.NewSignerServer(
|
|
dialerEndpoint,
|
|
config.ChainID(),
|
|
types.NewMockPV(),
|
|
)
|
|
|
|
go func() {
|
|
err := pvsc.Start()
|
|
require.NoError(t, err)
|
|
}()
|
|
defer pvsc.Stop() //nolint:errcheck // ignore for tests
|
|
n := getTestNode(t, config, log.TestingLogger())
|
|
assert.IsType(t, &privval.RetrySignerClient{}, n.PrivValidator())
|
|
}
|
|
|
|
// testFreeAddr claims a free port so we don't block on listener being ready.
|
|
func testFreeAddr(t *testing.T) string {
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
defer ln.Close()
|
|
|
|
return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port)
|
|
}
|
|
|
|
// create a proposal block using real and full
|
|
// mempool and evidence pool and validate it.
|
|
func TestCreateProposalBlock(t *testing.T) {
|
|
config := cfg.ResetTestRoot("node_create_proposal")
|
|
defer os.RemoveAll(config.RootDir)
|
|
cc := proxy.NewLocalClientCreator(kvstore.NewApplication())
|
|
proxyApp := proxy.NewAppConns(cc)
|
|
err := proxyApp.Start()
|
|
require.Nil(t, err)
|
|
defer proxyApp.Stop() //nolint:errcheck // ignore for tests
|
|
|
|
logger := log.TestingLogger()
|
|
|
|
const height int64 = 1
|
|
state, stateDB, privVals := state(1, height)
|
|
stateStore := sm.NewStore(stateDB)
|
|
maxBytes := 16384
|
|
const partSize uint32 = 256
|
|
maxEvidenceBytes := int64(maxBytes / 2)
|
|
state.ConsensusParams.Block.MaxBytes = int64(maxBytes)
|
|
state.ConsensusParams.Evidence.MaxBytes = maxEvidenceBytes
|
|
proposerAddr, _ := state.Validators.GetByIndex(0)
|
|
|
|
mp := mempoolv0.NewCListMempool(
|
|
config.Mempool,
|
|
proxyApp.Mempool(),
|
|
state.LastBlockHeight,
|
|
mempoolv0.WithMetrics(mempool.NopMetrics()),
|
|
mempoolv0.WithPreCheck(sm.TxPreCheck(state)),
|
|
mempoolv0.WithPostCheck(sm.TxPostCheck(state)),
|
|
)
|
|
mp.SetLogger(logger)
|
|
|
|
// Make EvidencePool
|
|
evidenceDB := dbm.NewMemDB()
|
|
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
|
evidencePool, err := evidence.NewPool(logger, evidenceDB, stateStore, blockStore)
|
|
require.NoError(t, err)
|
|
|
|
// fill the evidence pool with more evidence
|
|
// than can fit in a block
|
|
var currentBytes int64 = 0
|
|
for currentBytes <= maxEvidenceBytes {
|
|
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain")
|
|
currentBytes += int64(len(ev.Bytes()))
|
|
evidencePool.ReportConflictingVotes(ev.VoteA, ev.VoteB)
|
|
}
|
|
|
|
evList, size := evidencePool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
|
|
require.Less(t, size, state.ConsensusParams.Evidence.MaxBytes+1)
|
|
evData := &types.EvidenceData{Evidence: evList}
|
|
require.EqualValues(t, size, evData.ByteSize())
|
|
|
|
// fill the mempool with more txs
|
|
// than can fit in a block
|
|
txLength := 100
|
|
for i := 0; i <= maxBytes/txLength; i++ {
|
|
tx := tmrand.Bytes(txLength)
|
|
err := mp.CheckTx(context.Background(), tx, nil, mempool.TxInfo{})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
blockExec := sm.NewBlockExecutor(
|
|
stateStore,
|
|
logger,
|
|
proxyApp.Consensus(),
|
|
mp,
|
|
evidencePool,
|
|
blockStore,
|
|
)
|
|
|
|
commit := types.NewCommit(height-1, 0, types.BlockID{}, nil)
|
|
block, _ := blockExec.CreateProposalBlock(
|
|
height,
|
|
state, commit,
|
|
proposerAddr,
|
|
)
|
|
|
|
// check that the part set does not exceed the maximum block size
|
|
partSet := block.MakePartSet(partSize)
|
|
assert.Less(t, partSet.ByteSize(), int64(maxBytes))
|
|
|
|
partSetFromHeader := types.NewPartSetFromHeader(partSet.Header())
|
|
for partSetFromHeader.Count() < partSetFromHeader.Total() {
|
|
added, err := partSetFromHeader.AddPart(partSet.GetPart(int(partSetFromHeader.Count())))
|
|
require.NoError(t, err)
|
|
require.True(t, added)
|
|
}
|
|
assert.EqualValues(t, partSetFromHeader.ByteSize(), partSet.ByteSize())
|
|
|
|
err = blockExec.ValidateBlock(state, block)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestMaxTxsProposalBlockSize(t *testing.T) {
|
|
config := cfg.ResetTestRoot("node_create_proposal")
|
|
defer os.RemoveAll(config.RootDir)
|
|
cc := proxy.NewLocalClientCreator(kvstore.NewApplication())
|
|
proxyApp := proxy.NewAppConns(cc)
|
|
err := proxyApp.Start()
|
|
require.Nil(t, err)
|
|
defer proxyApp.Stop() //nolint:errcheck // ignore for tests
|
|
|
|
logger := log.TestingLogger()
|
|
|
|
const height int64 = 1
|
|
state, stateDB, _ := state(1, height)
|
|
stateStore := sm.NewStore(stateDB)
|
|
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
|
const maxBytes int64 = 16384
|
|
const partSize uint32 = 256
|
|
state.ConsensusParams.Block.MaxBytes = maxBytes
|
|
proposerAddr, _ := state.Validators.GetByIndex(0)
|
|
|
|
// Make Mempool
|
|
mp := mempoolv0.NewCListMempool(
|
|
config.Mempool,
|
|
proxyApp.Mempool(),
|
|
state.LastBlockHeight,
|
|
mempoolv0.WithMetrics(mempool.NopMetrics()),
|
|
mempoolv0.WithPreCheck(sm.TxPreCheck(state)),
|
|
mempoolv0.WithPostCheck(sm.TxPostCheck(state)),
|
|
)
|
|
mp.SetLogger(logger)
|
|
|
|
// fill the mempool with one txs just below the maximum size
|
|
txLength := int(types.MaxDataBytesNoEvidence(maxBytes, 1))
|
|
tx := tmrand.Bytes(txLength - 4) // to account for the varint
|
|
err = mp.CheckTx(context.Background(), tx, nil, mempool.TxInfo{})
|
|
assert.NoError(t, err)
|
|
|
|
blockExec := sm.NewBlockExecutor(
|
|
stateStore,
|
|
logger,
|
|
proxyApp.Consensus(),
|
|
mp,
|
|
sm.EmptyEvidencePool{},
|
|
blockStore,
|
|
)
|
|
|
|
commit := types.NewCommit(height-1, 0, types.BlockID{}, nil)
|
|
block, _ := blockExec.CreateProposalBlock(
|
|
height,
|
|
state, commit,
|
|
proposerAddr,
|
|
)
|
|
|
|
pb, err := block.ToProto()
|
|
require.NoError(t, err)
|
|
assert.Less(t, int64(pb.Size()), maxBytes)
|
|
|
|
// check that the part set does not exceed the maximum block size
|
|
partSet := block.MakePartSet(partSize)
|
|
assert.EqualValues(t, partSet.ByteSize(), int64(pb.Size()))
|
|
}
|
|
|
|
func TestMaxProposalBlockSize(t *testing.T) {
|
|
config := cfg.ResetTestRoot("node_create_proposal")
|
|
defer os.RemoveAll(config.RootDir)
|
|
cc := proxy.NewLocalClientCreator(kvstore.NewApplication())
|
|
proxyApp := proxy.NewAppConns(cc)
|
|
err := proxyApp.Start()
|
|
require.Nil(t, err)
|
|
defer proxyApp.Stop() //nolint:errcheck // ignore for tests
|
|
|
|
logger := log.TestingLogger()
|
|
|
|
state, stateDB, _ := state(types.MaxVotesCount, int64(1))
|
|
stateStore := sm.NewStore(stateDB)
|
|
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
|
const maxBytes int64 = 1024 * 1024 * 2
|
|
state.ConsensusParams.Block.MaxBytes = maxBytes
|
|
proposerAddr, _ := state.Validators.GetByIndex(0)
|
|
|
|
// Make Mempool
|
|
mp := mempoolv0.NewCListMempool(
|
|
config.Mempool,
|
|
proxyApp.Mempool(),
|
|
state.LastBlockHeight,
|
|
mempoolv0.WithMetrics(mempool.NopMetrics()),
|
|
mempoolv0.WithPreCheck(sm.TxPreCheck(state)),
|
|
mempoolv0.WithPostCheck(sm.TxPostCheck(state)),
|
|
)
|
|
mp.SetLogger(logger)
|
|
|
|
// fill the mempool with one txs just below the maximum size
|
|
txLength := int(types.MaxDataBytesNoEvidence(maxBytes, types.MaxVotesCount))
|
|
tx := tmrand.Bytes(txLength - 6) // to account for the varint
|
|
err = mp.CheckTx(context.Background(), tx, nil, mempool.TxInfo{})
|
|
assert.NoError(t, err)
|
|
// now produce more txs than what a normal block can hold with 10 smaller txs
|
|
// At the end of the test, only the single big tx should be added
|
|
for i := 0; i < 10; i++ {
|
|
tx := tmrand.Bytes(10)
|
|
err = mp.CheckTx(context.Background(), tx, nil, mempool.TxInfo{})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
blockExec := sm.NewBlockExecutor(
|
|
stateStore,
|
|
logger,
|
|
proxyApp.Consensus(),
|
|
mp,
|
|
sm.EmptyEvidencePool{},
|
|
blockStore,
|
|
)
|
|
|
|
blockID := types.BlockID{
|
|
Hash: tmhash.Sum([]byte("blockID_hash")),
|
|
PartSetHeader: types.PartSetHeader{
|
|
Total: math.MaxInt32,
|
|
Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")),
|
|
},
|
|
}
|
|
|
|
timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC)
|
|
// change state in order to produce the largest accepted header
|
|
state.LastBlockID = blockID
|
|
state.LastBlockHeight = math.MaxInt64 - 1
|
|
state.LastBlockTime = timestamp
|
|
state.LastResultsHash = tmhash.Sum([]byte("last_results_hash"))
|
|
state.AppHash = tmhash.Sum([]byte("app_hash"))
|
|
state.Version.Consensus.Block = math.MaxInt64
|
|
state.Version.Consensus.App = math.MaxInt64
|
|
maxChainID := ""
|
|
for i := 0; i < types.MaxChainIDLen; i++ {
|
|
maxChainID += "𠜎"
|
|
}
|
|
state.ChainID = maxChainID
|
|
|
|
cs := types.CommitSig{
|
|
BlockIDFlag: types.BlockIDFlagNil,
|
|
ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
|
|
Timestamp: timestamp,
|
|
Signature: crypto.CRandBytes(types.MaxSignatureSize),
|
|
}
|
|
|
|
commit := &types.Commit{
|
|
Height: math.MaxInt64,
|
|
Round: math.MaxInt32,
|
|
BlockID: blockID,
|
|
}
|
|
|
|
// add maximum amount of signatures to a single commit
|
|
for i := 0; i < types.MaxVotesCount; i++ {
|
|
commit.Signatures = append(commit.Signatures, cs)
|
|
}
|
|
|
|
block, partSet := blockExec.CreateProposalBlock(
|
|
math.MaxInt64,
|
|
state, commit,
|
|
proposerAddr,
|
|
)
|
|
|
|
// this ensures that the header is at max size
|
|
block.Header.Time = timestamp
|
|
|
|
pb, err := block.ToProto()
|
|
require.NoError(t, err)
|
|
|
|
// require that the header and commit be the max possible size
|
|
require.Equal(t, int64(pb.Header.Size()), types.MaxHeaderBytes)
|
|
require.Equal(t, int64(pb.LastCommit.Size()), types.MaxCommitBytes(types.MaxVotesCount))
|
|
// make sure that the block is less than the max possible size
|
|
assert.Equal(t, int64(pb.Size()), maxBytes)
|
|
// because of the proto overhead we expect the part set bytes to be equal or
|
|
// less than the pb block size
|
|
assert.LessOrEqual(t, partSet.ByteSize(), int64(pb.Size()))
|
|
|
|
}
|
|
|
|
func TestNodeNewSeedNode(t *testing.T) {
|
|
config := cfg.ResetTestRoot("node_new_node_custom_reactors_test")
|
|
config.Mode = cfg.ModeSeed
|
|
defer os.RemoveAll(config.RootDir)
|
|
|
|
nodeKey, err := types.LoadOrGenNodeKey(config.NodeKeyFile())
|
|
require.NoError(t, err)
|
|
|
|
ns, err := makeSeedNode(config,
|
|
cfg.DefaultDBProvider,
|
|
nodeKey,
|
|
defaultGenesisDocProviderFunc(config),
|
|
log.TestingLogger(),
|
|
)
|
|
require.NoError(t, err)
|
|
n, ok := ns.(*nodeImpl)
|
|
require.True(t, ok)
|
|
|
|
err = n.Start()
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, n.pexReactor.IsRunning())
|
|
}
|
|
|
|
func TestNodeSetEventSink(t *testing.T) {
|
|
config := cfg.ResetTestRoot("node_app_version_test")
|
|
defer os.RemoveAll(config.RootDir)
|
|
|
|
logger := log.TestingLogger()
|
|
setupTest := func(t *testing.T, conf *cfg.Config) []indexer.EventSink {
|
|
eventBus, err := createAndStartEventBus(logger)
|
|
require.NoError(t, err)
|
|
|
|
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
|
|
require.NoError(t, err)
|
|
|
|
indexService, eventSinks, err := createAndStartIndexerService(config,
|
|
cfg.DefaultDBProvider, eventBus, logger, genDoc.ChainID)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { require.NoError(t, indexService.Stop()) })
|
|
return eventSinks
|
|
}
|
|
|
|
eventSinks := setupTest(t, config)
|
|
assert.Equal(t, 1, len(eventSinks))
|
|
assert.Equal(t, indexer.KV, eventSinks[0].Type())
|
|
|
|
config.TxIndex.Indexer = []string{"null"}
|
|
eventSinks = setupTest(t, config)
|
|
|
|
assert.Equal(t, 1, len(eventSinks))
|
|
assert.Equal(t, indexer.NULL, eventSinks[0].Type())
|
|
|
|
config.TxIndex.Indexer = []string{"null", "kv"}
|
|
eventSinks = setupTest(t, config)
|
|
|
|
assert.Equal(t, 1, len(eventSinks))
|
|
assert.Equal(t, indexer.NULL, eventSinks[0].Type())
|
|
|
|
config.TxIndex.Indexer = []string{"kvv"}
|
|
ns, err := newDefaultNode(config, logger)
|
|
assert.Nil(t, ns)
|
|
assert.Equal(t, errors.New("unsupported event sink type"), err)
|
|
|
|
config.TxIndex.Indexer = []string{}
|
|
eventSinks = setupTest(t, config)
|
|
|
|
assert.Equal(t, 1, len(eventSinks))
|
|
assert.Equal(t, indexer.NULL, eventSinks[0].Type())
|
|
|
|
config.TxIndex.Indexer = []string{"psql"}
|
|
ns, err = newDefaultNode(config, logger)
|
|
assert.Nil(t, ns)
|
|
assert.Equal(t, errors.New("the psql connection settings cannot be empty"), err)
|
|
|
|
var psqlConn = "test"
|
|
|
|
config.TxIndex.Indexer = []string{"psql"}
|
|
config.TxIndex.PsqlConn = psqlConn
|
|
eventSinks = setupTest(t, config)
|
|
|
|
assert.Equal(t, 1, len(eventSinks))
|
|
assert.Equal(t, indexer.PSQL, eventSinks[0].Type())
|
|
|
|
config.TxIndex.Indexer = []string{"psql", "kv"}
|
|
config.TxIndex.PsqlConn = psqlConn
|
|
eventSinks = setupTest(t, config)
|
|
|
|
assert.Equal(t, 2, len(eventSinks))
|
|
// we use map to filter the duplicated sinks, so it's not guarantee the order when append sinks.
|
|
if eventSinks[0].Type() == indexer.KV {
|
|
assert.Equal(t, indexer.PSQL, eventSinks[1].Type())
|
|
} else {
|
|
assert.Equal(t, indexer.PSQL, eventSinks[0].Type())
|
|
assert.Equal(t, indexer.KV, eventSinks[1].Type())
|
|
}
|
|
|
|
config.TxIndex.Indexer = []string{"kv", "psql"}
|
|
config.TxIndex.PsqlConn = psqlConn
|
|
eventSinks = setupTest(t, config)
|
|
|
|
assert.Equal(t, 2, len(eventSinks))
|
|
if eventSinks[0].Type() == indexer.KV {
|
|
assert.Equal(t, indexer.PSQL, eventSinks[1].Type())
|
|
} else {
|
|
assert.Equal(t, indexer.PSQL, eventSinks[0].Type())
|
|
assert.Equal(t, indexer.KV, eventSinks[1].Type())
|
|
}
|
|
|
|
var e = errors.New("found duplicated sinks, please check the tx-index section in the config.toml")
|
|
config.TxIndex.Indexer = []string{"psql", "kv", "Kv"}
|
|
config.TxIndex.PsqlConn = psqlConn
|
|
_, err = newDefaultNode(config, logger)
|
|
require.Error(t, err)
|
|
assert.Equal(t, e, err)
|
|
|
|
config.TxIndex.Indexer = []string{"Psql", "kV", "kv", "pSql"}
|
|
config.TxIndex.PsqlConn = psqlConn
|
|
_, err = newDefaultNode(config, logger)
|
|
require.Error(t, err)
|
|
assert.Equal(t, e, err)
|
|
}
|
|
|
|
func state(nVals int, height int64) (sm.State, dbm.DB, []types.PrivValidator) {
|
|
privVals := make([]types.PrivValidator, nVals)
|
|
vals := make([]types.GenesisValidator, nVals)
|
|
for i := 0; i < nVals; i++ {
|
|
privVal := types.NewMockPV()
|
|
privVals[i] = privVal
|
|
vals[i] = types.GenesisValidator{
|
|
Address: privVal.PrivKey.PubKey().Address(),
|
|
PubKey: privVal.PrivKey.PubKey(),
|
|
Power: 1000,
|
|
Name: fmt.Sprintf("test%d", i),
|
|
}
|
|
}
|
|
s, _ := sm.MakeGenesisState(&types.GenesisDoc{
|
|
ChainID: "test-chain",
|
|
Validators: vals,
|
|
AppHash: nil,
|
|
})
|
|
|
|
// save validators to db for 2 heights
|
|
stateDB := dbm.NewMemDB()
|
|
stateStore := sm.NewStore(stateDB)
|
|
if err := stateStore.Save(s); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for i := 1; i < int(height); i++ {
|
|
s.LastBlockHeight++
|
|
s.LastValidators = s.Validators.Copy()
|
|
if err := stateStore.Save(s); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return s, stateDB, privVals
|
|
}
|
|
|
|
func TestLoadStateFromGenesis(t *testing.T) {
|
|
_ = loadStatefromGenesis(t)
|
|
}
|
|
|
|
func loadStatefromGenesis(t *testing.T) sm.State {
|
|
t.Helper()
|
|
|
|
stateDB := dbm.NewMemDB()
|
|
stateStore := sm.NewStore(stateDB)
|
|
config := cfg.ResetTestRoot("load_state_from_genesis")
|
|
|
|
loadedState, err := stateStore.Load()
|
|
require.NoError(t, err)
|
|
require.True(t, loadedState.IsEmpty())
|
|
|
|
genDoc, _ := factory.RandGenesisDoc(config, 0, false, 10)
|
|
|
|
state, err := loadStateFromDBOrGenesisDocProvider(
|
|
stateStore,
|
|
genDoc,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, state)
|
|
|
|
return state
|
|
}
|
|
|
|
func TestNodeStartStateSync(t *testing.T) {
|
|
mockSSR := &statesync.MockSyncReactor{}
|
|
mockFSR := &consmocks.BlockSyncReactor{}
|
|
mockCSR := &consmocks.ConsSyncReactor{}
|
|
mockSP := &ssmocks.StateProvider{}
|
|
state := loadStatefromGenesis(t)
|
|
config := cfg.ResetTestRoot("load_state_from_genesis")
|
|
|
|
eventBus, err := createAndStartEventBus(log.TestingLogger())
|
|
defer func() {
|
|
err := eventBus.Stop()
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, eventBus)
|
|
|
|
sub, err := eventBus.Subscribe(context.Background(), "test-client", types.EventQueryStateSyncStatus, 10)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sub)
|
|
|
|
cfgSS := config.StateSync
|
|
|
|
mockSSR.On("Sync", context.TODO(), mockSP, cfgSS.DiscoveryTime).Return(state, nil).
|
|
On("Backfill", state).Return(nil)
|
|
mockCSR.On("SetStateSyncingMetrics", float64(0)).Return().
|
|
On("SwitchToConsensus", state, true).Return()
|
|
|
|
require.NoError(t,
|
|
startStateSync(mockSSR, mockFSR, mockCSR, mockSP, config.StateSync, false, state.InitialHeight, eventBus))
|
|
|
|
for cnt := 0; cnt < 2; {
|
|
select {
|
|
case <-time.After(3 * time.Second):
|
|
t.Errorf("StateSyncStatus timeout")
|
|
case msg := <-sub.Out():
|
|
if cnt == 0 {
|
|
ensureStateSyncStatus(t, msg, false, state.InitialHeight)
|
|
cnt++
|
|
} else {
|
|
// the state height = 0 because we are not actually update the state in this test
|
|
ensureStateSyncStatus(t, msg, true, 0)
|
|
cnt++
|
|
}
|
|
}
|
|
}
|
|
|
|
mockSSR.AssertNumberOfCalls(t, "Sync", 1)
|
|
mockSSR.AssertNumberOfCalls(t, "Backfill", 1)
|
|
mockCSR.AssertNumberOfCalls(t, "SetStateSyncingMetrics", 1)
|
|
mockCSR.AssertNumberOfCalls(t, "SwitchToConsensus", 1)
|
|
}
|
|
|
|
func ensureStateSyncStatus(t *testing.T, msg tmpubsub.Message, complete bool, height int64) {
|
|
t.Helper()
|
|
status, ok := msg.Data().(types.EventDataStateSyncStatus)
|
|
|
|
require.True(t, ok)
|
|
require.Equal(t, complete, status.Complete)
|
|
require.Equal(t, height, status.Height)
|
|
}
|