mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-26 09:54:19 +00:00
Merge branch 'master' into wb/pbts-runbook
This commit is contained in:
@@ -168,7 +168,7 @@ func (pool *BlockPool) removeTimedoutPeers() {
|
||||
for _, peer := range pool.peers {
|
||||
// check if peer timed out
|
||||
if !peer.didTimeout && peer.numPending > 0 {
|
||||
curRate := peer.recvMonitor.Status().CurRate
|
||||
curRate := peer.recvMonitor.CurrentTransferRate()
|
||||
// curRate can be 0 on start
|
||||
if curRate != 0 && curRate < minRecvRate {
|
||||
err := errors.New("peer is not sending us data fast enough")
|
||||
|
||||
@@ -70,6 +70,8 @@ type Reactor struct {
|
||||
|
||||
// immutable
|
||||
initialState sm.State
|
||||
// store
|
||||
stateStore sm.Store
|
||||
|
||||
blockExec *sm.BlockExecutor
|
||||
store *store.BlockStore
|
||||
@@ -101,7 +103,7 @@ type Reactor struct {
|
||||
func NewReactor(
|
||||
ctx context.Context,
|
||||
logger log.Logger,
|
||||
state sm.State,
|
||||
stateStore sm.Store,
|
||||
blockExec *sm.BlockExecutor,
|
||||
store *store.BlockStore,
|
||||
consReactor consensusReactor,
|
||||
@@ -111,19 +113,6 @@ func NewReactor(
|
||||
metrics *consensus.Metrics,
|
||||
eventBus *eventbus.EventBus,
|
||||
) (*Reactor, error) {
|
||||
|
||||
if state.LastBlockHeight != store.Height() {
|
||||
return nil, fmt.Errorf("state (%v) and store (%v) height mismatch", state.LastBlockHeight, store.Height())
|
||||
}
|
||||
|
||||
startHeight := store.Height() + 1
|
||||
if startHeight == 1 {
|
||||
startHeight = state.InitialHeight
|
||||
}
|
||||
|
||||
requestsCh := make(chan BlockRequest, maxTotalRequesters)
|
||||
errorsCh := make(chan peerError, maxPeerErrBuffer) // NOTE: The capacity should be larger than the peer count.
|
||||
|
||||
blockSyncCh, err := channelCreator(ctx, GetChannelDescriptor())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -131,20 +120,16 @@ func NewReactor(
|
||||
|
||||
r := &Reactor{
|
||||
logger: logger,
|
||||
initialState: state,
|
||||
stateStore: stateStore,
|
||||
blockExec: blockExec,
|
||||
store: store,
|
||||
pool: NewBlockPool(logger, startHeight, requestsCh, errorsCh),
|
||||
consReactor: consReactor,
|
||||
blockSync: newAtomicBool(blockSync),
|
||||
requestsCh: requestsCh,
|
||||
errorsCh: errorsCh,
|
||||
blockSyncCh: blockSyncCh,
|
||||
blockSyncOutBridgeCh: make(chan p2p.Envelope),
|
||||
peerUpdates: peerUpdates,
|
||||
metrics: metrics,
|
||||
eventBus: eventBus,
|
||||
syncStartTime: time.Time{},
|
||||
}
|
||||
|
||||
r.BaseService = *service.NewBaseService(logger, "BlockSync", r)
|
||||
@@ -159,6 +144,27 @@ func NewReactor(
|
||||
// If blockSync is enabled, we also start the pool and the pool processing
|
||||
// goroutine. If the pool fails to start, an error is returned.
|
||||
func (r *Reactor) OnStart(ctx context.Context) error {
|
||||
state, err := r.stateStore.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.initialState = state
|
||||
|
||||
if state.LastBlockHeight != r.store.Height() {
|
||||
return fmt.Errorf("state (%v) and store (%v) height mismatch", state.LastBlockHeight, r.store.Height())
|
||||
}
|
||||
|
||||
startHeight := r.store.Height() + 1
|
||||
if startHeight == 1 {
|
||||
startHeight = state.InitialHeight
|
||||
}
|
||||
|
||||
requestsCh := make(chan BlockRequest, maxTotalRequesters)
|
||||
errorsCh := make(chan peerError, maxPeerErrBuffer) // NOTE: The capacity should be larger than the peer count.
|
||||
r.pool = NewBlockPool(r.logger, startHeight, requestsCh, errorsCh)
|
||||
r.requestsCh = requestsCh
|
||||
r.errorsCh = errorsCh
|
||||
|
||||
if r.blockSync.IsSet() {
|
||||
if err := r.pool.Start(ctx); err != nil {
|
||||
return err
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fortytw2/leaktest"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
@@ -14,7 +15,8 @@ import (
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/internal/consensus"
|
||||
"github.com/tendermint/tendermint/internal/mempool/mock"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks"
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/internal/p2p/p2ptest"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
@@ -33,7 +35,7 @@ type reactorTestSuite struct {
|
||||
nodes []types.NodeID
|
||||
|
||||
reactors map[types.NodeID]*Reactor
|
||||
app map[types.NodeID]proxy.AppConns
|
||||
app map[types.NodeID]abciclient.Client
|
||||
|
||||
blockSyncChannels map[types.NodeID]*p2p.Channel
|
||||
peerChans map[types.NodeID]chan p2p.PeerUpdate
|
||||
@@ -64,7 +66,7 @@ func setup(
|
||||
network: p2ptest.MakeNetwork(ctx, t, p2ptest.NetworkOptions{NumNodes: numNodes}),
|
||||
nodes: make([]types.NodeID, 0, numNodes),
|
||||
reactors: make(map[types.NodeID]*Reactor, numNodes),
|
||||
app: make(map[types.NodeID]proxy.AppConns, numNodes),
|
||||
app: make(map[types.NodeID]abciclient.Client, numNodes),
|
||||
blockSyncChannels: make(map[types.NodeID]*p2p.Channel, numNodes),
|
||||
peerChans: make(map[types.NodeID]chan p2p.PeerUpdate, numNodes),
|
||||
peerUpdates: make(map[types.NodeID]*p2p.PeerUpdates, numNodes),
|
||||
@@ -109,7 +111,7 @@ func (rts *reactorTestSuite) addNode(
|
||||
logger := log.TestingLogger()
|
||||
|
||||
rts.nodes = append(rts.nodes, nodeID)
|
||||
rts.app[nodeID] = proxy.NewAppConns(abciclient.NewLocalCreator(&abci.BaseApplication{}), logger, proxy.NopMetrics())
|
||||
rts.app[nodeID] = proxy.New(abciclient.NewLocalClient(logger, &abci.BaseApplication{}), logger, proxy.NopMetrics())
|
||||
require.NoError(t, rts.app[nodeID].Start(ctx))
|
||||
|
||||
blockDB := dbm.NewMemDB()
|
||||
@@ -120,14 +122,29 @@ func (rts *reactorTestSuite) addNode(
|
||||
state, err := sm.MakeGenesisState(genDoc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, stateStore.Save(state))
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
|
||||
eventbus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventbus.Start(ctx))
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
log.TestingLogger(),
|
||||
rts.app[nodeID].Consensus(),
|
||||
mock.Mempool{},
|
||||
rts.app[nodeID],
|
||||
mp,
|
||||
sm.EmptyEvidencePool{},
|
||||
blockStore,
|
||||
eventbus,
|
||||
)
|
||||
|
||||
for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
|
||||
@@ -154,8 +171,7 @@ func (rts *reactorTestSuite) addNode(
|
||||
)
|
||||
}
|
||||
|
||||
thisBlock, err := sf.MakeBlock(state, blockHeight, lastCommit)
|
||||
require.NoError(t, err)
|
||||
thisBlock := sf.MakeBlock(state, blockHeight, lastCommit)
|
||||
thisParts, err := thisBlock.MakePartSet(types.BlockPartSizeBytes)
|
||||
require.NoError(t, err)
|
||||
blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()}
|
||||
@@ -176,7 +192,7 @@ func (rts *reactorTestSuite) addNode(
|
||||
rts.reactors[nodeID], err = NewReactor(
|
||||
ctx,
|
||||
rts.logger.With("nodeID", nodeID),
|
||||
state.Copy(),
|
||||
stateStore,
|
||||
blockExec,
|
||||
blockStore,
|
||||
nil,
|
||||
|
||||
@@ -82,37 +82,33 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
|
||||
log.TestingLogger().With("module", "mempool"),
|
||||
thisConfig.Mempool,
|
||||
proxyAppConnMem,
|
||||
0,
|
||||
)
|
||||
if thisConfig.Consensus.WaitForTxs() {
|
||||
mempool.EnableTxsAvailable()
|
||||
}
|
||||
|
||||
eventBus := eventbus.NewDefault(log.TestingLogger().With("module", "events"))
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
// Make a full instance of the evidence pool
|
||||
evidenceDB := dbm.NewMemDB()
|
||||
evpool, err := evidence.NewPool(logger.With("module", "evidence"), evidenceDB, stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
evpool := evidence.NewPool(logger.With("module", "evidence"), evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
|
||||
// Make State
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool, blockStore)
|
||||
cs := NewState(ctx, logger, thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool)
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool, blockStore, eventBus)
|
||||
cs, err := NewState(ctx, logger, thisConfig.Consensus, stateStore, blockExec, blockStore, mempool, evpool, eventBus)
|
||||
require.NoError(t, err)
|
||||
// set private validator
|
||||
pv := privVals[i]
|
||||
cs.SetPrivValidator(ctx, pv)
|
||||
|
||||
eventBus := eventbus.NewDefault(log.TestingLogger().With("module", "events"))
|
||||
err = eventBus.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
cs.SetEventBus(eventBus)
|
||||
evpool.SetEventBus(eventBus)
|
||||
|
||||
cs.SetTimeoutTicker(tickerFunc())
|
||||
|
||||
states[i] = cs
|
||||
}()
|
||||
}
|
||||
|
||||
rts := setup(ctx, t, nValidators, states, 100) // buffer must be large enough to not deadlock
|
||||
rts := setup(ctx, t, nValidators, states, 512) // buffer must be large enough to not deadlock
|
||||
|
||||
var bzNodeID types.NodeID
|
||||
|
||||
@@ -180,7 +176,6 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
|
||||
require.NotNil(t, lazyNodeState.privValidator)
|
||||
|
||||
var commit *types.Commit
|
||||
var votes []*types.Vote
|
||||
switch {
|
||||
case lazyNodeState.Height == lazyNodeState.state.InitialHeight:
|
||||
// We're creating a proposal for the first block.
|
||||
@@ -189,7 +184,6 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
|
||||
case lazyNodeState.LastCommit.HasTwoThirdsMajority():
|
||||
// Make the commit from LastCommit
|
||||
commit = lazyNodeState.LastCommit.MakeCommit()
|
||||
votes = lazyNodeState.LastCommit.GetVotes()
|
||||
default: // This shouldn't happen.
|
||||
lazyNodeState.logger.Error("enterPropose: Cannot propose anything: No commit for the previous block")
|
||||
return
|
||||
@@ -206,9 +200,10 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
|
||||
}
|
||||
proposerAddr := lazyNodeState.privValidatorPubKey.Address()
|
||||
|
||||
block, blockParts, err := lazyNodeState.blockExec.CreateProposalBlock(
|
||||
ctx, lazyNodeState.Height, lazyNodeState.state, commit, proposerAddr, votes,
|
||||
)
|
||||
block, err := lazyNodeState.blockExec.CreateProposalBlock(
|
||||
ctx, lazyNodeState.Height, lazyNodeState.state, commit, proposerAddr, nil)
|
||||
require.NoError(t, err)
|
||||
blockParts, err := block.MakePartSet(types.BlockPartSizeBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Flush the WAL. Otherwise, we may not recompute the same proposal to sign,
|
||||
@@ -238,8 +233,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, reactor := range rts.reactors {
|
||||
state := reactor.state.GetState()
|
||||
reactor.SwitchToConsensus(ctx, state, false)
|
||||
reactor.SwitchToConsensus(ctx, reactor.state.GetState(), false)
|
||||
}
|
||||
|
||||
// Evidence should be submitted and committed at the third height but
|
||||
@@ -248,20 +242,26 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
i := 0
|
||||
subctx, subcancel := context.WithCancel(ctx)
|
||||
defer subcancel()
|
||||
for _, sub := range rts.subs {
|
||||
wg.Add(1)
|
||||
|
||||
go func(j int, s eventbus.Subscription) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
if subctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := s.Next(subctx)
|
||||
if subctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := s.Next(ctx)
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
cancel()
|
||||
t.Errorf("waiting for subscription: %v", err)
|
||||
subcancel()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -273,12 +273,18 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}(i, sub)
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// don't run more assertions if we've encountered a timeout
|
||||
select {
|
||||
case <-subctx.Done():
|
||||
t.Fatal("encountered timeout")
|
||||
default:
|
||||
}
|
||||
|
||||
pubkey, err := bzNodeState.privValidator.GetPubKey(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -290,267 +296,3 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
|
||||
assert.Equal(t, prevoteHeight, ev.Height())
|
||||
}
|
||||
}
|
||||
|
||||
// 4 validators. 1 is byzantine. The other three are partitioned into A (1 val) and B (2 vals).
|
||||
// byzantine validator sends conflicting proposals into A and B,
|
||||
// and prevotes/precommits on both of them.
|
||||
// B sees a commit, A doesn't.
|
||||
// Heal partition and ensure A sees the commit
|
||||
func TestByzantineConflictingProposalsWithPartition(t *testing.T) {
|
||||
// TODO: https://github.com/tendermint/tendermint/issues/6092
|
||||
t.SkipNow()
|
||||
|
||||
// n := 4
|
||||
// logger := consensusLogger().With("test", "byzantine")
|
||||
// app := newCounter
|
||||
|
||||
// states, cleanup := randConsensusState(n, "consensus_byzantine_test", newMockTickerFunc(false), app)
|
||||
// t.Cleanup(cleanup)
|
||||
|
||||
// // give the byzantine validator a normal ticker
|
||||
// ticker := NewTimeoutTicker()
|
||||
// ticker.SetLogger(states[0].logger)
|
||||
// states[0].SetTimeoutTicker(ticker)
|
||||
|
||||
// p2pLogger := logger.With("module", "p2p")
|
||||
|
||||
// blocksSubs := make([]types.Subscription, n)
|
||||
// reactors := make([]p2p.Reactor, n)
|
||||
// for i := 0; i < n; i++ {
|
||||
// // enable txs so we can create different proposals
|
||||
// assertMempool(states[i].txNotifier).EnableTxsAvailable()
|
||||
|
||||
// eventBus := states[i].eventBus
|
||||
// eventBus.SetLogger(logger.With("module", "events", "validator", i))
|
||||
|
||||
// var err error
|
||||
// blocksSubs[i], err = eventBus.Subscribe(ctx, testSubscriber, types.EventQueryNewBlock)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// conR := NewReactor(states[i], true) // so we don't start the consensus states
|
||||
// conR.SetLogger(logger.With("validator", i))
|
||||
// conR.SetEventBus(eventBus)
|
||||
|
||||
// var conRI p2p.Reactor = conR
|
||||
|
||||
// // make first val byzantine
|
||||
// if i == 0 {
|
||||
// conRI = NewByzantineReactor(conR)
|
||||
// }
|
||||
|
||||
// reactors[i] = conRI
|
||||
// err = states[i].blockExec.Store().Save(states[i].state) // for save height 1's validators info
|
||||
// require.NoError(t, err)
|
||||
// }
|
||||
|
||||
// switches := p2p.MakeConnectedSwitches(config.P2P, N, func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
// sw.SetLogger(p2pLogger.With("validator", i))
|
||||
// sw.AddReactor("CONSENSUS", reactors[i])
|
||||
// return sw
|
||||
// }, func(sws []*p2p.Switch, i, j int) {
|
||||
// // the network starts partitioned with globally active adversary
|
||||
// if i != 0 {
|
||||
// return
|
||||
// }
|
||||
// p2p.Connect2Switches(sws, i, j)
|
||||
// })
|
||||
|
||||
// // make first val byzantine
|
||||
// // NOTE: Now, test validators are MockPV, which by default doesn't
|
||||
// // do any safety checks.
|
||||
// states[0].privValidator.(types.MockPV).DisableChecks()
|
||||
// states[0].decideProposal = func(j int32) func(int64, int32) {
|
||||
// return func(height int64, round int32) {
|
||||
// byzantineDecideProposalFunc(t, height, round, states[j], switches[j])
|
||||
// }
|
||||
// }(int32(0))
|
||||
// // We are setting the prevote function to do nothing because the prevoting
|
||||
// // and precommitting are done alongside the proposal.
|
||||
// states[0].doPrevote = func(height int64, round int32) {}
|
||||
|
||||
// defer func() {
|
||||
// for _, sw := range switches {
|
||||
// err := sw.Stop()
|
||||
// require.NoError(t, err)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// // start the non-byz state machines.
|
||||
// // note these must be started before the byz
|
||||
// for i := 1; i < n; i++ {
|
||||
// cr := reactors[i].(*Reactor)
|
||||
// cr.SwitchToConsensus(cr.conS.GetState(), false)
|
||||
// }
|
||||
|
||||
// // start the byzantine state machine
|
||||
// byzR := reactors[0].(*ByzantineReactor)
|
||||
// s := byzR.reactor.conS.GetState()
|
||||
// byzR.reactor.SwitchToConsensus(s, false)
|
||||
|
||||
// // byz proposer sends one block to peers[0]
|
||||
// // and the other block to peers[1] and peers[2].
|
||||
// // note peers and switches order don't match.
|
||||
// peers := switches[0].Peers().List()
|
||||
|
||||
// // partition A
|
||||
// ind0 := getSwitchIndex(switches, peers[0])
|
||||
|
||||
// // partition B
|
||||
// ind1 := getSwitchIndex(switches, peers[1])
|
||||
// ind2 := getSwitchIndex(switches, peers[2])
|
||||
// p2p.Connect2Switches(switches, ind1, ind2)
|
||||
|
||||
// // wait for someone in the big partition (B) to make a block
|
||||
// <-blocksSubs[ind2].Out()
|
||||
|
||||
// t.Log("A block has been committed. Healing partition")
|
||||
// p2p.Connect2Switches(switches, ind0, ind1)
|
||||
// p2p.Connect2Switches(switches, ind0, ind2)
|
||||
|
||||
// // wait till everyone makes the first new block
|
||||
// // (one of them already has)
|
||||
// wg := new(sync.WaitGroup)
|
||||
// for i := 1; i < N-1; i++ {
|
||||
// wg.Add(1)
|
||||
// go func(j int) {
|
||||
// <-blocksSubs[j].Out()
|
||||
// wg.Done()
|
||||
// }(i)
|
||||
// }
|
||||
|
||||
// done := make(chan struct{})
|
||||
// go func() {
|
||||
// wg.Wait()
|
||||
// close(done)
|
||||
// }()
|
||||
|
||||
// tick := time.NewTicker(time.Second * 10)
|
||||
// select {
|
||||
// case <-done:
|
||||
// case <-tick.C:
|
||||
// for i, reactor := range reactors {
|
||||
// t.Log(fmt.Sprintf("Consensus Reactor %v", i))
|
||||
// t.Log(fmt.Sprintf("%v", reactor))
|
||||
// }
|
||||
// t.Fatalf("Timed out waiting for all validators to commit first block")
|
||||
// }
|
||||
}
|
||||
|
||||
// func byzantineDecideProposalFunc(t *testing.T, height int64, round int32, cs *State, sw *p2p.Switch) {
|
||||
// // byzantine user should create two proposals and try to split the vote.
|
||||
// // Avoid sending on internalMsgQueue and running consensus state.
|
||||
|
||||
// // Create a new proposal block from state/txs from the mempool.
|
||||
// block1, blockParts1 := cs.createProposalBlock()
|
||||
// polRound, propBlockID := cs.ValidRound, types.BlockID{Hash: block1.Hash(), PartSetHeader: blockParts1.Header()}
|
||||
// proposal1 := types.NewProposal(height, round, polRound, propBlockID)
|
||||
// p1 := proposal1.ToProto()
|
||||
// if err := cs.privValidator.SignProposal(cs.state.ChainID, p1); err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
|
||||
// proposal1.Signature = p1.Signature
|
||||
|
||||
// // some new transactions come in (this ensures that the proposals are different)
|
||||
// deliverTxsRange(cs, 0, 1)
|
||||
|
||||
// // Create a new proposal block from state/txs from the mempool.
|
||||
// block2, blockParts2 := cs.createProposalBlock()
|
||||
// polRound, propBlockID = cs.ValidRound, types.BlockID{Hash: block2.Hash(), PartSetHeader: blockParts2.Header()}
|
||||
// proposal2 := types.NewProposal(height, round, polRound, propBlockID)
|
||||
// p2 := proposal2.ToProto()
|
||||
// if err := cs.privValidator.SignProposal(cs.state.ChainID, p2); err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
|
||||
// proposal2.Signature = p2.Signature
|
||||
|
||||
// block1Hash := block1.Hash()
|
||||
// block2Hash := block2.Hash()
|
||||
|
||||
// // broadcast conflicting proposals/block parts to peers
|
||||
// peers := sw.Peers().List()
|
||||
// t.Logf("Byzantine: broadcasting conflicting proposals to %d peers", len(peers))
|
||||
// for i, peer := range peers {
|
||||
// if i < len(peers)/2 {
|
||||
// go sendProposalAndParts(height, round, cs, peer, proposal1, block1Hash, blockParts1)
|
||||
// } else {
|
||||
// go sendProposalAndParts(height, round, cs, peer, proposal2, block2Hash, blockParts2)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// func sendProposalAndParts(
|
||||
// height int64,
|
||||
// round int32,
|
||||
// cs *State,
|
||||
// peer p2p.Peer,
|
||||
// proposal *types.Proposal,
|
||||
// blockHash []byte,
|
||||
// parts *types.PartSet,
|
||||
// ) {
|
||||
// // proposal
|
||||
// msg := &ProposalMessage{Proposal: proposal}
|
||||
// peer.Send(DataChannel, MustEncode(msg))
|
||||
|
||||
// // parts
|
||||
// for i := 0; i < int(parts.Total()); i++ {
|
||||
// part := parts.GetPart(i)
|
||||
// msg := &BlockPartMessage{
|
||||
// Height: height, // This tells peer that this part applies to us.
|
||||
// Round: round, // This tells peer that this part applies to us.
|
||||
// Part: part,
|
||||
// }
|
||||
// peer.Send(DataChannel, MustEncode(msg))
|
||||
// }
|
||||
|
||||
// // votes
|
||||
// cs.mtx.Lock()
|
||||
// prevote, _ := cs.signVote(tmproto.PrevoteType, blockHash, parts.Header())
|
||||
// precommit, _ := cs.signVote(tmproto.PrecommitType, blockHash, parts.Header())
|
||||
// cs.mtx.Unlock()
|
||||
|
||||
// peer.Send(VoteChannel, MustEncode(&VoteMessage{prevote}))
|
||||
// peer.Send(VoteChannel, MustEncode(&VoteMessage{precommit}))
|
||||
// }
|
||||
|
||||
// type ByzantineReactor struct {
|
||||
// service.Service
|
||||
// reactor *Reactor
|
||||
// }
|
||||
|
||||
// func NewByzantineReactor(conR *Reactor) *ByzantineReactor {
|
||||
// return &ByzantineReactor{
|
||||
// Service: conR,
|
||||
// reactor: conR,
|
||||
// }
|
||||
// }
|
||||
|
||||
// func (br *ByzantineReactor) SetSwitch(s *p2p.Switch) { br.reactor.SetSwitch(s) }
|
||||
// func (br *ByzantineReactor) GetChannels() []*p2p.ChannelDescriptor { return br.reactor.GetChannels() }
|
||||
|
||||
// func (br *ByzantineReactor) AddPeer(peer p2p.Peer) {
|
||||
// if !br.reactor.IsRunning() {
|
||||
// return
|
||||
// }
|
||||
|
||||
// // Create peerState for peer
|
||||
// peerState := NewPeerState(peer).SetLogger(br.reactor.logger)
|
||||
// peer.Set(types.PeerStateKey, peerState)
|
||||
|
||||
// // Send our state to peer.
|
||||
// // If we're syncing, broadcast a RoundStepMessage later upon SwitchToConsensus().
|
||||
// if !br.reactor.waitSync {
|
||||
// br.reactor.sendNewRoundStepMessage(peer)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func (br *ByzantineReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
|
||||
// br.reactor.RemovePeer(peer, reason)
|
||||
// }
|
||||
|
||||
// func (br *ByzantineReactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) {
|
||||
// br.reactor.Receive(chID, peer, msgBytes)
|
||||
// }
|
||||
|
||||
// func (br *ByzantineReactor) InitPeer(peer p2p.Peer) p2p.Peer { return peer }
|
||||
|
||||
@@ -69,6 +69,9 @@ func configSetup(t *testing.T) *config.Config {
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.RemoveAll(configByzantineTest.RootDir) })
|
||||
|
||||
walDir := filepath.Dir(cfg.Consensus.WalFile())
|
||||
ensureDir(t, walDir, 0700)
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
@@ -239,7 +242,9 @@ func decideProposal(
|
||||
t.Helper()
|
||||
|
||||
cs1.mtx.Lock()
|
||||
block, blockParts, err := cs1.createProposalBlock(ctx)
|
||||
block, err := cs1.createProposalBlock(ctx)
|
||||
require.NoError(t, err)
|
||||
blockParts, err := block.MakePartSet(types.BlockPartSizeBytes)
|
||||
require.NoError(t, err)
|
||||
validRound := cs1.ValidRound
|
||||
chainID := cs1.state.ChainID
|
||||
@@ -370,7 +375,11 @@ func subscribeToVoter(ctx context.Context, t *testing.T, cs *State, addr []byte)
|
||||
vote := msg.Data().(types.EventDataVote)
|
||||
// we only fire for our own votes
|
||||
if bytes.Equal(addr, vote.Vote.ValidatorAddress) {
|
||||
ch <- msg
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case ch <- msg:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, types.EventQueryVote); err != nil {
|
||||
@@ -401,7 +410,10 @@ func subscribeToVoterBuffered(ctx context.Context, t *testing.T, cs *State, addr
|
||||
vote := msg.Data().(types.EventDataVote)
|
||||
// we only fire for our own votes
|
||||
if bytes.Equal(addr, vote.Vote.ValidatorAddress) {
|
||||
ch <- msg
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case ch <- msg:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -462,7 +474,6 @@ func newStateWithConfigAndBlockStore(
|
||||
logger.With("module", "mempool"),
|
||||
thisConfig.Mempool,
|
||||
proxyAppConnMem,
|
||||
0,
|
||||
)
|
||||
|
||||
if thisConfig.Consensus.WaitForTxs() {
|
||||
@@ -476,22 +487,26 @@ func newStateWithConfigAndBlockStore(
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
require.NoError(t, stateStore.Save(state))
|
||||
|
||||
blockExec := sm.NewBlockExecutor(stateStore, logger, proxyAppConnCon, mempool, evpool, blockStore)
|
||||
cs := NewState(ctx,
|
||||
eventBus := eventbus.NewDefault(logger.With("module", "events"))
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
blockExec := sm.NewBlockExecutor(stateStore, logger, proxyAppConnCon, mempool, evpool, blockStore, eventBus)
|
||||
cs, err := NewState(ctx,
|
||||
logger.With("module", "consensus"),
|
||||
thisConfig.Consensus,
|
||||
state,
|
||||
stateStore,
|
||||
blockExec,
|
||||
blockStore,
|
||||
mempool,
|
||||
evpool,
|
||||
eventBus,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cs.SetPrivValidator(ctx, pv)
|
||||
|
||||
eventBus := eventbus.NewDefault(logger.With("module", "events"))
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
cs.SetEventBus(eventBus)
|
||||
return cs
|
||||
}
|
||||
|
||||
@@ -775,6 +790,7 @@ func makeConsensusState(
|
||||
configOpts ...func(*config.Config),
|
||||
) ([]*State, cleanupFunc) {
|
||||
t.Helper()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
valSet, privVals := factory.ValidatorSet(ctx, t, nValidators, 30)
|
||||
genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
|
||||
@@ -789,7 +805,7 @@ func makeConsensusState(
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB()) // each state needs its own db
|
||||
state, err := sm.MakeGenesisState(genDoc)
|
||||
require.NoError(t, err)
|
||||
thisConfig, err := ResetConfig(t.TempDir(), fmt.Sprintf("%s_%d", testName, i))
|
||||
thisConfig, err := ResetConfig(tempDir, fmt.Sprintf("%s_%d", testName, i))
|
||||
require.NoError(t, err)
|
||||
|
||||
configRootDirs = append(configRootDirs, thisConfig.RootDir)
|
||||
@@ -798,7 +814,8 @@ func makeConsensusState(
|
||||
opt(thisConfig)
|
||||
}
|
||||
|
||||
ensureDir(t, filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
||||
walDir := filepath.Dir(thisConfig.Consensus.WalFile())
|
||||
ensureDir(t, walDir, 0700)
|
||||
|
||||
app := kvstore.NewApplication()
|
||||
closeFuncs = append(closeFuncs, app.Close)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
func TestReactorInvalidPrecommit(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
config := configSetup(t)
|
||||
@@ -49,14 +50,14 @@ func TestReactorInvalidPrecommit(t *testing.T) {
|
||||
byzState := rts.states[node.NodeID]
|
||||
byzReactor := rts.reactors[node.NodeID]
|
||||
|
||||
calledDoPrevote := false
|
||||
signal := make(chan struct{})
|
||||
// Update the doPrevote function to just send a valid precommit for a random
|
||||
// block and otherwise disable the priv validator.
|
||||
byzState.mtx.Lock()
|
||||
privVal := byzState.privValidator
|
||||
byzState.doPrevote = func(ctx context.Context, height int64, round int32) {
|
||||
defer close(signal)
|
||||
invalidDoPrevoteFunc(ctx, t, height, round, byzState, byzReactor, privVal)
|
||||
calledDoPrevote = true
|
||||
}
|
||||
byzState.mtx.Unlock()
|
||||
|
||||
@@ -72,16 +73,30 @@ func TestReactorInvalidPrecommit(t *testing.T) {
|
||||
go func(s eventbus.Subscription) {
|
||||
defer wg.Done()
|
||||
_, err := s.Next(ctx)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if !assert.NoError(t, err) {
|
||||
cancel() // cancel other subscribers on failure
|
||||
}
|
||||
}(sub)
|
||||
}
|
||||
}
|
||||
wait := make(chan struct{})
|
||||
go func() { defer close(wait); wg.Wait() }()
|
||||
|
||||
wg.Wait()
|
||||
if !calledDoPrevote {
|
||||
t.Fatal("test failed to run core logic")
|
||||
select {
|
||||
case <-wait:
|
||||
if _, ok := <-signal; !ok {
|
||||
t.Fatal("test condition did not fire")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if _, ok := <-signal; !ok {
|
||||
t.Fatal("test condition did not fire after timeout")
|
||||
return
|
||||
}
|
||||
case <-signal:
|
||||
// test passed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,19 +145,27 @@ func invalidDoPrevoteFunc(
|
||||
cs.privValidator = nil // disable priv val so we don't do normal votes
|
||||
cs.mtx.Unlock()
|
||||
|
||||
count := 0
|
||||
r.mtx.Lock()
|
||||
ids := make([]types.NodeID, 0, len(r.peers))
|
||||
for _, ps := range r.peers {
|
||||
ids = append(ids, ps.peerID)
|
||||
}
|
||||
r.mtx.Unlock()
|
||||
|
||||
count := 0
|
||||
for _, peerID := range ids {
|
||||
count++
|
||||
err := r.voteCh.Send(ctx, p2p.Envelope{
|
||||
To: ps.peerID,
|
||||
To: peerID,
|
||||
Message: &tmcons.Vote{
|
||||
Vote: precommit.ToProto(),
|
||||
},
|
||||
})
|
||||
// we want to have sent some of these votes,
|
||||
// but if the test completes without erroring
|
||||
// and we get here, we shouldn't error
|
||||
if errors.Is(err, context.Canceled) && count > 1 {
|
||||
// or not sending any messages, then we should
|
||||
// error.
|
||||
if errors.Is(err, context.Canceled) && count > 0 {
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) {
|
||||
|
||||
ensureNewEventOnChannel(t, newBlockCh) // first block gets committed
|
||||
ensureNoNewEventOnChannel(t, newBlockCh)
|
||||
deliverTxsRange(ctx, t, cs, 0, 1)
|
||||
checkTxsRange(ctx, t, cs, 0, 1)
|
||||
ensureNewEventOnChannel(t, newBlockCh) // commit txs
|
||||
ensureNewEventOnChannel(t, newBlockCh) // commit updated app hash
|
||||
ensureNoNewEventOnChannel(t, newBlockCh)
|
||||
@@ -118,7 +118,7 @@ func TestMempoolProgressInHigherRound(t *testing.T) {
|
||||
round = 0
|
||||
|
||||
ensureNewRound(t, newRoundCh, height, round) // first round at next height
|
||||
deliverTxsRange(ctx, t, cs, 0, 1) // we deliver txs, but dont set a proposal so we get the next round
|
||||
checkTxsRange(ctx, t, cs, 0, 1) // we deliver txs, but don't set a proposal so we get the next round
|
||||
ensureNewTimeout(t, timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds())
|
||||
|
||||
round++ // moving to the next round
|
||||
@@ -126,7 +126,7 @@ func TestMempoolProgressInHigherRound(t *testing.T) {
|
||||
ensureNewEventOnChannel(t, newBlockCh) // now we can commit the block
|
||||
}
|
||||
|
||||
func deliverTxsRange(ctx context.Context, t *testing.T, cs *State, start, end int) {
|
||||
func checkTxsRange(ctx context.Context, t *testing.T, cs *State, start, end int) {
|
||||
t.Helper()
|
||||
// Deliver some txs.
|
||||
for i := start; i < end; i++ {
|
||||
@@ -159,7 +159,7 @@ func TestMempoolTxConcurrentWithCommit(t *testing.T) {
|
||||
newBlockHeaderCh := subscribe(ctx, t, cs.eventBus, types.EventQueryNewBlockHeader)
|
||||
|
||||
const numTxs int64 = 3000
|
||||
go deliverTxsRange(ctx, t, cs, 0, int(numTxs))
|
||||
go checkTxsRange(ctx, t, cs, 0, int(numTxs))
|
||||
|
||||
startTestRound(ctx, cs, cs.Height, cs.Round)
|
||||
for n := int64(0); n < numTxs; {
|
||||
@@ -192,8 +192,8 @@ func TestMempoolRmBadTx(t *testing.T) {
|
||||
txBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(txBytes, uint64(0))
|
||||
|
||||
resDeliver := app.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{txBytes}})
|
||||
assert.False(t, resDeliver.Txs[0].IsErr(), fmt.Sprintf("expected no error. got %v", resDeliver))
|
||||
resFinalize := app.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{txBytes}})
|
||||
assert.False(t, resFinalize.TxResults[0].IsErr(), fmt.Sprintf("expected no error. got %v", resFinalize))
|
||||
|
||||
resCommit := app.Commit()
|
||||
assert.True(t, len(resCommit.Data) > 0)
|
||||
@@ -212,7 +212,7 @@ func TestMempoolRmBadTx(t *testing.T) {
|
||||
checkTxRespCh <- struct{}{}
|
||||
}, mempool.TxInfo{})
|
||||
if err != nil {
|
||||
t.Errorf("error after CheckTx: %w", err)
|
||||
t.Errorf("error after CheckTx: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -265,20 +265,20 @@ func (app *CounterApplication) Info(req abci.RequestInfo) abci.ResponseInfo {
|
||||
}
|
||||
|
||||
func (app *CounterApplication) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock {
|
||||
respTxs := make([]*abci.ResponseDeliverTx, len(req.Txs))
|
||||
respTxs := make([]*abci.ExecTxResult, len(req.Txs))
|
||||
for i, tx := range req.Txs {
|
||||
txValue := txAsUint64(tx)
|
||||
if txValue != uint64(app.txCount) {
|
||||
respTxs[i] = &abci.ResponseDeliverTx{
|
||||
respTxs[i] = &abci.ExecTxResult{
|
||||
Code: code.CodeTypeBadNonce,
|
||||
Log: fmt.Sprintf("Invalid nonce. Expected %d, got %d", app.txCount, txValue),
|
||||
}
|
||||
continue
|
||||
}
|
||||
app.txCount++
|
||||
respTxs[i] = &abci.ResponseDeliverTx{Code: code.CodeTypeOK}
|
||||
respTxs[i] = &abci.ExecTxResult{Code: code.CodeTypeOK}
|
||||
}
|
||||
return abci.ResponseFinalizeBlock{Txs: respTxs}
|
||||
return abci.ResponseFinalizeBlock{TxResults: respTxs}
|
||||
}
|
||||
|
||||
func (app *CounterApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
|
||||
@@ -310,7 +310,7 @@ func (app *CounterApplication) Commit() abci.ResponseCommit {
|
||||
|
||||
func (app *CounterApplication) PrepareProposal(
|
||||
req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
|
||||
return abci.ResponsePrepareProposal{BlockData: req.BlockData}
|
||||
return abci.ResponsePrepareProposal{}
|
||||
}
|
||||
|
||||
func (app *CounterApplication) ProcessProposal(
|
||||
|
||||
@@ -203,7 +203,7 @@ func (p *pbtsTestHarness) nextHeight(ctx context.Context, t *testing.T, proposer
|
||||
|
||||
ensureNewRound(t, p.roundCh, p.currentHeight, p.currentRound)
|
||||
|
||||
b, _, err := p.observedState.createProposalBlock(ctx)
|
||||
b, err := p.observedState.createProposalBlock(ctx)
|
||||
require.NoError(t, err)
|
||||
b.Height = p.currentHeight
|
||||
b.Header.Height = p.currentHeight
|
||||
|
||||
@@ -138,6 +138,7 @@ func NewReactor(
|
||||
cs *State,
|
||||
channelCreator p2p.ChannelCreator,
|
||||
peerUpdates *p2p.PeerUpdates,
|
||||
eventBus *eventbus.EventBus,
|
||||
waitSync bool,
|
||||
metrics *Metrics,
|
||||
) (*Reactor, error) {
|
||||
@@ -166,6 +167,7 @@ func NewReactor(
|
||||
state: cs,
|
||||
waitSync: waitSync,
|
||||
peers: make(map[types.NodeID]*PeerState),
|
||||
eventBus: eventBus,
|
||||
Metrics: metrics,
|
||||
stateCh: stateCh,
|
||||
dataCh: dataCh,
|
||||
@@ -226,12 +228,6 @@ func (r *Reactor) OnStop() {
|
||||
}
|
||||
}
|
||||
|
||||
// SetEventBus sets the reactor's event bus.
|
||||
func (r *Reactor) SetEventBus(b *eventbus.EventBus) {
|
||||
r.eventBus = b
|
||||
r.state.SetEventBus(b)
|
||||
}
|
||||
|
||||
// WaitSync returns whether the consensus reactor is waiting for state/block sync.
|
||||
func (r *Reactor) WaitSync() bool {
|
||||
r.mtx.RLock()
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -110,13 +109,12 @@ func setup(
|
||||
state,
|
||||
chCreator(nodeID),
|
||||
node.MakePeerUpdates(ctx, t),
|
||||
state.eventBus,
|
||||
true,
|
||||
NopMetrics(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.SetEventBus(state.eventBus)
|
||||
|
||||
blocksSub, err := state.eventBus.SubscribeWithArgs(ctx, tmpubsub.SubscribeArgs{
|
||||
ClientID: testSubscriber,
|
||||
Query: types.EventQueryNewBlock,
|
||||
@@ -461,12 +459,12 @@ func TestReactorWithEvidence(t *testing.T) {
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
state, err := sm.MakeGenesisState(genDoc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, stateStore.Save(state))
|
||||
thisConfig, err := ResetConfig(t.TempDir(), fmt.Sprintf("%s_%d", testName, i))
|
||||
require.NoError(t, err)
|
||||
|
||||
defer os.RemoveAll(thisConfig.RootDir)
|
||||
|
||||
ensureDir(t, path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
||||
app := kvstore.NewApplication()
|
||||
vals := types.TM2PB.ValidatorUpdates(state.Validators)
|
||||
app.InitChain(abci.RequestInitChain{Validators: vals})
|
||||
@@ -483,7 +481,6 @@ func TestReactorWithEvidence(t *testing.T) {
|
||||
log.TestingLogger().With("module", "mempool"),
|
||||
thisConfig.Mempool,
|
||||
proxyAppConnMem,
|
||||
0,
|
||||
)
|
||||
|
||||
if thisConfig.Consensus.WaitForTxs() {
|
||||
@@ -504,15 +501,15 @@ func TestReactorWithEvidence(t *testing.T) {
|
||||
|
||||
evpool2 := sm.EmptyEvidencePool{}
|
||||
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool, blockStore)
|
||||
|
||||
cs := NewState(ctx, logger.With("validator", i, "module", "consensus"),
|
||||
thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool2)
|
||||
cs.SetPrivValidator(ctx, pv)
|
||||
|
||||
eventBus := eventbus.NewDefault(log.TestingLogger().With("module", "events"))
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
cs.SetEventBus(eventBus)
|
||||
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyAppConnCon, mempool, evpool, blockStore, eventBus)
|
||||
|
||||
cs, err := NewState(ctx, logger.With("validator", i, "module", "consensus"),
|
||||
thisConfig.Consensus, stateStore, blockExec, blockStore, mempool, evpool2, eventBus)
|
||||
require.NoError(t, err)
|
||||
cs.SetPrivValidator(ctx, pv)
|
||||
|
||||
cs.SetTimeoutTicker(tickerFunc())
|
||||
|
||||
@@ -565,7 +562,6 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) {
|
||||
c.Consensus.CreateEmptyBlocks = false
|
||||
},
|
||||
)
|
||||
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
rts := setup(ctx, t, n, states, 100) // buffer must be large enough to not deadlock
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/merkle"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
@@ -204,7 +205,7 @@ type Handshaker struct {
|
||||
stateStore sm.Store
|
||||
initialState sm.State
|
||||
store sm.BlockStore
|
||||
eventBus types.BlockEventPublisher
|
||||
eventBus *eventbus.EventBus
|
||||
genDoc *types.GenesisDoc
|
||||
logger log.Logger
|
||||
|
||||
@@ -216,7 +217,7 @@ func NewHandshaker(
|
||||
stateStore sm.Store,
|
||||
state sm.State,
|
||||
store sm.BlockStore,
|
||||
eventBus types.BlockEventPublisher,
|
||||
eventBus *eventbus.EventBus,
|
||||
genDoc *types.GenesisDoc,
|
||||
) *Handshaker {
|
||||
|
||||
@@ -237,10 +238,10 @@ func (h *Handshaker) NBlocks() int {
|
||||
}
|
||||
|
||||
// TODO: retry the handshake/replay if it fails ?
|
||||
func (h *Handshaker) Handshake(ctx context.Context, proxyApp proxy.AppConns) error {
|
||||
func (h *Handshaker) Handshake(ctx context.Context, appClient abciclient.Client) error {
|
||||
|
||||
// Handshake is done via ABCI Info on the query conn.
|
||||
res, err := proxyApp.Query().Info(ctx, proxy.RequestInfo)
|
||||
res, err := appClient.Info(ctx, proxy.RequestInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error calling Info: %w", err)
|
||||
}
|
||||
@@ -264,7 +265,7 @@ func (h *Handshaker) Handshake(ctx context.Context, proxyApp proxy.AppConns) err
|
||||
}
|
||||
|
||||
// Replay blocks up to the latest in the blockstore.
|
||||
_, err = h.ReplayBlocks(ctx, h.initialState, appHash, blockHeight, proxyApp)
|
||||
_, err = h.ReplayBlocks(ctx, h.initialState, appHash, blockHeight, appClient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error on replay: %w", err)
|
||||
}
|
||||
@@ -285,7 +286,7 @@ func (h *Handshaker) ReplayBlocks(
|
||||
state sm.State,
|
||||
appHash []byte,
|
||||
appBlockHeight int64,
|
||||
proxyApp proxy.AppConns,
|
||||
appClient abciclient.Client,
|
||||
) ([]byte, error) {
|
||||
storeBlockBase := h.store.Base()
|
||||
storeBlockHeight := h.store.Height()
|
||||
@@ -316,7 +317,7 @@ func (h *Handshaker) ReplayBlocks(
|
||||
Validators: nextVals,
|
||||
AppStateBytes: h.genDoc.AppState,
|
||||
}
|
||||
res, err := proxyApp.Consensus().InitChain(ctx, req)
|
||||
res, err := appClient.InitChain(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -390,7 +391,7 @@ func (h *Handshaker) ReplayBlocks(
|
||||
// Either the app is asking for replay, or we're all synced up.
|
||||
if appBlockHeight < storeBlockHeight {
|
||||
// the app is behind, so replay blocks, but no need to go through WAL (state is already synced to store)
|
||||
return h.replayBlocks(ctx, state, proxyApp, appBlockHeight, storeBlockHeight, false)
|
||||
return h.replayBlocks(ctx, state, appClient, appBlockHeight, storeBlockHeight, false)
|
||||
|
||||
} else if appBlockHeight == storeBlockHeight {
|
||||
// We're good!
|
||||
@@ -405,7 +406,7 @@ func (h *Handshaker) ReplayBlocks(
|
||||
case appBlockHeight < stateBlockHeight:
|
||||
// the app is further behind than it should be, so replay blocks
|
||||
// but leave the last block to go through the WAL
|
||||
return h.replayBlocks(ctx, state, proxyApp, appBlockHeight, storeBlockHeight, true)
|
||||
return h.replayBlocks(ctx, state, appClient, appBlockHeight, storeBlockHeight, true)
|
||||
|
||||
case appBlockHeight == stateBlockHeight:
|
||||
// We haven't run Commit (both the state and app are one block behind),
|
||||
@@ -413,7 +414,7 @@ func (h *Handshaker) ReplayBlocks(
|
||||
// NOTE: We could instead use the cs.WAL on cs.Start,
|
||||
// but we'd have to allow the WAL to replay a block that wrote it's #ENDHEIGHT
|
||||
h.logger.Info("Replay last block using real app")
|
||||
state, err = h.replayBlock(ctx, state, storeBlockHeight, proxyApp.Consensus())
|
||||
state, err = h.replayBlock(ctx, state, storeBlockHeight, appClient)
|
||||
return state.AppHash, err
|
||||
|
||||
case appBlockHeight == storeBlockHeight:
|
||||
@@ -426,6 +427,9 @@ func (h *Handshaker) ReplayBlocks(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := mockApp.Start(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.logger.Info("Replay last block using mock app")
|
||||
state, err = h.replayBlock(ctx, state, storeBlockHeight, mockApp)
|
||||
@@ -445,7 +449,7 @@ func (h *Handshaker) ReplayBlocks(
|
||||
func (h *Handshaker) replayBlocks(
|
||||
ctx context.Context,
|
||||
state sm.State,
|
||||
proxyApp proxy.AppConns,
|
||||
appClient abciclient.Client,
|
||||
appBlockHeight,
|
||||
storeBlockHeight int64,
|
||||
mutateState bool) ([]byte, error) {
|
||||
@@ -480,17 +484,15 @@ func (h *Handshaker) replayBlocks(
|
||||
if i == finalBlock && !mutateState {
|
||||
// We emit events for the index services at the final block due to the sync issue when
|
||||
// the node shutdown during the block committing status.
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
h.stateStore, h.logger, proxyApp.Consensus(), emptyMempool{}, sm.EmptyEvidencePool{}, h.store)
|
||||
blockExec.SetEventBus(h.eventBus)
|
||||
blockExec := sm.NewBlockExecutor(h.stateStore, h.logger, appClient, emptyMempool{}, sm.EmptyEvidencePool{}, h.store, h.eventBus)
|
||||
appHash, err = sm.ExecCommitBlock(ctx,
|
||||
blockExec, proxyApp.Consensus(), block, h.logger, h.stateStore, h.genDoc.InitialHeight, state)
|
||||
blockExec, appClient, block, h.logger, h.stateStore, h.genDoc.InitialHeight, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
appHash, err = sm.ExecCommitBlock(ctx,
|
||||
nil, proxyApp.Consensus(), block, h.logger, h.stateStore, h.genDoc.InitialHeight, state)
|
||||
nil, appClient, block, h.logger, h.stateStore, h.genDoc.InitialHeight, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -501,7 +503,7 @@ func (h *Handshaker) replayBlocks(
|
||||
|
||||
if mutateState {
|
||||
// sync the final block
|
||||
state, err = h.replayBlock(ctx, state, storeBlockHeight, proxyApp.Consensus())
|
||||
state, err = h.replayBlock(ctx, state, storeBlockHeight, appClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -517,15 +519,14 @@ func (h *Handshaker) replayBlock(
|
||||
ctx context.Context,
|
||||
state sm.State,
|
||||
height int64,
|
||||
proxyApp proxy.AppConnConsensus,
|
||||
appClient abciclient.Client,
|
||||
) (sm.State, error) {
|
||||
block := h.store.LoadBlock(height)
|
||||
meta := h.store.LoadBlockMeta(height)
|
||||
|
||||
// Use stubs for both mempool and evidence pool since no transactions nor
|
||||
// evidence are needed here - block already exists.
|
||||
blockExec := sm.NewBlockExecutor(h.stateStore, h.logger, proxyApp, emptyMempool{}, sm.EmptyEvidencePool{}, h.store)
|
||||
blockExec.SetEventBus(h.eventBus)
|
||||
blockExec := sm.NewBlockExecutor(h.stateStore, h.logger, appClient, emptyMempool{}, sm.EmptyEvidencePool{}, h.store, h.eventBus)
|
||||
|
||||
var err error
|
||||
state, err = blockExec.ApplyBlock(ctx, state, meta.BlockID, block)
|
||||
|
||||
@@ -84,7 +84,7 @@ func (cs *State) ReplayFile(ctx context.Context, file string, console bool) erro
|
||||
return err
|
||||
}
|
||||
|
||||
pb := newPlayback(file, fp, cs, cs.state.Copy())
|
||||
pb := newPlayback(file, fp, cs, cs.stateStore)
|
||||
defer pb.fp.Close()
|
||||
|
||||
var nextN int // apply N msgs in a row
|
||||
@@ -126,17 +126,17 @@ type playback struct {
|
||||
count int // how many lines/msgs into the file are we
|
||||
|
||||
// replays can be reset to beginning
|
||||
fileName string // so we can close/reopen the file
|
||||
genesisState sm.State // so the replay session knows where to restart from
|
||||
fileName string // so we can close/reopen the file
|
||||
stateStore sm.Store
|
||||
}
|
||||
|
||||
func newPlayback(fileName string, fp *os.File, cs *State, genState sm.State) *playback {
|
||||
func newPlayback(fileName string, fp *os.File, cs *State, store sm.Store) *playback {
|
||||
return &playback{
|
||||
cs: cs,
|
||||
fp: fp,
|
||||
fileName: fileName,
|
||||
genesisState: genState,
|
||||
dec: NewWALDecoder(fp),
|
||||
cs: cs,
|
||||
fp: fp,
|
||||
fileName: fileName,
|
||||
stateStore: store,
|
||||
dec: NewWALDecoder(fp),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,9 +145,11 @@ func (pb *playback) replayReset(ctx context.Context, count int, newStepSub event
|
||||
pb.cs.Stop()
|
||||
pb.cs.Wait()
|
||||
|
||||
newCS := NewState(ctx, pb.cs.logger, pb.cs.config, pb.genesisState.Copy(), pb.cs.blockExec,
|
||||
pb.cs.blockStore, pb.cs.txNotifier, pb.cs.evpool)
|
||||
newCS.SetEventBus(pb.cs.eventBus)
|
||||
newCS, err := NewState(ctx, pb.cs.logger, pb.cs.config, pb.stateStore, pb.cs.blockExec,
|
||||
pb.cs.blockStore, pb.cs.txNotifier, pb.cs.evpool, pb.cs.eventBus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newCS.startForReplay()
|
||||
|
||||
if err := pb.fp.Close(); err != nil {
|
||||
@@ -323,9 +325,12 @@ func newConsensusStateForReplay(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create proxyAppConn connection (consensus, mempool, query)
|
||||
clientCreator, _ := proxy.DefaultClientCreator(logger, cfg.ProxyApp, cfg.ABCI, cfg.DBDir())
|
||||
proxyApp := proxy.NewAppConns(clientCreator, logger, proxy.NopMetrics())
|
||||
client, _, err := proxy.ClientFactory(logger, cfg.ProxyApp, cfg.ABCI, cfg.DBDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyApp := proxy.New(client, logger, proxy.NopMetrics())
|
||||
err = proxyApp.Start(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("starting proxy app conns: %w", err)
|
||||
@@ -343,11 +348,12 @@ func newConsensusStateForReplay(
|
||||
}
|
||||
|
||||
mempool, evpool := emptyMempool{}, sm.EmptyEvidencePool{}
|
||||
blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp.Consensus(), mempool, evpool, blockStore)
|
||||
blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp, mempool, evpool, blockStore, eventBus)
|
||||
|
||||
consensusState := NewState(ctx, logger, csConfig, state.Copy(), blockExec,
|
||||
blockStore, mempool, evpool)
|
||||
|
||||
consensusState.SetEventBus(eventBus)
|
||||
consensusState, err := NewState(ctx, logger, csConfig, stateStore, blockExec,
|
||||
blockStore, mempool, evpool, eventBus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return consensusState, nil
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func (emptyMempool) Update(
|
||||
_ context.Context,
|
||||
_ int64,
|
||||
_ types.Txs,
|
||||
_ []*abci.ResponseDeliverTx,
|
||||
_ []*abci.ExecTxResult,
|
||||
_ mempool.PreCheckFunc,
|
||||
_ mempool.PostCheckFunc,
|
||||
) error {
|
||||
@@ -61,22 +61,11 @@ func newMockProxyApp(
|
||||
logger log.Logger,
|
||||
appHash []byte,
|
||||
abciResponses *tmstate.ABCIResponses,
|
||||
) (proxy.AppConnConsensus, error) {
|
||||
|
||||
clientCreator := abciclient.NewLocalCreator(&mockProxyApp{
|
||||
) (abciclient.Client, error) {
|
||||
return proxy.New(abciclient.NewLocalClient(logger, &mockProxyApp{
|
||||
appHash: appHash,
|
||||
abciResponses: abciResponses,
|
||||
})
|
||||
cli, err := clientCreator(logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = cli.Start(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proxy.NewAppConnConsensus(cli, proxy.NopMetrics()), nil
|
||||
}), logger, proxy.NopMetrics()), nil
|
||||
}
|
||||
|
||||
type mockProxyApp struct {
|
||||
|
||||
@@ -35,7 +35,6 @@ import (
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
@@ -381,7 +380,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
|
||||
newValidatorTx1 := kvstore.MakeValSetChangeTx(valPubKey1ABCI, testMinPower)
|
||||
err = assertMempool(t, css[0].txNotifier).CheckTx(ctx, newValidatorTx1, nil, mempool.TxInfo{})
|
||||
assert.NoError(t, err)
|
||||
propBlock, _, err := css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
propBlock, err := css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
require.NoError(t, err)
|
||||
propBlockParts, err := propBlock.MakePartSet(partSize)
|
||||
require.NoError(t, err)
|
||||
@@ -415,7 +414,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
|
||||
updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25)
|
||||
err = assertMempool(t, css[0].txNotifier).CheckTx(ctx, updateValidatorTx1, nil, mempool.TxInfo{})
|
||||
assert.NoError(t, err)
|
||||
propBlock, _, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
propBlock, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
require.NoError(t, err)
|
||||
propBlockParts, err = propBlock.MakePartSet(partSize)
|
||||
require.NoError(t, err)
|
||||
@@ -456,7 +455,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
|
||||
newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower)
|
||||
err = assertMempool(t, css[0].txNotifier).CheckTx(ctx, newValidatorTx3, nil, mempool.TxInfo{})
|
||||
assert.NoError(t, err)
|
||||
propBlock, _, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
propBlock, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
require.NoError(t, err)
|
||||
propBlockParts, err = propBlock.MakePartSet(partSize)
|
||||
require.NoError(t, err)
|
||||
@@ -544,7 +543,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
|
||||
removeValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, 0)
|
||||
err = assertMempool(t, css[0].txNotifier).CheckTx(ctx, removeValidatorTx3, nil, mempool.TxInfo{})
|
||||
assert.NoError(t, err)
|
||||
propBlock, _, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
propBlock, err = css[0].createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
require.NoError(t, err)
|
||||
propBlockParts, err = propBlock.MakePartSet(partSize)
|
||||
require.NoError(t, err)
|
||||
@@ -652,61 +651,6 @@ func TestHandshakeReplayNone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test mockProxyApp should not panic when app return ABCIResponses with some empty ResponseDeliverTx
|
||||
func TestMockProxyApp(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
sim := setupSimulator(ctx, t) // setup config and simulator
|
||||
cfg := sim.Config
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
logger := log.TestingLogger()
|
||||
var validTxs, invalidTxs = 0, 0
|
||||
txCount := 0
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
abciResWithEmptyDeliverTx := new(tmstate.ABCIResponses)
|
||||
abciResWithEmptyDeliverTx.FinalizeBlock = new(abci.ResponseFinalizeBlock)
|
||||
abciResWithEmptyDeliverTx.FinalizeBlock.Txs = make([]*abci.ResponseDeliverTx, 0)
|
||||
abciResWithEmptyDeliverTx.FinalizeBlock.Txs = append(abciResWithEmptyDeliverTx.FinalizeBlock.Txs, &abci.ResponseDeliverTx{})
|
||||
|
||||
// called when saveABCIResponses:
|
||||
bytes, err := proto.Marshal(abciResWithEmptyDeliverTx)
|
||||
require.NoError(t, err)
|
||||
loadedAbciRes := new(tmstate.ABCIResponses)
|
||||
|
||||
// this also happens sm.LoadABCIResponses
|
||||
err = proto.Unmarshal(bytes, loadedAbciRes)
|
||||
require.NoError(t, err)
|
||||
|
||||
mock, err := newMockProxyApp(ctx, logger, []byte("mock_hash"), loadedAbciRes)
|
||||
require.NoError(t, err)
|
||||
|
||||
abciRes := new(tmstate.ABCIResponses)
|
||||
abciRes.FinalizeBlock = new(abci.ResponseFinalizeBlock)
|
||||
abciRes.FinalizeBlock.Txs = make([]*abci.ResponseDeliverTx, len(loadedAbciRes.FinalizeBlock.Txs))
|
||||
|
||||
someTx := []byte("tx")
|
||||
resp, err := mock.FinalizeBlock(ctx, abci.RequestFinalizeBlock{Txs: [][]byte{someTx}})
|
||||
require.NoError(t, err)
|
||||
// TODO: make use of res.Log
|
||||
// TODO: make use of this info
|
||||
// Blocks may include invalid txs.
|
||||
for _, tx := range resp.Txs {
|
||||
if tx.Code == abci.CodeTypeOK {
|
||||
validTxs++
|
||||
} else {
|
||||
invalidTxs++
|
||||
}
|
||||
txCount++
|
||||
}
|
||||
})
|
||||
require.Equal(t, 1, txCount)
|
||||
require.Equal(t, 1, validTxs)
|
||||
require.Zero(t, invalidTxs)
|
||||
}
|
||||
|
||||
func tempWALWithData(t *testing.T, data []byte) string {
|
||||
t.Helper()
|
||||
|
||||
@@ -804,16 +748,19 @@ func testHandshakeReplay(
|
||||
filepath.Join(cfg.DBDir(), fmt.Sprintf("replay_test_%d_%d_a_r%d", nBlocks, mode, rand.Int())))
|
||||
t.Cleanup(func() { require.NoError(t, kvstoreApp.Close()) })
|
||||
|
||||
clientCreator2 := abciclient.NewLocalCreator(kvstoreApp)
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
clientCreator2 := abciclient.NewLocalClient(logger, kvstoreApp)
|
||||
if nBlocks > 0 {
|
||||
// run nBlocks against a new client to build up the app state.
|
||||
// use a throwaway tendermint state
|
||||
proxyApp := proxy.NewAppConns(clientCreator2, logger, proxy.NopMetrics())
|
||||
proxyApp := proxy.New(clientCreator2, logger, proxy.NopMetrics())
|
||||
stateDB1 := dbm.NewMemDB()
|
||||
stateStore := sm.NewStore(stateDB1)
|
||||
err := stateStore.Save(genesisState)
|
||||
require.NoError(t, err)
|
||||
buildAppStateFromChain(ctx, t, proxyApp, stateStore, sim.Mempool, sim.Evpool, genesisState, chain, nBlocks, mode, store)
|
||||
buildAppStateFromChain(ctx, t, proxyApp, stateStore, sim.Mempool, sim.Evpool, genesisState, chain, eventBus, nBlocks, mode, store)
|
||||
}
|
||||
|
||||
// Prune block store if requested
|
||||
@@ -828,10 +775,11 @@ func testHandshakeReplay(
|
||||
// now start the app using the handshake - it should sync
|
||||
genDoc, err := sm.MakeGenesisDocFromFile(cfg.GenesisFile())
|
||||
require.NoError(t, err)
|
||||
handshaker := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc)
|
||||
proxyApp := proxy.NewAppConns(clientCreator2, logger, proxy.NopMetrics())
|
||||
handshaker := NewHandshaker(logger, stateStore, state, store, eventBus, genDoc)
|
||||
proxyApp := proxy.New(clientCreator2, logger, proxy.NopMetrics())
|
||||
require.NoError(t, proxyApp.Start(ctx), "Error starting proxy app connections")
|
||||
|
||||
require.True(t, proxyApp.IsRunning())
|
||||
require.NotNil(t, proxyApp)
|
||||
t.Cleanup(func() { cancel(); proxyApp.Wait() })
|
||||
|
||||
err = handshaker.Handshake(ctx, proxyApp)
|
||||
@@ -842,7 +790,7 @@ func testHandshakeReplay(
|
||||
require.NoError(t, err, "Error on abci handshake")
|
||||
|
||||
// get the latest app hash from the app
|
||||
res, err := proxyApp.Query().Info(ctx, abci.RequestInfo{Version: ""})
|
||||
res, err := proxyApp.Info(ctx, abci.RequestInfo{Version: ""})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -875,11 +823,12 @@ func applyBlock(
|
||||
evpool sm.EvidencePool,
|
||||
st sm.State,
|
||||
blk *types.Block,
|
||||
proxyApp proxy.AppConns,
|
||||
appClient abciclient.Client,
|
||||
blockStore *mockBlockStore,
|
||||
eventBus *eventbus.EventBus,
|
||||
) sm.State {
|
||||
testPartSize := types.BlockPartSizeBytes
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool, blockStore)
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), appClient, mempool, evpool, blockStore, eventBus)
|
||||
|
||||
bps, err := blk.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
@@ -892,23 +841,24 @@ func applyBlock(
|
||||
func buildAppStateFromChain(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
proxyApp proxy.AppConns,
|
||||
appClient abciclient.Client,
|
||||
stateStore sm.Store,
|
||||
mempool mempool.Mempool,
|
||||
evpool sm.EvidencePool,
|
||||
state sm.State,
|
||||
chain []*types.Block,
|
||||
eventBus *eventbus.EventBus,
|
||||
nBlocks int,
|
||||
mode uint,
|
||||
blockStore *mockBlockStore,
|
||||
) {
|
||||
t.Helper()
|
||||
// start a new app without handshake, play nBlocks blocks
|
||||
require.NoError(t, proxyApp.Start(ctx))
|
||||
require.NoError(t, appClient.Start(ctx))
|
||||
|
||||
state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version
|
||||
validators := types.TM2PB.ValidatorUpdates(state.Validators)
|
||||
_, err := proxyApp.Consensus().InitChain(ctx, abci.RequestInitChain{
|
||||
_, err := appClient.InitChain(ctx, abci.RequestInitChain{
|
||||
Validators: validators,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -919,18 +869,18 @@ func buildAppStateFromChain(
|
||||
case 0:
|
||||
for i := 0; i < nBlocks; i++ {
|
||||
block := chain[i]
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore)
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, appClient, blockStore, eventBus)
|
||||
}
|
||||
case 1, 2, 3:
|
||||
for i := 0; i < nBlocks-1; i++ {
|
||||
block := chain[i]
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore)
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, appClient, blockStore, eventBus)
|
||||
}
|
||||
|
||||
if mode == 2 || mode == 3 {
|
||||
// update the kvstore height and apphash
|
||||
// as if we ran commit but not
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, chain[nBlocks-1], proxyApp, blockStore)
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, chain[nBlocks-1], appClient, blockStore, eventBus)
|
||||
}
|
||||
default:
|
||||
require.Fail(t, "unknown mode %v", mode)
|
||||
@@ -958,37 +908,40 @@ func buildTMStateFromChain(
|
||||
kvstoreApp := kvstore.NewPersistentKVStoreApplication(logger,
|
||||
filepath.Join(cfg.DBDir(), fmt.Sprintf("replay_test_%d_%d_t", nBlocks, mode)))
|
||||
defer kvstoreApp.Close()
|
||||
clientCreator := abciclient.NewLocalCreator(kvstoreApp)
|
||||
client := abciclient.NewLocalClient(logger, kvstoreApp)
|
||||
|
||||
proxyApp := proxy.NewAppConns(clientCreator, logger, proxy.NopMetrics())
|
||||
proxyApp := proxy.New(client, logger, proxy.NopMetrics())
|
||||
require.NoError(t, proxyApp.Start(ctx))
|
||||
|
||||
state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version
|
||||
validators := types.TM2PB.ValidatorUpdates(state.Validators)
|
||||
_, err := proxyApp.Consensus().InitChain(ctx, abci.RequestInitChain{
|
||||
_, err := proxyApp.InitChain(ctx, abci.RequestInitChain{
|
||||
Validators: validators,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, stateStore.Save(state))
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
switch mode {
|
||||
case 0:
|
||||
// sync right up
|
||||
for _, block := range chain {
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore)
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore, eventBus)
|
||||
}
|
||||
|
||||
case 1, 2, 3:
|
||||
// sync up to the penultimate as if we stored the block.
|
||||
// whether we commit or not depends on the appHash
|
||||
for _, block := range chain[:len(chain)-1] {
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore)
|
||||
state = applyBlock(ctx, t, stateStore, mempool, evpool, state, block, proxyApp, blockStore, eventBus)
|
||||
}
|
||||
|
||||
// apply the final block to a state copy so we can
|
||||
// get the right next appHash but keep the state back
|
||||
applyBlock(ctx, t, stateStore, mempool, evpool, state, chain[len(chain)-1], proxyApp, blockStore)
|
||||
applyBlock(ctx, t, stateStore, mempool, evpool, state, chain[len(chain)-1], proxyApp, blockStore, eventBus)
|
||||
default:
|
||||
require.Fail(t, "unknown mode %v", mode)
|
||||
}
|
||||
@@ -1025,20 +978,23 @@ func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) {
|
||||
|
||||
logger := log.TestingLogger()
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
// 2. Tendermint must panic if app returns wrong hash for the first block
|
||||
// - RANDOM HASH
|
||||
// - 0x02
|
||||
// - 0x03
|
||||
{
|
||||
app := &badApp{numBlocks: 3, allHashesAreWrong: true}
|
||||
clientCreator := abciclient.NewLocalCreator(app)
|
||||
proxyApp := proxy.NewAppConns(clientCreator, logger, proxy.NopMetrics())
|
||||
client := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(client, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { cancel(); proxyApp.Wait() })
|
||||
|
||||
assert.Panics(t, func() {
|
||||
h := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc)
|
||||
h := NewHandshaker(logger, stateStore, state, store, eventBus, genDoc)
|
||||
if err = h.Handshake(ctx, proxyApp); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
@@ -1051,14 +1007,14 @@ func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) {
|
||||
// - RANDOM HASH
|
||||
{
|
||||
app := &badApp{numBlocks: 3, onlyLastHashIsWrong: true}
|
||||
clientCreator := abciclient.NewLocalCreator(app)
|
||||
proxyApp := proxy.NewAppConns(clientCreator, logger, proxy.NopMetrics())
|
||||
client := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(client, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { cancel(); proxyApp.Wait() })
|
||||
|
||||
assert.Panics(t, func() {
|
||||
h := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc)
|
||||
h := NewHandshaker(logger, stateStore, state, store, eventBus, genDoc)
|
||||
if err = h.Handshake(ctx, proxyApp); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
@@ -1282,12 +1238,16 @@ func TestHandshakeUpdatesValidators(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
votePower := 10 + int64(rand.Uint32())
|
||||
val, _, err := factory.Validator(ctx, votePower)
|
||||
require.NoError(t, err)
|
||||
vals := types.NewValidatorSet([]*types.Validator{val})
|
||||
app := &initChainApp{vals: types.TM2PB.ValidatorUpdates(vals)}
|
||||
clientCreator := abciclient.NewLocalCreator(app)
|
||||
client := abciclient.NewLocalClient(logger, app)
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
cfg, err := ResetConfig(t.TempDir(), "handshake_test_")
|
||||
require.NoError(t, err)
|
||||
@@ -1306,9 +1266,8 @@ func TestHandshakeUpdatesValidators(t *testing.T) {
|
||||
genDoc, err := sm.MakeGenesisDocFromFile(cfg.GenesisFile())
|
||||
require.NoError(t, err)
|
||||
|
||||
logger := log.TestingLogger()
|
||||
handshaker := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc)
|
||||
proxyApp := proxy.NewAppConns(clientCreator, logger, proxy.NopMetrics())
|
||||
handshaker := NewHandshaker(logger, stateStore, state, store, eventBus, genDoc)
|
||||
proxyApp := proxy.New(client, logger, proxy.NopMetrics())
|
||||
require.NoError(t, proxyApp.Start(ctx), "Error starting proxy app connections")
|
||||
|
||||
require.NoError(t, handshaker.Handshake(ctx, proxyApp), "error on abci handshake")
|
||||
|
||||
+82
-45
@@ -20,6 +20,7 @@ import (
|
||||
cstypes "github.com/tendermint/tendermint/internal/consensus/types"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
"github.com/tendermint/tendermint/internal/jsontypes"
|
||||
"github.com/tendermint/tendermint/internal/libs/autofile"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
tmevents "github.com/tendermint/tendermint/libs/events"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -121,6 +122,9 @@ type State struct {
|
||||
// store blocks and commits
|
||||
blockStore sm.BlockStore
|
||||
|
||||
stateStore sm.Store
|
||||
initialStatePopulated bool
|
||||
|
||||
// create and execute blocks
|
||||
blockExec *sm.BlockExecutor
|
||||
|
||||
@@ -189,18 +193,21 @@ func NewState(
|
||||
ctx context.Context,
|
||||
logger log.Logger,
|
||||
cfg *config.ConsensusConfig,
|
||||
state sm.State,
|
||||
store sm.Store,
|
||||
blockExec *sm.BlockExecutor,
|
||||
blockStore sm.BlockStore,
|
||||
txNotifier txNotifier,
|
||||
evpool evidencePool,
|
||||
eventBus *eventbus.EventBus,
|
||||
options ...StateOption,
|
||||
) *State {
|
||||
) (*State, error) {
|
||||
cs := &State{
|
||||
eventBus: eventBus,
|
||||
logger: logger,
|
||||
config: cfg,
|
||||
blockExec: blockExec,
|
||||
blockStore: blockStore,
|
||||
stateStore: store,
|
||||
txNotifier: txNotifier,
|
||||
peerMsgQueue: make(chan msgInfo, msgQueueSize),
|
||||
internalMsgQueue: make(chan msgInfo, msgQueueSize),
|
||||
@@ -220,6 +227,31 @@ func NewState(
|
||||
cs.doPrevote = cs.defaultDoPrevote
|
||||
cs.setProposal = cs.defaultSetProposal
|
||||
|
||||
if err := cs.updateStateFromStore(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// NOTE: we do not call scheduleRound0 yet, we do that upon Start()
|
||||
cs.BaseService = *service.NewBaseService(logger, "State", cs)
|
||||
for _, option := range options {
|
||||
option(cs)
|
||||
}
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (cs *State) updateStateFromStore(ctx context.Context) error {
|
||||
if cs.initialStatePopulated {
|
||||
return nil
|
||||
}
|
||||
state, err := cs.stateStore.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading state: %w", err)
|
||||
}
|
||||
if state.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We have no votes, so reconstruct LastCommit from SeenCommit.
|
||||
if state.LastBlockHeight > 0 {
|
||||
cs.reconstructLastCommit(state)
|
||||
@@ -227,20 +259,8 @@ func NewState(
|
||||
|
||||
cs.updateToState(ctx, state)
|
||||
|
||||
// NOTE: we do not call scheduleRound0 yet, we do that upon Start()
|
||||
|
||||
cs.BaseService = *service.NewBaseService(logger, "State", cs)
|
||||
for _, option := range options {
|
||||
option(cs)
|
||||
}
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
// SetEventBus sets event bus.
|
||||
func (cs *State) SetEventBus(b *eventbus.EventBus) {
|
||||
cs.eventBus = b
|
||||
cs.blockExec.SetEventBus(b)
|
||||
cs.initialStatePopulated = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// StateMetrics sets the metrics.
|
||||
@@ -365,6 +385,10 @@ func (cs *State) LoadCommit(height int64) *types.Commit {
|
||||
// OnStart loads the latest state via the WAL, and starts the timeout and
|
||||
// receive routines.
|
||||
func (cs *State) OnStart(ctx context.Context) error {
|
||||
if err := cs.updateStateFromStore(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We may set the WAL in testing before calling Start, so only OpenWAL if its
|
||||
// still the nilWAL.
|
||||
if _, ok := cs.wal.(nilWAL); ok {
|
||||
@@ -846,15 +870,27 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
cs.logger.Error("CONSENSUS FAILURE!!!", "err", r, "stack", string(debug.Stack()))
|
||||
// stop gracefully
|
||||
//
|
||||
// NOTE: We most probably shouldn't be running any further when there is
|
||||
// some unexpected panic. Some unknown error happened, and so we don't
|
||||
// know if that will result in the validator signing an invalid thing. It
|
||||
// might be worthwhile to explore a mechanism for manual resuming via
|
||||
// some console or secure RPC system, but for now, halting the chain upon
|
||||
// unexpected consensus bugs sounds like the better option.
|
||||
|
||||
// Make a best-effort attempt to close the WAL, but otherwise do not
|
||||
// attempt to gracefully terminate. Once consensus has irrecoverably
|
||||
// failed, any additional progress we permit the node to make may
|
||||
// complicate diagnosing and recovering from the failure.
|
||||
onExit(cs)
|
||||
|
||||
// Re-panic to ensure the node terminates.
|
||||
//
|
||||
// TODO(creachadair): In ordinary operation, the WAL autofile should
|
||||
// never be closed. This only happens during shutdown and production
|
||||
// nodes usually halt by panicking. Many existing tests, however,
|
||||
// assume a clean shutdown is possible. Prior to #8111, we were
|
||||
// swallowing the panic in receiveRoutine, making that appear to
|
||||
// work. Filtering this specific error is slightly risky, but should
|
||||
// affect only unit tests. In any case, not re-panicking here only
|
||||
// preserves the pre-existing behavior for this one error type.
|
||||
if err, ok := r.(error); ok && errors.Is(err, autofile.ErrAutoFileClosed) {
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -867,14 +903,11 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) {
|
||||
}
|
||||
}
|
||||
|
||||
rs := cs.RoundState
|
||||
var mi msgInfo
|
||||
|
||||
select {
|
||||
case <-cs.txNotifier.TxsAvailable():
|
||||
cs.handleTxsAvailable(ctx)
|
||||
|
||||
case mi = <-cs.peerMsgQueue:
|
||||
case mi := <-cs.peerMsgQueue:
|
||||
if err := cs.wal.Write(mi); err != nil {
|
||||
cs.logger.Error("failed writing to WAL", "err", err)
|
||||
}
|
||||
@@ -883,11 +916,11 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) {
|
||||
// may generate internal events (votes, complete proposals, 2/3 majorities)
|
||||
cs.handleMsg(ctx, mi)
|
||||
|
||||
case mi = <-cs.internalMsgQueue:
|
||||
case mi := <-cs.internalMsgQueue:
|
||||
err := cs.wal.WriteSync(mi) // NOTE: fsync
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf(
|
||||
"failed to write %v msg to consensus WAL due to %v; check your file system and restart the node",
|
||||
panic(fmt.Errorf(
|
||||
"failed to write %v msg to consensus WAL due to %w; check your file system and restart the node",
|
||||
mi, err,
|
||||
))
|
||||
}
|
||||
@@ -902,7 +935,7 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) {
|
||||
|
||||
// if the timeout is relevant to the rs
|
||||
// go to the next step
|
||||
cs.handleTimeout(ctx, ti, rs)
|
||||
cs.handleTimeout(ctx, ti, cs.RoundState)
|
||||
|
||||
case <-ctx.Done():
|
||||
onExit(cs)
|
||||
@@ -964,13 +997,11 @@ func (cs *State) handleMsg(ctx context.Context, mi msgInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
// if err == ErrAddingVote {
|
||||
// TODO: punish peer
|
||||
// We probably don't want to stop the peer here. The vote does not
|
||||
// necessarily comes from a malicious peer but can be just broadcasted by
|
||||
// a typical peer.
|
||||
// https://github.com/tendermint/tendermint/issues/1281
|
||||
// }
|
||||
|
||||
// NOTE: the vote is broadcast to peers by the reactor listening
|
||||
// for vote events
|
||||
@@ -1269,8 +1300,16 @@ func (cs *State) defaultDecideProposal(ctx context.Context, height int64, round
|
||||
} else {
|
||||
// Create a new proposal block from state/txs from the mempool.
|
||||
var err error
|
||||
block, blockParts, err = cs.createProposalBlock(ctx)
|
||||
if block == nil || err != nil {
|
||||
block, err = cs.createProposalBlock(ctx)
|
||||
if err != nil {
|
||||
cs.logger.Error("unable to create proposal block", "error", err)
|
||||
return
|
||||
} else if block == nil {
|
||||
return
|
||||
}
|
||||
blockParts, err = block.MakePartSet(types.BlockPartSizeBytes)
|
||||
if err != nil {
|
||||
cs.logger.Error("unable to create proposal block part set", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1329,13 +1368,12 @@ func (cs *State) isProposalComplete() bool {
|
||||
//
|
||||
// NOTE: keep it side-effect free for clarity.
|
||||
// CONTRACT: cs.privValidator is not nil.
|
||||
func (cs *State) createProposalBlock(ctx context.Context) (block *types.Block, blockParts *types.PartSet, err error) {
|
||||
func (cs *State) createProposalBlock(ctx context.Context) (*types.Block, error) {
|
||||
if cs.privValidator == nil {
|
||||
return nil, nil, errors.New("entered createProposalBlock with privValidator being nil")
|
||||
return nil, errors.New("entered createProposalBlock with privValidator being nil")
|
||||
}
|
||||
|
||||
var commit *types.Commit
|
||||
var votes []*types.Vote
|
||||
switch {
|
||||
case cs.Height == cs.state.InitialHeight:
|
||||
// We're creating a proposal for the first block.
|
||||
@@ -1345,23 +1383,22 @@ func (cs *State) createProposalBlock(ctx context.Context) (block *types.Block, b
|
||||
case cs.LastCommit.HasTwoThirdsMajority():
|
||||
// Make the commit from LastCommit
|
||||
commit = cs.LastCommit.MakeCommit()
|
||||
votes = cs.LastCommit.GetVotes()
|
||||
|
||||
default: // This shouldn't happen.
|
||||
cs.logger.Error("propose step; cannot propose anything without commit for the previous block")
|
||||
return
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if cs.privValidatorPubKey == nil {
|
||||
// If this node is a validator & proposer in the current round, it will
|
||||
// miss the opportunity to create a block.
|
||||
cs.logger.Error("propose step; empty priv validator public key", "err", errPubKeyIsNotSet)
|
||||
return
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
proposerAddr := cs.privValidatorPubKey.Address()
|
||||
|
||||
return cs.blockExec.CreateProposalBlock(ctx, cs.Height, cs.state, commit, proposerAddr, votes)
|
||||
return cs.blockExec.CreateProposalBlock(ctx, cs.Height, cs.state, commit, proposerAddr, cs.LastCommit.GetVotes())
|
||||
}
|
||||
|
||||
// Enter: `timeoutPropose` after entering Propose.
|
||||
@@ -1880,8 +1917,8 @@ func (cs *State) finalizeCommit(ctx context.Context, height int64) {
|
||||
// restart).
|
||||
endMsg := EndHeightMessage{height}
|
||||
if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync
|
||||
panic(fmt.Sprintf(
|
||||
"failed to write %v msg to consensus WAL due to %v; check your file system and restart the node",
|
||||
panic(fmt.Errorf(
|
||||
"failed to write %v msg to consensus WAL due to %w; check your file system and restart the node",
|
||||
endMsg, err,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ func TestStateBadProposal(t *testing.T) {
|
||||
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
voteCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryVote)
|
||||
|
||||
propBlock, _, err := cs1.createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
propBlock, err := cs1.createProposalBlock(ctx) // changeProposer(t, cs1, vs2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// make the second validator the proposer by incrementing round
|
||||
@@ -282,7 +282,7 @@ func TestStateOversizedBlock(t *testing.T) {
|
||||
timeoutProposeCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
voteCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryVote)
|
||||
|
||||
propBlock, _, err := cs1.createProposalBlock(ctx)
|
||||
propBlock, err := cs1.createProposalBlock(ctx)
|
||||
require.NoError(t, err)
|
||||
propBlock.Data.Txs = []types.Tx{tmrand.Bytes(2001)}
|
||||
propBlock.Header.DataHash = propBlock.Data.Hash()
|
||||
@@ -1965,6 +1965,79 @@ func TestProcessProposalAccept(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalizeBlockCalled(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
voteNil bool
|
||||
expectCalled bool
|
||||
}{
|
||||
{
|
||||
name: "finalze block called when block committed",
|
||||
voteNil: false,
|
||||
expectCalled: true,
|
||||
},
|
||||
{
|
||||
name: "not called when block not committed",
|
||||
voteNil: true,
|
||||
expectCalled: false,
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
config := configSetup(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
m := abcimocks.NewBaseMock()
|
||||
m.On("ProcessProposal", mock.Anything).Return(abcitypes.ResponseProcessProposal{Accept: true})
|
||||
m.On("VerifyVoteExtension", mock.Anything).Return(abcitypes.ResponseVerifyVoteExtension{
|
||||
Result: abcitypes.ResponseVerifyVoteExtension_ACCEPT,
|
||||
})
|
||||
m.On("FinalizeBlock", mock.Anything).Return(abcitypes.ResponseFinalizeBlock{}).Maybe()
|
||||
cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m})
|
||||
height, round := cs1.Height, cs1.Round
|
||||
|
||||
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound)
|
||||
pv1, err := cs1.privValidator.GetPubKey(ctx)
|
||||
require.NoError(t, err)
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(ctx, t, cs1, addr)
|
||||
|
||||
startTestRound(ctx, cs1, cs1.Height, round)
|
||||
ensureNewRound(t, newRoundCh, height, round)
|
||||
ensureNewProposal(t, proposalCh, height, round)
|
||||
rs := cs1.GetRoundState()
|
||||
|
||||
blockID := types.BlockID{}
|
||||
nextRound := round + 1
|
||||
nextHeight := height
|
||||
if !testCase.voteNil {
|
||||
nextRound = 0
|
||||
nextHeight = height + 1
|
||||
blockID = types.BlockID{
|
||||
Hash: rs.ProposalBlock.Hash(),
|
||||
PartSetHeader: rs.ProposalBlockParts.Header(),
|
||||
}
|
||||
}
|
||||
|
||||
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[1:]...)
|
||||
ensurePrevoteMatch(t, voteCh, height, round, rs.ProposalBlock.Hash())
|
||||
|
||||
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vss[1:]...)
|
||||
ensurePrecommit(t, voteCh, height, round)
|
||||
|
||||
ensureNewRound(t, newRoundCh, nextHeight, nextRound)
|
||||
m.AssertExpectations(t)
|
||||
|
||||
if !testCase.expectCalled {
|
||||
m.AssertNotCalled(t, "FinalizeBlock", mock.Anything)
|
||||
} else {
|
||||
m.AssertCalled(t, "FinalizeBlock", mock.Anything)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 4 vals, 3 Nil Precommits at P0
|
||||
// What we want:
|
||||
// P0 waits for timeoutPrecommit before starting next round
|
||||
@@ -2559,7 +2632,7 @@ func TestStateTimestamp_ProposalNotMatch(t *testing.T) {
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(ctx, t, cs1, addr)
|
||||
|
||||
propBlock, _, err := cs1.createProposalBlock(ctx)
|
||||
propBlock, err := cs1.createProposalBlock(ctx)
|
||||
require.NoError(t, err)
|
||||
round++
|
||||
incrementRound(vss[1:]...)
|
||||
@@ -2607,7 +2680,7 @@ func TestStateTimestamp_ProposalMatch(t *testing.T) {
|
||||
addr := pv1.Address()
|
||||
voteCh := subscribeToVoter(ctx, t, cs1, addr)
|
||||
|
||||
propBlock, _, err := cs1.createProposalBlock(ctx)
|
||||
propBlock, err := cs1.createProposalBlock(ctx)
|
||||
require.NoError(t, err)
|
||||
round++
|
||||
incrementRound(vss[1:]...)
|
||||
|
||||
@@ -30,8 +30,10 @@ import (
|
||||
// stripped down version of node (proxy app, event bus, consensus state) with a
|
||||
// persistent kvstore application and special consensus wal instance
|
||||
// (byteBufferWAL) and waits until numBlocks are created.
|
||||
// If the node fails to produce given numBlocks, it returns an error.
|
||||
func WALGenerateNBlocks(ctx context.Context, t *testing.T, logger log.Logger, wr io.Writer, numBlocks int) (err error) {
|
||||
// If the node fails to produce given numBlocks, it fails the test.
|
||||
func WALGenerateNBlocks(ctx context.Context, t *testing.T, logger log.Logger, wr io.Writer, numBlocks int) {
|
||||
t.Helper()
|
||||
|
||||
cfg := getConfig(t)
|
||||
|
||||
app := kvstore.NewPersistentKVStoreApplication(logger, filepath.Join(cfg.DBDir(), "wal_generator"))
|
||||
@@ -46,41 +48,46 @@ func WALGenerateNBlocks(ctx context.Context, t *testing.T, logger log.Logger, wr
|
||||
privValidatorStateFile := cfg.PrivValidator.StateFile()
|
||||
privValidator, err := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile)
|
||||
if err != nil {
|
||||
return err
|
||||
t.Fatal(err)
|
||||
}
|
||||
genDoc, err := types.GenesisDocFromFile(cfg.GenesisFile())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read genesis file: %w", err)
|
||||
t.Fatal(fmt.Errorf("failed to read genesis file: %w", err))
|
||||
}
|
||||
blockStoreDB := dbm.NewMemDB()
|
||||
stateDB := blockStoreDB
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
state, err := sm.MakeGenesisState(genDoc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make genesis state: %w", err)
|
||||
t.Fatal(fmt.Errorf("failed to make genesis state: %w", err))
|
||||
}
|
||||
state.Version.Consensus.App = kvstore.ProtocolVersion
|
||||
if err = stateStore.Save(state); err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blockStore := store.NewBlockStore(blockStoreDB)
|
||||
|
||||
proxyApp := proxy.NewAppConns(abciclient.NewLocalCreator(app), logger.With("module", "proxy"), proxy.NopMetrics())
|
||||
proxyLogger := logger.With("module", "proxy")
|
||||
proxyApp := proxy.New(abciclient.NewLocalClient(logger, app), proxyLogger, proxy.NopMetrics())
|
||||
if err := proxyApp.Start(ctx); err != nil {
|
||||
return fmt.Errorf("failed to start proxy app connections: %w", err)
|
||||
t.Fatal(fmt.Errorf("failed to start proxy app connections: %w", err))
|
||||
}
|
||||
t.Cleanup(proxyApp.Wait)
|
||||
|
||||
eventBus := eventbus.NewDefault(logger.With("module", "events"))
|
||||
if err := eventBus.Start(ctx); err != nil {
|
||||
return fmt.Errorf("failed to start event bus: %w", err)
|
||||
t.Fatal(fmt.Errorf("failed to start event bus: %w", err))
|
||||
}
|
||||
t.Cleanup(func() { eventBus.Stop(); eventBus.Wait() })
|
||||
|
||||
mempool := emptyMempool{}
|
||||
evpool := sm.EmptyEvidencePool{}
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool, blockStore)
|
||||
consensusState := NewState(ctx, logger, cfg.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool)
|
||||
consensusState.SetEventBus(eventBus)
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp, mempool, evpool, blockStore, eventBus)
|
||||
consensusState, err := NewState(ctx, logger, cfg.Consensus, stateStore, blockExec, blockStore, mempool, evpool, eventBus)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if privValidator != nil && privValidator != (*privval.FilePV)(nil) {
|
||||
consensusState.SetPrivValidator(ctx, privValidator)
|
||||
}
|
||||
@@ -91,22 +98,24 @@ func WALGenerateNBlocks(ctx context.Context, t *testing.T, logger log.Logger, wr
|
||||
wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten)
|
||||
// see wal.go#103
|
||||
if err := wal.Write(EndHeightMessage{0}); err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
consensusState.wal = wal
|
||||
|
||||
if err := consensusState.Start(ctx); err != nil {
|
||||
return fmt.Errorf("failed to start consensus state: %w", err)
|
||||
t.Fatal(fmt.Errorf("failed to start consensus state: %w", err))
|
||||
}
|
||||
t.Cleanup(consensusState.Wait)
|
||||
|
||||
defer consensusState.Stop()
|
||||
timer := time.NewTimer(time.Minute)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case <-numBlocksWritten:
|
||||
consensusState.Stop()
|
||||
return nil
|
||||
case <-time.After(1 * time.Minute):
|
||||
consensusState.Stop()
|
||||
return fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks)
|
||||
case <-timer.C:
|
||||
t.Fatal(fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,9 +124,7 @@ func WALWithNBlocks(ctx context.Context, t *testing.T, logger log.Logger, numBlo
|
||||
var b bytes.Buffer
|
||||
wr := bufio.NewWriter(&b)
|
||||
|
||||
if err := WALGenerateNBlocks(ctx, t, logger, wr, numBlocks); err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
WALGenerateNBlocks(ctx, t, logger, wr, numBlocks)
|
||||
|
||||
wr.Flush()
|
||||
return b.Bytes(), nil
|
||||
|
||||
@@ -3,6 +3,7 @@ package consensus
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"testing"
|
||||
@@ -41,13 +42,12 @@ func TestWALTruncate(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
err = wal.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(wal.Wait)
|
||||
t.Cleanup(func() { wal.Stop(); wal.Group().Stop(); wal.Group().Wait(); wal.Wait() })
|
||||
|
||||
// 60 block's size nearly 70K, greater than group's headBuf size(4096 * 10),
|
||||
// when headBuf is full, truncate content will Flush to the file. at this
|
||||
// time, RotateFile is called, truncate content exist in each file.
|
||||
err = WALGenerateNBlocks(ctx, t, logger, wal.Group(), 60)
|
||||
require.NoError(t, err)
|
||||
WALGenerateNBlocks(ctx, t, logger, wal.Group(), 60)
|
||||
|
||||
// put the leakcheck here so it runs after other cleanup
|
||||
// functions.
|
||||
@@ -112,7 +112,7 @@ func TestWALWrite(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
err = wal.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(wal.Wait)
|
||||
t.Cleanup(func() { wal.Stop(); wal.Group().Stop(); wal.Group().Wait(); wal.Wait() })
|
||||
|
||||
// 1) Write returns an error if msg is too big
|
||||
msg := &BlockPartMessage{
|
||||
@@ -151,7 +151,6 @@ func TestWALSearchForEndHeight(t *testing.T) {
|
||||
|
||||
wal, err := NewWAL(ctx, logger, walFile)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { wal.Stop(); wal.Wait() })
|
||||
|
||||
h := int64(3)
|
||||
gr, found, err := wal.SearchForEndHeight(h, &WALSearchOptions{})
|
||||
@@ -176,24 +175,24 @@ func TestWALPeriodicSync(t *testing.T) {
|
||||
|
||||
walDir := t.TempDir()
|
||||
walFile := filepath.Join(walDir, "wal")
|
||||
wal, err := NewWAL(ctx, log.TestingLogger(), walFile, autofile.GroupCheckDuration(1*time.Millisecond))
|
||||
defer os.RemoveAll(walFile)
|
||||
|
||||
wal, err := NewWAL(ctx, log.TestingLogger(), walFile, autofile.GroupCheckDuration(250*time.Millisecond))
|
||||
require.NoError(t, err)
|
||||
|
||||
wal.SetFlushInterval(walTestFlushInterval)
|
||||
logger := log.NewNopLogger()
|
||||
|
||||
// Generate some data
|
||||
err = WALGenerateNBlocks(ctx, t, logger, wal.Group(), 5)
|
||||
require.NoError(t, err)
|
||||
WALGenerateNBlocks(ctx, t, logger, wal.Group(), 5)
|
||||
|
||||
// We should have data in the buffer now
|
||||
assert.NotZero(t, wal.Group().Buffered())
|
||||
|
||||
require.NoError(t, wal.Start(ctx))
|
||||
t.Cleanup(func() { wal.Stop(); wal.Wait() })
|
||||
t.Cleanup(func() { wal.Stop(); wal.Group().Stop(); wal.Group().Wait(); wal.Wait() })
|
||||
|
||||
time.Sleep(walTestFlushInterval + (10 * time.Millisecond))
|
||||
time.Sleep(walTestFlushInterval + (20 * time.Millisecond))
|
||||
|
||||
// The data should have been flushed by the periodic sync
|
||||
assert.Zero(t, wal.Group().Buffered())
|
||||
|
||||
@@ -50,13 +50,6 @@ func (b *EventBus) NumClientSubscriptions(clientID string) int {
|
||||
return b.pubsub.NumClientSubscriptions(clientID)
|
||||
}
|
||||
|
||||
// Deprecated: Use SubscribeWithArgs instead.
|
||||
func (b *EventBus) Subscribe(ctx context.Context,
|
||||
clientID string, query *tmquery.Query, capacities ...int) (Subscription, error) {
|
||||
|
||||
return b.pubsub.Subscribe(ctx, clientID, query, capacities...)
|
||||
}
|
||||
|
||||
func (b *EventBus) SubscribeWithArgs(ctx context.Context, args tmpubsub.SubscribeArgs) (Subscription, error) {
|
||||
return b.pubsub.SubscribeWithArgs(ctx, args)
|
||||
}
|
||||
@@ -201,28 +194,3 @@ func (b *EventBus) PublishEventValidatorSetUpdates(ctx context.Context, data typ
|
||||
func (b *EventBus) PublishEventEvidenceValidated(ctx context.Context, evidence types.EventDataEvidenceValidated) error {
|
||||
return b.Publish(ctx, types.EventEvidenceValidatedValue, evidence)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// NopEventBus implements a types.BlockEventPublisher that discards all events.
|
||||
type NopEventBus struct{}
|
||||
|
||||
func (NopEventBus) PublishEventNewBlock(context.Context, types.EventDataNewBlock) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NopEventBus) PublishEventNewBlockHeader(context.Context, types.EventDataNewBlockHeader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NopEventBus) PublishEventNewEvidence(context.Context, types.EventDataNewEvidence) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NopEventBus) PublishEventTx(context.Context, types.EventDataTx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NopEventBus) PublishEventValidatorSetUpdates(context.Context, types.EventDataValidatorSetUpdates) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestEventBusPublishEventTx(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
tx := types.Tx("foo")
|
||||
result := abci.ResponseDeliverTx{
|
||||
result := abci.ExecTxResult{
|
||||
Data: []byte("bar"),
|
||||
Events: []abci.Event{
|
||||
{Type: "testType", Attributes: []abci.EventAttribute{{Key: "baz", Value: "1"}}},
|
||||
@@ -134,7 +134,7 @@ func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
tx := types.Tx("foo")
|
||||
result := abci.ResponseDeliverTx{
|
||||
result := abci.ExecTxResult{
|
||||
Data: []byte("bar"),
|
||||
Events: []abci.Event{
|
||||
{
|
||||
|
||||
+33
-35
@@ -36,14 +36,14 @@ type Pool struct {
|
||||
evidenceList *clist.CList // concurrent linked-list of evidence
|
||||
evidenceSize uint32 // amount of pending evidence
|
||||
|
||||
// needed to load validators to verify evidence
|
||||
stateDB sm.Store
|
||||
// needed to load headers and commits to verify evidence
|
||||
blockStore BlockStore
|
||||
stateDB sm.Store
|
||||
|
||||
mtx sync.Mutex
|
||||
// latest state
|
||||
state sm.State
|
||||
state sm.State
|
||||
isStarted bool
|
||||
// evidence from consensus is buffered to this slice, awaiting until the next height
|
||||
// before being flushed to the pool. This prevents broadcasting and proposing of
|
||||
// evidence before the height with which the evidence happened is finished.
|
||||
@@ -60,46 +60,19 @@ type Pool struct {
|
||||
Metrics *Metrics
|
||||
}
|
||||
|
||||
func (evpool *Pool) SetEventBus(e *eventbus.EventBus) {
|
||||
evpool.eventBus = e
|
||||
}
|
||||
|
||||
// NewPool creates an evidence pool. If using an existing evidence store,
|
||||
// it will add all pending evidence to the concurrent list.
|
||||
func NewPool(logger log.Logger, evidenceDB dbm.DB, stateDB sm.Store, blockStore BlockStore, metrics *Metrics) (*Pool, error) {
|
||||
state, err := stateDB.Load()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load state: %w", err)
|
||||
}
|
||||
|
||||
pool := &Pool{
|
||||
stateDB: stateDB,
|
||||
func NewPool(logger log.Logger, evidenceDB dbm.DB, stateStore sm.Store, blockStore BlockStore, metrics *Metrics, eventBus *eventbus.EventBus) *Pool {
|
||||
return &Pool{
|
||||
blockStore: blockStore,
|
||||
state: state,
|
||||
stateDB: stateStore,
|
||||
logger: logger,
|
||||
evidenceStore: evidenceDB,
|
||||
evidenceList: clist.New(),
|
||||
consensusBuffer: make([]duplicateVoteSet, 0),
|
||||
Metrics: metrics,
|
||||
eventBus: eventBus,
|
||||
}
|
||||
|
||||
// If pending evidence already in db, in event of prior failure, then check
|
||||
// for expiration, update the size and load it back to the evidenceList.
|
||||
pool.pruningHeight, pool.pruningTime = pool.removeExpiredPendingEvidence()
|
||||
evList, _, err := pool.listEvidence(prefixPending, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&pool.evidenceSize, uint32(len(evList)))
|
||||
pool.Metrics.NumEvidence.Set(float64(pool.evidenceSize))
|
||||
|
||||
for _, ev := range evList {
|
||||
pool.evidenceList.PushBack(ev)
|
||||
}
|
||||
pool.eventBus = nil
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// PendingEvidence is used primarily as part of block proposal and returns up to
|
||||
@@ -277,6 +250,31 @@ func (evpool *Pool) State() sm.State {
|
||||
return evpool.state
|
||||
}
|
||||
|
||||
func (evpool *Pool) Start(state sm.State) error {
|
||||
if evpool.isStarted {
|
||||
return errors.New("pool is already running")
|
||||
}
|
||||
|
||||
evpool.state = state
|
||||
|
||||
// If pending evidence already in db, in event of prior failure, then check
|
||||
// for expiration, update the size and load it back to the evidenceList.
|
||||
evpool.pruningHeight, evpool.pruningTime = evpool.removeExpiredPendingEvidence()
|
||||
evList, _, err := evpool.listEvidence(prefixPending, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&evpool.evidenceSize, uint32(len(evList)))
|
||||
evpool.Metrics.NumEvidence.Set(float64(evpool.evidenceSize))
|
||||
|
||||
for _, ev := range evList {
|
||||
evpool.evidenceList.PushBack(ev)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (evpool *Pool) Close() error {
|
||||
return evpool.evidenceStore.Close()
|
||||
}
|
||||
@@ -449,6 +447,7 @@ func (evpool *Pool) listEvidence(prefixKey int64, maxBytes int64) ([]types.Evide
|
||||
}
|
||||
|
||||
func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) {
|
||||
|
||||
batch := evpool.evidenceStore.NewBatch()
|
||||
defer batch.Close()
|
||||
|
||||
@@ -473,7 +472,6 @@ func (evpool *Pool) removeExpiredPendingEvidence() (int64, time.Time) {
|
||||
|
||||
// remove evidence from the clist
|
||||
evpool.removeEvidenceFromList(blockEvidenceMap)
|
||||
|
||||
// update the evidence size
|
||||
atomic.AddUint32(&evpool.evidenceSize, ^uint32(len(blockEvidenceMap)-1))
|
||||
|
||||
|
||||
@@ -34,6 +34,18 @@ var (
|
||||
defaultEvidenceMaxBytes int64 = 1000
|
||||
)
|
||||
|
||||
func startPool(t *testing.T, pool *evidence.Pool, store sm.Store) {
|
||||
t.Helper()
|
||||
state, err := store.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("cannot load state: %v", err)
|
||||
}
|
||||
if err := pool.Start(state); err != nil {
|
||||
t.Fatalf("cannot start state pool: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEvidencePoolBasic(t *testing.T) {
|
||||
var (
|
||||
height = int64(1)
|
||||
@@ -51,9 +63,13 @@ func TestEvidencePoolBasic(t *testing.T) {
|
||||
stateStore.On("LoadValidators", mock.AnythingOfType("int64")).Return(valSet, nil)
|
||||
stateStore.On("Load").Return(createState(height+1, valSet), nil)
|
||||
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
logger := log.NewNopLogger()
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
pool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
startPool(t, pool, stateStore)
|
||||
|
||||
// evidence not seen yet:
|
||||
evs, size := pool.PendingEvidence(defaultEvidenceMaxBytes)
|
||||
require.Equal(t, 0, len(evs))
|
||||
@@ -115,10 +131,12 @@ func TestAddExpiredEvidence(t *testing.T) {
|
||||
return &types.BlockMeta{Header: types.Header{Time: expiredEvidenceTime}}
|
||||
})
|
||||
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
logger := log.NewNopLogger()
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
startPool(t, pool, stateStore)
|
||||
|
||||
testCases := []struct {
|
||||
evHeight int64
|
||||
@@ -159,9 +177,7 @@ func TestReportConflictingVotes(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
pool, pv := defaultTestPool(ctx, t, height)
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool, pv, _ := defaultTestPool(ctx, t, height)
|
||||
|
||||
val := types.NewValidator(pv.PrivKey.PubKey(), 10)
|
||||
|
||||
@@ -201,9 +217,7 @@ func TestEvidencePoolUpdate(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
pool, val := defaultTestPool(ctx, t, height)
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool, val, _ := defaultTestPool(ctx, t, height)
|
||||
|
||||
state := pool.State()
|
||||
|
||||
@@ -273,9 +287,7 @@ func TestVerifyPendingEvidencePasses(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
pool, val := defaultTestPool(ctx, t, height)
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool, val, _ := defaultTestPool(ctx, t, height)
|
||||
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(
|
||||
ctx,
|
||||
@@ -295,9 +307,7 @@ func TestVerifyDuplicatedEvidenceFails(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
pool, val := defaultTestPool(ctx, t, height)
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool, val, _ := defaultTestPool(ctx, t, height)
|
||||
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(
|
||||
ctx,
|
||||
@@ -321,7 +331,7 @@ func TestEventOnEvidenceValidated(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
pool, val := defaultTestPool(ctx, t, height)
|
||||
pool, val, eventBus := defaultTestPool(ctx, t, height)
|
||||
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(
|
||||
ctx,
|
||||
@@ -332,11 +342,6 @@ func TestEventOnEvidenceValidated(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
eventBus := eventbus.NewDefault(log.TestingLogger())
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
pool.SetEventBus(eventBus)
|
||||
|
||||
const query = `tm.event='EvidenceValidated'`
|
||||
evSub, err := eventBus.SubscribeWithArgs(ctx, tmpubsub.SubscribeArgs{
|
||||
ClientID: "test",
|
||||
@@ -348,6 +353,9 @@ func TestEventOnEvidenceValidated(t *testing.T) {
|
||||
go func() {
|
||||
defer close(done)
|
||||
msg, err := evSub.Next(ctx)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
edt := msg.Data().(types.EventDataEvidenceValidated)
|
||||
@@ -394,14 +402,15 @@ func TestLightClientAttackEvidenceLifecycle(t *testing.T) {
|
||||
blockStore.On("LoadBlockCommit", height).Return(trusted.Commit)
|
||||
blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit)
|
||||
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
logger := log.NewNopLogger()
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
|
||||
hash := ev.Hash()
|
||||
|
||||
err = pool.AddEvidence(ctx, ev)
|
||||
err := pool.AddEvidence(ctx, ev)
|
||||
require.NoError(t, err)
|
||||
err = pool.AddEvidence(ctx, ev)
|
||||
require.NoError(t, err)
|
||||
@@ -449,11 +458,13 @@ func TestRecoverPendingEvidence(t *testing.T) {
|
||||
blockStore, err := initializeBlockStore(dbm.NewMemDB(), state, valAddress)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create previous pool and populate it
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
logger := log.NewNopLogger()
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
// create previous pool and populate it
|
||||
pool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
startPool(t, pool, stateStore)
|
||||
|
||||
goodEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(
|
||||
ctx,
|
||||
@@ -495,9 +506,8 @@ func TestRecoverPendingEvidence(t *testing.T) {
|
||||
},
|
||||
}, nil)
|
||||
|
||||
newPool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, newStateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
|
||||
newPool := evidence.NewPool(logger, evidenceDB, newStateStore, blockStore, evidence.NopMetrics(), nil)
|
||||
startPool(t, newPool, newStateStore)
|
||||
evList, _ := newPool.PendingEvidence(defaultEvidenceMaxBytes)
|
||||
require.Equal(t, 1, len(evList))
|
||||
|
||||
@@ -559,10 +569,7 @@ func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) (*store.Blo
|
||||
|
||||
for i := int64(1); i <= state.LastBlockHeight; i++ {
|
||||
lastCommit := makeCommit(i-1, valAddr)
|
||||
block, err := sf.MakeBlock(state, i, lastCommit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block := sf.MakeBlock(state, i, lastCommit)
|
||||
|
||||
block.Header.Time = defaultEvidenceTime.Add(time.Duration(i) * time.Minute)
|
||||
block.Header.Version = version.Consensus{Block: version.BlockProtocol, App: 1}
|
||||
@@ -590,7 +597,7 @@ func makeCommit(height int64, valAddr []byte) *types.Commit {
|
||||
return types.NewCommit(height, 0, types.BlockID{}, commitSigs)
|
||||
}
|
||||
|
||||
func defaultTestPool(ctx context.Context, t *testing.T, height int64) (*evidence.Pool, types.MockPV) {
|
||||
func defaultTestPool(ctx context.Context, t *testing.T, height int64) (*evidence.Pool, types.MockPV, *eventbus.EventBus) {
|
||||
t.Helper()
|
||||
val := types.NewMockPV()
|
||||
valAddress := val.PrivKey.PubKey().Address()
|
||||
@@ -601,10 +608,14 @@ func defaultTestPool(ctx context.Context, t *testing.T, height int64) (*evidence
|
||||
blockStore, err := initializeBlockStore(dbm.NewMemDB(), state, valAddress)
|
||||
require.NoError(t, err)
|
||||
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), evidenceDB, stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err, "test evidence pool could not be created")
|
||||
logger := log.NewNopLogger()
|
||||
|
||||
return pool, val
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
pool := evidence.NewPool(logger, evidenceDB, stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
startPool(t, pool, stateStore)
|
||||
return pool, val, eventBus
|
||||
}
|
||||
|
||||
func createState(height int64, valSet *types.ValidatorSet) sm.State {
|
||||
@@ -616,12 +627,3 @@ func createState(height int64, valSet *types.ValidatorSet) sm.State {
|
||||
ConsensusParams: *types.DefaultConsensusParams(),
|
||||
}
|
||||
}
|
||||
|
||||
func setupEventBus(ctx context.Context, evpool *evidence.Pool) error {
|
||||
eventBus := eventbus.NewDefault(log.TestingLogger())
|
||||
if err := eventBus.Start(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
evpool.SetEventBus(eventBus)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -82,13 +82,14 @@ func setup(ctx context.Context, t *testing.T, stateStores []sm.Store, chBuf uint
|
||||
}
|
||||
return nil
|
||||
})
|
||||
rts.pools[nodeID], err = evidence.NewPool(logger, evidenceDB, stateStores[idx], blockStore, evidence.NopMetrics())
|
||||
|
||||
require.NoError(t, err)
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
err = eventBus.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
rts.pools[nodeID].SetEventBus(eventBus)
|
||||
|
||||
rts.pools[nodeID] = evidence.NewPool(logger, evidenceDB, stateStores[idx], blockStore, evidence.NopMetrics(), eventBus)
|
||||
startPool(t, rts.pools[nodeID], stateStores[idx])
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
rts.peerChans[nodeID] = make(chan p2p.PeerUpdate)
|
||||
rts.peerUpdates[nodeID] = p2p.NewPeerUpdates(rts.peerChans[nodeID], 1)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
"github.com/tendermint/tendermint/internal/evidence"
|
||||
"github.com/tendermint/tendermint/internal/evidence/mocks"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
@@ -76,6 +77,7 @@ func TestVerify_LunaticAttackAgainstState(t *testing.T) {
|
||||
)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
logger := log.NewNopLogger()
|
||||
|
||||
attackTime := defaultEvidenceTime.Add(1 * time.Hour)
|
||||
// create valid lunatic evidence
|
||||
@@ -96,8 +98,7 @@ func TestVerify_LunaticAttackAgainstState(t *testing.T) {
|
||||
blockStore.On("LoadBlockMeta", height).Return(&types.BlockMeta{Header: *trusted.Header})
|
||||
blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit)
|
||||
blockStore.On("LoadBlockCommit", height).Return(trusted.Commit)
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
pool := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), nil)
|
||||
|
||||
evList := types.EvidenceList{ev}
|
||||
// check that the evidence pool correctly verifies the evidence
|
||||
@@ -111,32 +112,29 @@ func TestVerify_LunaticAttackAgainstState(t *testing.T) {
|
||||
// if we submit evidence only against a single byzantine validator when we see there are more validators then this
|
||||
// should return an error
|
||||
ev.ByzantineValidators = ev.ByzantineValidators[:1]
|
||||
t.Log(evList)
|
||||
assert.Error(t, pool.CheckEvidence(ctx, evList))
|
||||
// restore original byz vals
|
||||
ev.ByzantineValidators = ev.GetByzantineValidators(common.ValidatorSet, trusted.SignedHeader)
|
||||
|
||||
// duplicate evidence should be rejected
|
||||
evList = types.EvidenceList{ev, ev}
|
||||
pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
pool = evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), nil)
|
||||
assert.Error(t, pool.CheckEvidence(ctx, evList))
|
||||
|
||||
// If evidence is submitted with an altered timestamp it should return an error
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
ev.Timestamp = defaultEvidenceTime.Add(1 * time.Minute)
|
||||
pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
pool = evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
|
||||
err = pool.AddEvidence(ctx, ev)
|
||||
err := pool.AddEvidence(ctx, ev)
|
||||
assert.Error(t, err)
|
||||
ev.Timestamp = defaultEvidenceTime
|
||||
|
||||
// Evidence submitted with a different validator power should fail
|
||||
ev.TotalVotingPower = 1
|
||||
pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
pool = evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), nil)
|
||||
err = pool.AddEvidence(ctx, ev)
|
||||
assert.Error(t, err)
|
||||
ev.TotalVotingPower = common.ValidatorSet.TotalVotingPower()
|
||||
@@ -154,6 +152,9 @@ func TestVerify_ForwardLunaticAttack(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
|
||||
// create a forward lunatic attack
|
||||
ev, trusted, common := makeLunaticEvidence(ctx,
|
||||
t, attackHeight, commonHeight, totalVals, byzVals, totalVals-byzVals, defaultEvidenceTime, attackTime)
|
||||
@@ -179,10 +180,11 @@ func TestVerify_ForwardLunaticAttack(t *testing.T) {
|
||||
blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit)
|
||||
blockStore.On("LoadBlockCommit", nodeHeight).Return(trusted.Commit)
|
||||
blockStore.On("Height").Return(nodeHeight)
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
|
||||
// check that the evidence pool correctly verifies the evidence
|
||||
assert.NoError(t, pool.CheckEvidence(ctx, types.EvidenceList{ev}))
|
||||
@@ -199,8 +201,7 @@ func TestVerify_ForwardLunaticAttack(t *testing.T) {
|
||||
oldBlockStore.On("Height").Return(nodeHeight)
|
||||
require.Equal(t, defaultEvidenceTime, oldBlockStore.LoadBlockMeta(nodeHeight).Header.Time)
|
||||
|
||||
pool, err = evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, oldBlockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
pool = evidence.NewPool(logger, dbm.NewMemDB(), stateStore, oldBlockStore, evidence.NopMetrics(), nil)
|
||||
assert.Error(t, pool.CheckEvidence(ctx, types.EvidenceList{ev}))
|
||||
}
|
||||
|
||||
@@ -208,6 +209,8 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
|
||||
conflictingVals, conflictingPrivVals := factory.ValidatorSet(ctx, t, 5, 10)
|
||||
|
||||
conflictingHeader := factory.MakeHeader(t, &types.Header{
|
||||
@@ -289,10 +292,10 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
|
||||
blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader})
|
||||
blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit)
|
||||
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
|
||||
evList := types.EvidenceList{ev}
|
||||
err = pool.CheckEvidence(ctx, evList)
|
||||
@@ -305,6 +308,9 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
|
||||
func TestVerifyLightClientAttack_Amnesia(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
|
||||
var height int64 = 10
|
||||
conflictingVals, conflictingPrivVals := factory.ValidatorSet(ctx, t, 5, 10)
|
||||
|
||||
@@ -378,10 +384,10 @@ func TestVerifyLightClientAttack_Amnesia(t *testing.T) {
|
||||
blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader})
|
||||
blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit)
|
||||
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
|
||||
evList := types.EvidenceList{ev}
|
||||
err = pool.CheckEvidence(ctx, evList)
|
||||
@@ -401,6 +407,7 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
val := types.NewMockPV()
|
||||
val2 := types.NewMockPV()
|
||||
valSet := types.NewValidatorSet([]*types.Validator{val.ExtractIntoValidator(ctx, 1)})
|
||||
@@ -478,10 +485,11 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) {
|
||||
blockStore := &mocks.BlockStore{}
|
||||
blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}})
|
||||
|
||||
pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics())
|
||||
require.NoError(t, err)
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
require.NoError(t, setupEventBus(ctx, pool))
|
||||
pool := evidence.NewPool(logger, dbm.NewMemDB(), stateStore, blockStore, evidence.NopMetrics(), eventBus)
|
||||
startPool(t, pool, stateStore)
|
||||
|
||||
evList := types.EvidenceList{goodEv}
|
||||
err = pool.CheckEvidence(ctx, evList)
|
||||
|
||||
@@ -265,7 +265,7 @@ func TestBlockResults(t *testing.T) {
|
||||
// tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
|
||||
stateStoreMock.On("LoadABCIResponses", testHeight).Return(&state.ABCIResponses{
|
||||
FinalizeBlock: &abcitypes.ResponseFinalizeBlock{
|
||||
Txs: []*abcitypes.ResponseDeliverTx{
|
||||
TxResults: []*abcitypes.ExecTxResult{
|
||||
{
|
||||
GasUsed: testGasUsed,
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
// Create/Append to ./autofile_test
|
||||
af, err := OpenAutoFile("autofile_test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Stream of writes.
|
||||
@@ -32,7 +32,7 @@ for i := 0; i < 60; i++ {
|
||||
// Close the AutoFile
|
||||
err = af.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -41,9 +41,9 @@ const (
|
||||
autoFilePerms = os.FileMode(0600)
|
||||
)
|
||||
|
||||
// errAutoFileClosed is reported when operations attempt to use an autofile
|
||||
// ErrAutoFileClosed is reported when operations attempt to use an autofile
|
||||
// after it has been closed.
|
||||
var errAutoFileClosed = errors.New("autofile is closed")
|
||||
var ErrAutoFileClosed = errors.New("autofile is closed")
|
||||
|
||||
// AutoFile automatically closes and re-opens file for writing. The file is
|
||||
// automatically setup to close itself every 1s and upon receiving SIGHUP.
|
||||
@@ -155,7 +155,7 @@ func (af *AutoFile) Write(b []byte) (n int, err error) {
|
||||
af.mtx.Lock()
|
||||
defer af.mtx.Unlock()
|
||||
if af.closed {
|
||||
return 0, fmt.Errorf("write: %w", errAutoFileClosed)
|
||||
return 0, fmt.Errorf("write: %w", ErrAutoFileClosed)
|
||||
}
|
||||
|
||||
if af.file == nil {
|
||||
@@ -174,7 +174,7 @@ func (af *AutoFile) Write(b []byte) (n int, err error) {
|
||||
func (af *AutoFile) Sync() error {
|
||||
return af.withLock(func() error {
|
||||
if af.closed {
|
||||
return fmt.Errorf("sync: %w", errAutoFileClosed)
|
||||
return fmt.Errorf("sync: %w", ErrAutoFileClosed)
|
||||
} else if af.file == nil {
|
||||
return nil // nothing to sync
|
||||
}
|
||||
@@ -189,13 +189,7 @@ func (af *AutoFile) openFile() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// fileInfo, err := file.Stat()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if fileInfo.Mode() != autoFilePerms {
|
||||
// return errors.NewErrPermissionsChanged(file.Name(), fileInfo.Mode(), autoFilePerms)
|
||||
// }
|
||||
|
||||
af.file = file
|
||||
return nil
|
||||
}
|
||||
@@ -207,7 +201,7 @@ func (af *AutoFile) Size() (int64, error) {
|
||||
af.mtx.Lock()
|
||||
defer af.mtx.Unlock()
|
||||
if af.closed {
|
||||
return 0, fmt.Errorf("size: %w", errAutoFileClosed)
|
||||
return 0, fmt.Errorf("size: %w", ErrAutoFileClosed)
|
||||
}
|
||||
|
||||
if af.file == nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
stdlog "log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
@@ -19,19 +20,26 @@ const Version = "0.0.1"
|
||||
const readBufferSize = 1024 // 1KB at a time
|
||||
|
||||
// Parse command-line options
|
||||
func parseFlags() (headPath string, chopSize int64, limitSize int64, version bool) {
|
||||
func parseFlags() (headPath string, chopSize int64, limitSize int64, version bool, err error) {
|
||||
var flagSet = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||
var chopSizeStr, limitSizeStr string
|
||||
flagSet.StringVar(&headPath, "head", "logjack.out", "Destination (head) file.")
|
||||
flagSet.StringVar(&chopSizeStr, "chop", "100M", "Move file if greater than this")
|
||||
flagSet.StringVar(&limitSizeStr, "limit", "10G", "Only keep this much (for each specified file). Remove old files.")
|
||||
flagSet.BoolVar(&version, "version", false, "Version")
|
||||
if err := flagSet.Parse(os.Args[1:]); err != nil {
|
||||
fmt.Printf("err parsing flag: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
if err = flagSet.Parse(os.Args[1:]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
chopSize, err = parseByteSize(chopSizeStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
limitSize, err = parseByteSize(limitSizeStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
chopSize = parseBytesize(chopSizeStr)
|
||||
limitSize = parseBytesize(limitSizeStr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -41,22 +49,23 @@ func main() {
|
||||
defer func() { fmt.Println("logjack shutting down") }()
|
||||
|
||||
// Read options
|
||||
headPath, chopSize, limitSize, version := parseFlags()
|
||||
headPath, chopSize, limitSize, version, err := parseFlags()
|
||||
if err != nil {
|
||||
stdlog.Fatalf("problem parsing arguments: %q", err.Error())
|
||||
}
|
||||
|
||||
if version {
|
||||
fmt.Printf("logjack version %v\n", Version)
|
||||
return
|
||||
stdlog.Printf("logjack version %s", Version)
|
||||
}
|
||||
|
||||
// Open Group
|
||||
group, err := auto.OpenGroup(ctx, log.NewNopLogger(), headPath, auto.GroupHeadSizeLimit(chopSize), auto.GroupTotalSizeLimit(limitSize))
|
||||
if err != nil {
|
||||
fmt.Printf("logjack couldn't create output file %v\n", headPath)
|
||||
os.Exit(1)
|
||||
stdlog.Fatalf("logjack couldn't create output file %q", headPath)
|
||||
}
|
||||
|
||||
if err = group.Start(ctx); err != nil {
|
||||
fmt.Printf("logjack couldn't start with file %v\n", headPath)
|
||||
os.Exit(1)
|
||||
stdlog.Fatalf("logjack couldn't start with file %q", headPath)
|
||||
}
|
||||
|
||||
// Forever read from stdin and write to AutoFile.
|
||||
@@ -65,25 +74,21 @@ func main() {
|
||||
n, err := os.Stdin.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
fmt.Println("logjack errored:", err.Error())
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
stdlog.Fatalln("logjack errored:", err.Error())
|
||||
}
|
||||
_, err = group.Write(buf[:n])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "logjack failed write with error %v\n", headPath)
|
||||
os.Exit(1)
|
||||
stdlog.Fatalf("logjack failed write %q with error: %q", headPath, err.Error())
|
||||
}
|
||||
if err := group.FlushAndSync(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "logjack flushsync fail with error %v\n", headPath)
|
||||
os.Exit(1)
|
||||
stdlog.Fatalf("logjack flushsync %q fail with error: %q", headPath, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseBytesize(chopSize string) int64 {
|
||||
func parseByteSize(chopSize string) (int64, error) {
|
||||
// Handle suffix multiplier
|
||||
var multiplier int64 = 1
|
||||
if strings.HasSuffix(chopSize, "T") {
|
||||
@@ -106,8 +111,8 @@ func parseBytesize(chopSize string) int64 {
|
||||
// Parse the numeric part
|
||||
chopSizeInt, err := strconv.Atoi(chopSize)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int64(chopSizeInt) * multiplier
|
||||
return int64(chopSizeInt) * multiplier, nil
|
||||
}
|
||||
|
||||
@@ -274,6 +274,10 @@ func (g *Group) checkTotalSizeLimit(ctx context.Context) {
|
||||
g.mtx.Lock()
|
||||
defer g.mtx.Unlock()
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if g.totalSizeLimit == 0 {
|
||||
return
|
||||
}
|
||||
@@ -290,6 +294,11 @@ func (g *Group) checkTotalSizeLimit(ctx context.Context) {
|
||||
g.logger.Error("Group's head may grow without bound", "head", g.Head.Path)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pathToRemove := filePathForIndex(g.Head.Path, index, gInfo.MaxIndex)
|
||||
fInfo, err := os.Stat(pathToRemove)
|
||||
if err != nil {
|
||||
@@ -309,11 +318,16 @@ func (g *Group) checkTotalSizeLimit(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// rotateFile causes group to close the current head and assign it some index.
|
||||
// rotateFile causes group to close the current head and assign it
|
||||
// some index. Panics if it encounters an error.
|
||||
func (g *Group) rotateFile(ctx context.Context) {
|
||||
g.mtx.Lock()
|
||||
defer g.mtx.Unlock()
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
headPath := g.Head.Path
|
||||
|
||||
if err := g.headBuf.Flush(); err != nil {
|
||||
|
||||
@@ -275,3 +275,15 @@ func (m *Monitor) waitNextSample(now time.Duration) time.Duration {
|
||||
}
|
||||
return now
|
||||
}
|
||||
|
||||
// CurrentTransferRate returns the current transfer rate
|
||||
func (m *Monitor) CurrentTransferRate() int64 {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.sLast > m.start && m.active {
|
||||
return round(m.rEMA)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ func TestWait(t *testing.T) {
|
||||
defer close(done)
|
||||
got, err := q.Wait(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Wait: unexpected error: %w", err)
|
||||
t.Errorf("Wait: unexpected error: %v", err)
|
||||
} else if got != input {
|
||||
t.Errorf("Wait: got %q, want %q", got, input)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/internal/libs/clist"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmmath "github.com/tendermint/tendermint/libs/math"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
@@ -31,7 +31,7 @@ type TxMempool struct {
|
||||
logger log.Logger
|
||||
metrics *Metrics
|
||||
config *config.MempoolConfig
|
||||
proxyAppConn proxy.AppConnMempool
|
||||
proxyAppConn abciclient.Client
|
||||
|
||||
// txsAvailable fires once for each height when the mempool is not empty
|
||||
txsAvailable chan struct{}
|
||||
@@ -93,8 +93,7 @@ type TxMempool struct {
|
||||
func NewTxMempool(
|
||||
logger log.Logger,
|
||||
cfg *config.MempoolConfig,
|
||||
proxyAppConn proxy.AppConnMempool,
|
||||
height int64,
|
||||
proxyAppConn abciclient.Client,
|
||||
options ...TxMempoolOption,
|
||||
) *TxMempool {
|
||||
|
||||
@@ -102,7 +101,7 @@ func NewTxMempool(
|
||||
logger: logger,
|
||||
config: cfg,
|
||||
proxyAppConn: proxyAppConn,
|
||||
height: height,
|
||||
height: -1,
|
||||
cache: NopTxCache{},
|
||||
metrics: NopMetrics(),
|
||||
txStore: NewTxStore(),
|
||||
@@ -418,11 +417,10 @@ func (txmp *TxMempool) Update(
|
||||
ctx context.Context,
|
||||
blockHeight int64,
|
||||
blockTxs types.Txs,
|
||||
deliverTxResponses []*abci.ResponseDeliverTx,
|
||||
execTxResult []*abci.ExecTxResult,
|
||||
newPreFn PreCheckFunc,
|
||||
newPostFn PostCheckFunc,
|
||||
) error {
|
||||
|
||||
txmp.height = blockHeight
|
||||
txmp.notifiedTxsAvailable = false
|
||||
|
||||
@@ -434,7 +432,7 @@ func (txmp *TxMempool) Update(
|
||||
}
|
||||
|
||||
for i, tx := range blockTxs {
|
||||
if deliverTxResponses[i].Code == abci.CodeTypeOK {
|
||||
if execTxResult[i].Code == abci.CodeTypeOK {
|
||||
// add the valid committed transaction to the cache (if missing)
|
||||
_ = txmp.cache.Push(tx)
|
||||
} else if !txmp.config.KeepInvalidTxsInCache {
|
||||
|
||||
@@ -78,24 +78,24 @@ func setup(ctx context.Context, t testing.TB, cacheSize int, options ...TxMempoo
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
|
||||
app := &application{kvstore.NewApplication()}
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
logger := log.TestingLogger()
|
||||
|
||||
conn := abciclient.NewLocalClient(logger, &application{
|
||||
kvstore.NewApplication(),
|
||||
})
|
||||
|
||||
cfg, err := config.ResetTestRoot(t.TempDir(), strings.ReplaceAll(t.Name(), "/", "|"))
|
||||
require.NoError(t, err)
|
||||
cfg.Mempool.CacheSize = cacheSize
|
||||
appConnMem, err := cc(logger)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, appConnMem.Start(ctx))
|
||||
require.NoError(t, conn.Start(ctx))
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(cfg.RootDir)
|
||||
cancel()
|
||||
appConnMem.Wait()
|
||||
conn.Wait()
|
||||
})
|
||||
|
||||
return NewTxMempool(logger.With("test", t.Name()), cfg.Mempool, appConnMem, 0, options...)
|
||||
return NewTxMempool(logger.With("test", t.Name()), cfg.Mempool, conn, options...)
|
||||
}
|
||||
|
||||
func checkTxs(ctx context.Context, t *testing.T, txmp *TxMempool, numTxs int, peerID uint16) []testTx {
|
||||
@@ -172,9 +172,9 @@ func TestTxMempool_TxsAvailable(t *testing.T) {
|
||||
rawTxs[i] = tx.tx
|
||||
}
|
||||
|
||||
responses := make([]*abci.ResponseDeliverTx, len(rawTxs[:50]))
|
||||
responses := make([]*abci.ExecTxResult, len(rawTxs[:50]))
|
||||
for i := 0; i < len(responses); i++ {
|
||||
responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
|
||||
responses[i] = &abci.ExecTxResult{Code: abci.CodeTypeOK}
|
||||
}
|
||||
|
||||
// commit half the transactions and ensure we fire an event
|
||||
@@ -204,9 +204,9 @@ func TestTxMempool_Size(t *testing.T) {
|
||||
rawTxs[i] = tx.tx
|
||||
}
|
||||
|
||||
responses := make([]*abci.ResponseDeliverTx, len(rawTxs[:50]))
|
||||
responses := make([]*abci.ExecTxResult, len(rawTxs[:50]))
|
||||
for i := 0; i < len(responses); i++ {
|
||||
responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
|
||||
responses[i] = &abci.ExecTxResult{Code: abci.CodeTypeOK}
|
||||
}
|
||||
|
||||
txmp.Lock()
|
||||
@@ -231,9 +231,9 @@ func TestTxMempool_Flush(t *testing.T) {
|
||||
rawTxs[i] = tx.tx
|
||||
}
|
||||
|
||||
responses := make([]*abci.ResponseDeliverTx, len(rawTxs[:50]))
|
||||
responses := make([]*abci.ExecTxResult, len(rawTxs[:50]))
|
||||
for i := 0; i < len(responses); i++ {
|
||||
responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
|
||||
responses[i] = &abci.ExecTxResult{Code: abci.CodeTypeOK}
|
||||
}
|
||||
|
||||
txmp.Lock()
|
||||
@@ -446,7 +446,7 @@ func TestTxMempool_ConcurrentTxs(t *testing.T) {
|
||||
for range ticker.C {
|
||||
reapedTxs := txmp.ReapMaxTxs(200)
|
||||
if len(reapedTxs) > 0 {
|
||||
responses := make([]*abci.ResponseDeliverTx, len(reapedTxs))
|
||||
responses := make([]*abci.ExecTxResult, len(reapedTxs))
|
||||
for i := 0; i < len(responses); i++ {
|
||||
var code uint32
|
||||
|
||||
@@ -456,7 +456,7 @@ func TestTxMempool_ConcurrentTxs(t *testing.T) {
|
||||
code = abci.CodeTypeOK
|
||||
}
|
||||
|
||||
responses[i] = &abci.ResponseDeliverTx{Code: code}
|
||||
responses[i] = &abci.ExecTxResult{Code: code}
|
||||
}
|
||||
|
||||
txmp.Lock()
|
||||
@@ -494,9 +494,9 @@ func TestTxMempool_ExpiredTxs_NumBlocks(t *testing.T) {
|
||||
|
||||
// reap 5 txs at the next height -- no txs should expire
|
||||
reapedTxs := txmp.ReapMaxTxs(5)
|
||||
responses := make([]*abci.ResponseDeliverTx, len(reapedTxs))
|
||||
responses := make([]*abci.ExecTxResult, len(reapedTxs))
|
||||
for i := 0; i < len(responses); i++ {
|
||||
responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
|
||||
responses[i] = &abci.ExecTxResult{Code: abci.CodeTypeOK}
|
||||
}
|
||||
|
||||
txmp.Lock()
|
||||
@@ -520,9 +520,9 @@ func TestTxMempool_ExpiredTxs_NumBlocks(t *testing.T) {
|
||||
// removed. However, we do know that that at most 95 txs can be expired and
|
||||
// removed.
|
||||
reapedTxs = txmp.ReapMaxTxs(5)
|
||||
responses = make([]*abci.ResponseDeliverTx, len(reapedTxs))
|
||||
responses = make([]*abci.ExecTxResult, len(reapedTxs))
|
||||
for i := 0; i < len(responses); i++ {
|
||||
responses[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
|
||||
responses[i] = &abci.ExecTxResult{Code: abci.CodeTypeOK}
|
||||
}
|
||||
|
||||
txmp.Lock()
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/internal/libs/clist"
|
||||
"github.com/tendermint/tendermint/internal/mempool"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Mempool is an empty implementation of a Mempool, useful for testing.
|
||||
type Mempool struct{}
|
||||
|
||||
var _ Mempool = Mempool{}
|
||||
|
||||
func (Mempool) Lock() {}
|
||||
func (Mempool) Unlock() {}
|
||||
func (Mempool) Size() int { return 0 }
|
||||
func (Mempool) CheckTx(context.Context, types.Tx, func(*abci.ResponseCheckTx), mempool.TxInfo) error {
|
||||
return nil
|
||||
}
|
||||
func (Mempool) RemoveTxByKey(txKey types.TxKey) error { return nil }
|
||||
func (Mempool) ReapMaxBytesMaxGas(_, _ int64) types.Txs { return types.Txs{} }
|
||||
func (Mempool) ReapMaxTxs(n int) types.Txs { return types.Txs{} }
|
||||
func (Mempool) Update(
|
||||
_ context.Context,
|
||||
_ int64,
|
||||
_ types.Txs,
|
||||
_ []*abci.ResponseDeliverTx,
|
||||
_ mempool.PreCheckFunc,
|
||||
_ mempool.PostCheckFunc,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
func (Mempool) Flush() {}
|
||||
func (Mempool) FlushAppConn(ctx context.Context) error { return nil }
|
||||
func (Mempool) TxsAvailable() <-chan struct{} { return make(chan struct{}) }
|
||||
func (Mempool) EnableTxsAvailable() {}
|
||||
func (Mempool) SizeBytes() int64 { return 0 }
|
||||
|
||||
func (Mempool) TxsFront() *clist.CElement { return nil }
|
||||
func (Mempool) TxsWaitChan() <-chan struct{} { return nil }
|
||||
|
||||
func (Mempool) InitWAL() error { return nil }
|
||||
func (Mempool) CloseWAL() {}
|
||||
@@ -0,0 +1,172 @@
|
||||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
abcitypes "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
mempool "github.com/tendermint/tendermint/internal/mempool"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
types "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Mempool is an autogenerated mock type for the Mempool type
|
||||
type Mempool struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CheckTx provides a mock function with given fields: ctx, tx, callback, txInfo
|
||||
func (_m *Mempool) CheckTx(ctx context.Context, tx types.Tx, callback func(*abcitypes.ResponseCheckTx), txInfo mempool.TxInfo) error {
|
||||
ret := _m.Called(ctx, tx, callback, txInfo)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, types.Tx, func(*abcitypes.ResponseCheckTx), mempool.TxInfo) error); ok {
|
||||
r0 = rf(ctx, tx, callback, txInfo)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// EnableTxsAvailable provides a mock function with given fields:
|
||||
func (_m *Mempool) EnableTxsAvailable() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// Flush provides a mock function with given fields:
|
||||
func (_m *Mempool) Flush() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// FlushAppConn provides a mock function with given fields: _a0
|
||||
func (_m *Mempool) FlushAppConn(_a0 context.Context) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Lock provides a mock function with given fields:
|
||||
func (_m *Mempool) Lock() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// ReapMaxBytesMaxGas provides a mock function with given fields: maxBytes, maxGas
|
||||
func (_m *Mempool) ReapMaxBytesMaxGas(maxBytes int64, maxGas int64) types.Txs {
|
||||
ret := _m.Called(maxBytes, maxGas)
|
||||
|
||||
var r0 types.Txs
|
||||
if rf, ok := ret.Get(0).(func(int64, int64) types.Txs); ok {
|
||||
r0 = rf(maxBytes, maxGas)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(types.Txs)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ReapMaxTxs provides a mock function with given fields: max
|
||||
func (_m *Mempool) ReapMaxTxs(max int) types.Txs {
|
||||
ret := _m.Called(max)
|
||||
|
||||
var r0 types.Txs
|
||||
if rf, ok := ret.Get(0).(func(int) types.Txs); ok {
|
||||
r0 = rf(max)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(types.Txs)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// RemoveTxByKey provides a mock function with given fields: txKey
|
||||
func (_m *Mempool) RemoveTxByKey(txKey types.TxKey) error {
|
||||
ret := _m.Called(txKey)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(types.TxKey) error); ok {
|
||||
r0 = rf(txKey)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Size provides a mock function with given fields:
|
||||
func (_m *Mempool) Size() int {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 int
|
||||
if rf, ok := ret.Get(0).(func() int); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SizeBytes provides a mock function with given fields:
|
||||
func (_m *Mempool) SizeBytes() int64 {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func() int64); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// TxsAvailable provides a mock function with given fields:
|
||||
func (_m *Mempool) TxsAvailable() <-chan struct{} {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 <-chan struct{}
|
||||
if rf, ok := ret.Get(0).(func() <-chan struct{}); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(<-chan struct{})
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Unlock provides a mock function with given fields:
|
||||
func (_m *Mempool) Unlock() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, blockHeight, blockTxs, txResults, newPreFn, newPostFn
|
||||
func (_m *Mempool) Update(ctx context.Context, blockHeight int64, blockTxs types.Txs, txResults []*abcitypes.ExecTxResult, newPreFn mempool.PreCheckFunc, newPostFn mempool.PostCheckFunc) error {
|
||||
ret := _m.Called(ctx, blockHeight, blockTxs, txResults, newPreFn, newPostFn)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, types.Txs, []*abcitypes.ExecTxResult, mempool.PreCheckFunc, mempool.PostCheckFunc) error); ok {
|
||||
r0 = rf(ctx, blockHeight, blockTxs, txResults, newPreFn, newPostFn)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
@@ -242,9 +242,9 @@ func TestReactorConcurrency(t *testing.T) {
|
||||
mempool.Lock()
|
||||
defer mempool.Unlock()
|
||||
|
||||
deliverTxResponses := make([]*abci.ResponseDeliverTx, len(txs))
|
||||
deliverTxResponses := make([]*abci.ExecTxResult, len(txs))
|
||||
for i := range txs {
|
||||
deliverTxResponses[i] = &abci.ResponseDeliverTx{Code: 0}
|
||||
deliverTxResponses[i] = &abci.ExecTxResult{Code: 0}
|
||||
}
|
||||
|
||||
require.NoError(t, mempool.Update(ctx, 1, convertTex(txs), deliverTxResponses, nil, nil))
|
||||
@@ -261,7 +261,7 @@ func TestReactorConcurrency(t *testing.T) {
|
||||
mempool.Lock()
|
||||
defer mempool.Unlock()
|
||||
|
||||
err := mempool.Update(ctx, 1, []types.Tx{}, make([]*abci.ResponseDeliverTx, 0), nil, nil)
|
||||
err := mempool.Update(ctx, 1, []types.Tx{}, make([]*abci.ExecTxResult, 0), nil, nil)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ const (
|
||||
MaxActiveIDs = math.MaxUint16
|
||||
)
|
||||
|
||||
//go:generate ../../scripts/mockery_generate.sh Mempool
|
||||
|
||||
// Mempool defines the mempool interface.
|
||||
//
|
||||
// Updates to the mempool need to be synchronized with committing a block so
|
||||
@@ -66,7 +68,7 @@ type Mempool interface {
|
||||
ctx context.Context,
|
||||
blockHeight int64,
|
||||
blockTxs types.Txs,
|
||||
deliverTxResponses []*abci.ResponseDeliverTx,
|
||||
txResults []*abci.ExecTxResult,
|
||||
newPreFn PreCheckFunc,
|
||||
newPostFn PostCheckFunc,
|
||||
) error
|
||||
|
||||
@@ -413,7 +413,7 @@ func (c *MConnection) sendSomePacketMsgs(ctx context.Context) bool {
|
||||
// Block until .sendMonitor says we can write.
|
||||
// Once we're ready we send more than we asked for,
|
||||
// but amortized it should even out.
|
||||
c.sendMonitor.Limit(c._maxPacketMsgSize, atomic.LoadInt64(&c.config.SendRate), true)
|
||||
c.sendMonitor.Limit(c._maxPacketMsgSize, c.config.SendRate, true)
|
||||
|
||||
// Now send some PacketMsgs.
|
||||
for i := 0; i < numBatchPacketMsgs; i++ {
|
||||
@@ -481,7 +481,7 @@ FOR_LOOP:
|
||||
}
|
||||
|
||||
// Block until .recvMonitor says we can read.
|
||||
c.recvMonitor.Limit(c._maxPacketMsgSize, atomic.LoadInt64(&c.config.RecvRate), true)
|
||||
c.recvMonitor.Limit(c._maxPacketMsgSize, c.config.RecvRate, true)
|
||||
|
||||
// Peek into bufConnReader for debugging
|
||||
/*
|
||||
|
||||
@@ -126,7 +126,7 @@ func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
nodePrvKey := ed25519.GenPrivKey()
|
||||
nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey)
|
||||
if err != nil {
|
||||
t.Errorf("failed to establish SecretConnection for node: %w", err)
|
||||
t.Errorf("failed to establish SecretConnection for node: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
// In parallel, handle some reads and writes.
|
||||
@@ -136,7 +136,7 @@ func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
for _, nodeWrite := range nodeWrites {
|
||||
n, err := nodeSecretConn.Write([]byte(nodeWrite))
|
||||
if err != nil {
|
||||
t.Errorf("failed to write to nodeSecretConn: %w", err)
|
||||
t.Errorf("failed to write to nodeSecretConn: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
if n != len(nodeWrite) {
|
||||
@@ -163,7 +163,7 @@ func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
}
|
||||
return nil, false, nil
|
||||
} else if err != nil {
|
||||
t.Errorf("failed to read from nodeSecretConn: %w", err)
|
||||
t.Errorf("failed to read from nodeSecretConn: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
*nodeReads = append(*nodeReads, string(readBuffer[:n]))
|
||||
@@ -288,7 +288,7 @@ func writeLots(t *testing.T, wg *sync.WaitGroup, conn io.Writer, txt string, n i
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := conn.Write([]byte(txt))
|
||||
if err != nil {
|
||||
t.Errorf("failed to write to fooSecConn: %w", err)
|
||||
t.Errorf("failed to write to fooSecConn: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -343,7 +343,7 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection
|
||||
func(_ int) (val interface{}, abort bool, err error) {
|
||||
fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey)
|
||||
if err != nil {
|
||||
tb.Errorf("failed to establish SecretConnection for foo: %w", err)
|
||||
tb.Errorf("failed to establish SecretConnection for foo: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
remotePubBytes := fooSecConn.RemotePubKey()
|
||||
@@ -358,7 +358,7 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection
|
||||
func(_ int) (val interface{}, abort bool, err error) {
|
||||
barSecConn, err = MakeSecretConnection(barConn, barPrvKey)
|
||||
if barSecConn == nil {
|
||||
tb.Errorf("failed to establish SecretConnection for bar: %w", err)
|
||||
tb.Errorf("failed to establish SecretConnection for bar: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
remotePubBytes := barSecConn.RemotePubKey()
|
||||
@@ -405,7 +405,7 @@ func BenchmarkWriteSecretConnection(b *testing.B) {
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
b.Errorf("failed to read from barSecConn: %w", err)
|
||||
b.Errorf("failed to read from barSecConn: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -416,7 +416,7 @@ func BenchmarkWriteSecretConnection(b *testing.B) {
|
||||
idx := mrand.Intn(len(fooWriteBytes))
|
||||
_, err := fooSecConn.Write(fooWriteBytes[idx])
|
||||
if err != nil {
|
||||
b.Errorf("failed to write to fooSecConn: %w", err)
|
||||
b.Errorf("failed to write to fooSecConn: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+96
-137
@@ -3,14 +3,12 @@ package pex
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/internal/p2p/conn"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmmath "github.com/tendermint/tendermint/libs/math"
|
||||
"github.com/tendermint/tendermint/libs/service"
|
||||
protop2p "github.com/tendermint/tendermint/proto/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
@@ -42,7 +40,7 @@ const (
|
||||
minReceiveRequestInterval = 100 * time.Millisecond
|
||||
|
||||
// the maximum amount of addresses that can be included in a response
|
||||
maxAddresses uint16 = 100
|
||||
maxAddresses = 100
|
||||
|
||||
// How long to wait when there are no peers available before trying again
|
||||
noAvailablePeersWaitPeriod = 1 * time.Second
|
||||
@@ -100,15 +98,8 @@ type Reactor struct {
|
||||
// minReceiveRequestInterval).
|
||||
lastReceivedRequests map[types.NodeID]time.Time
|
||||
|
||||
// keep track of how many new peers to existing peers we have received to
|
||||
// extrapolate the size of the network
|
||||
newPeers uint32
|
||||
totalPeers uint32
|
||||
|
||||
// discoveryRatio is the inverse ratio of new peers to old peers squared.
|
||||
// This is multiplied by the minimum duration to calculate how long to wait
|
||||
// between each request.
|
||||
discoveryRatio float32
|
||||
// the total number of unique peers added
|
||||
totalPeers int
|
||||
}
|
||||
|
||||
// NewReactor returns a reference to a new reactor.
|
||||
@@ -156,16 +147,6 @@ func (r *Reactor) OnStop() {}
|
||||
// processPexCh implements a blocking event loop where we listen for p2p
|
||||
// Envelope messages from the pexCh.
|
||||
func (r *Reactor) processPexCh(ctx context.Context) {
|
||||
timer := time.NewTimer(0)
|
||||
defer timer.Stop()
|
||||
|
||||
r.mtx.Lock()
|
||||
var (
|
||||
duration = r.calculateNextRequestTime()
|
||||
err error
|
||||
)
|
||||
r.mtx.Unlock()
|
||||
|
||||
incoming := make(chan *p2p.Envelope)
|
||||
go func() {
|
||||
defer close(incoming)
|
||||
@@ -179,36 +160,51 @@ func (r *Reactor) processPexCh(ctx context.Context) {
|
||||
}
|
||||
}()
|
||||
|
||||
// Initially, we will request peers quickly to bootstrap. This duration
|
||||
// will be adjusted upward as knowledge of the network grows.
|
||||
var nextPeerRequest = minReceiveRequestInterval
|
||||
|
||||
timer := time.NewTimer(0)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
timer.Reset(duration)
|
||||
timer.Reset(nextPeerRequest)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
// outbound requests for new peers
|
||||
case <-timer.C:
|
||||
duration, err = r.sendRequestForPeers(ctx)
|
||||
if err != nil {
|
||||
// Send a request for more peer addresses.
|
||||
if err := r.sendRequestForPeers(ctx); err != nil {
|
||||
return
|
||||
// TODO(creachadair): Do we really want to stop processing the PEX
|
||||
// channel just because of an error here?
|
||||
}
|
||||
// inbound requests for new peers or responses to requests sent by this
|
||||
// reactor
|
||||
|
||||
// Note we do not update the poll timer upon making a request, only
|
||||
// when we receive an update that updates our priors.
|
||||
|
||||
case envelope, ok := <-incoming:
|
||||
if !ok {
|
||||
return
|
||||
return // channel closed
|
||||
}
|
||||
duration, err = r.handleMessage(ctx, r.pexCh.ID, envelope)
|
||||
|
||||
// A request from another peer, or a response to one of our requests.
|
||||
dur, err := r.handlePexMessage(ctx, envelope)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to process message", "ch_id", r.pexCh.ID, "envelope", envelope, "err", err)
|
||||
r.logger.Error("failed to process message",
|
||||
"ch_id", r.pexCh.ID, "envelope", envelope, "err", err)
|
||||
if serr := r.pexCh.SendError(ctx, p2p.PeerError{
|
||||
NodeID: envelope.From,
|
||||
Err: err,
|
||||
}); serr != nil {
|
||||
return
|
||||
}
|
||||
} else if dur != 0 {
|
||||
// We got a useful result; update the poll timer.
|
||||
nextPeerRequest = dur
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,19 +224,20 @@ func (r *Reactor) processPeerUpdates(ctx context.Context) {
|
||||
}
|
||||
|
||||
// handlePexMessage handles envelopes sent from peers on the PexChannel.
|
||||
// If an update was received, a new polling interval is returned; otherwise the
|
||||
// duration is 0.
|
||||
func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope) (time.Duration, error) {
|
||||
logger := r.logger.With("peer", envelope.From)
|
||||
|
||||
switch msg := envelope.Message.(type) {
|
||||
case *protop2p.PexRequest:
|
||||
// check if the peer hasn't sent a prior request too close to this one
|
||||
// in time
|
||||
// Verify that this peer hasn't sent us another request too recently.
|
||||
if err := r.markPeerRequest(envelope.From); err != nil {
|
||||
return time.Minute, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// request peers from the peer manager and parse the NodeAddresses into
|
||||
// URL strings
|
||||
// Fetch peers from the peer manager, convert NodeAddresses into URL
|
||||
// strings, and send them back to the caller.
|
||||
nodeAddresses := r.peerManager.Advertise(envelope.From, maxAddresses)
|
||||
pexAddresses := make([]protop2p.PexAddress, len(nodeAddresses))
|
||||
for idx, addr := range nodeAddresses {
|
||||
@@ -248,28 +245,24 @@ func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope)
|
||||
URL: addr.String(),
|
||||
}
|
||||
}
|
||||
if err := r.pexCh.Send(ctx, p2p.Envelope{
|
||||
return 0, r.pexCh.Send(ctx, p2p.Envelope{
|
||||
To: envelope.From,
|
||||
Message: &protop2p.PexResponse{Addresses: pexAddresses},
|
||||
}); err != nil {
|
||||
})
|
||||
|
||||
case *protop2p.PexResponse:
|
||||
// Verify that this response corresponds to one of our pending requests.
|
||||
if err := r.markPeerResponse(envelope.From); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return time.Second, nil
|
||||
case *protop2p.PexResponse:
|
||||
// check if the response matches a request that was made to that peer
|
||||
if err := r.markPeerResponse(envelope.From); err != nil {
|
||||
return time.Minute, err
|
||||
}
|
||||
|
||||
// check the size of the response
|
||||
if len(msg.Addresses) > int(maxAddresses) {
|
||||
return 10 * time.Minute, fmt.Errorf("peer sent too many addresses (max: %d, got: %d)",
|
||||
maxAddresses,
|
||||
len(msg.Addresses),
|
||||
)
|
||||
// Verify that the response does not exceed the safety limit.
|
||||
if len(msg.Addresses) > maxAddresses {
|
||||
return 0, fmt.Errorf("peer sent too many addresses (%d > maxiumum %d)",
|
||||
len(msg.Addresses), maxAddresses)
|
||||
}
|
||||
|
||||
var numAdded int
|
||||
for _, pexAddress := range msg.Addresses {
|
||||
peerAddress, err := p2p.ParseNodeAddress(pexAddress.URL)
|
||||
if err != nil {
|
||||
@@ -278,47 +271,21 @@ func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope)
|
||||
added, err := r.peerManager.Add(peerAddress)
|
||||
if err != nil {
|
||||
logger.Error("failed to add PEX address", "address", peerAddress, "err", err)
|
||||
continue
|
||||
}
|
||||
if added {
|
||||
r.newPeers++
|
||||
numAdded++
|
||||
logger.Debug("added PEX address", "address", peerAddress)
|
||||
}
|
||||
r.totalPeers++
|
||||
}
|
||||
|
||||
return 10 * time.Minute, nil
|
||||
return r.calculateNextRequestTime(numAdded), nil
|
||||
|
||||
default:
|
||||
return time.Second, fmt.Errorf("received unknown message: %T", msg)
|
||||
return 0, fmt.Errorf("received unknown message: %T", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMessage handles an Envelope sent from a peer on a specific p2p Channel.
|
||||
// It will handle errors and any possible panics gracefully. A caller can handle
|
||||
// any error returned by sending a PeerError on the respective channel.
|
||||
func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (duration time.Duration, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("panic in processing message: %v", e)
|
||||
r.logger.Error(
|
||||
"recovering from processing message panic",
|
||||
"err", err,
|
||||
"stack", string(debug.Stack()),
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
||||
r.logger.Debug("received PEX message", "peer", envelope.From)
|
||||
|
||||
switch chID {
|
||||
case p2p.ChannelID(PexChannel):
|
||||
duration, err = r.handlePexMessage(ctx, envelope)
|
||||
default:
|
||||
err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// processPeerUpdate processes a PeerUpdate. For added peers, PeerStatusUp, we
|
||||
// send a request for addresses.
|
||||
func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) {
|
||||
@@ -338,95 +305,87 @@ func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) {
|
||||
}
|
||||
}
|
||||
|
||||
// sendRequestForPeers pops the first peerID off the list and sends the
|
||||
// peer a request for more peer addresses. The function then moves the
|
||||
// peer into the requestsSent bucket and calculates when the next request
|
||||
// time should be
|
||||
func (r *Reactor) sendRequestForPeers(ctx context.Context) (time.Duration, error) {
|
||||
// sendRequestForPeers chooses a peer from the set of available peers and sends
|
||||
// that peer a request for more peer addresses. The chosen peer is moved into
|
||||
// the requestsSent bucket so that we will not attempt to contact them again
|
||||
// until they've replied or updated.
|
||||
func (r *Reactor) sendRequestForPeers(ctx context.Context) error {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
if len(r.availablePeers) == 0 {
|
||||
// no peers are available
|
||||
r.logger.Debug("no available peers to send request to, waiting...")
|
||||
return noAvailablePeersWaitPeriod, nil
|
||||
r.logger.Debug("no available peers to send a PEX request to (retrying)")
|
||||
return nil
|
||||
}
|
||||
var peerID types.NodeID
|
||||
|
||||
// use range to get a random peer.
|
||||
// Select an arbitrary peer from the available set.
|
||||
var peerID types.NodeID
|
||||
for peerID = range r.availablePeers {
|
||||
break
|
||||
}
|
||||
|
||||
// send out the pex request
|
||||
if err := r.pexCh.Send(ctx, p2p.Envelope{
|
||||
To: peerID,
|
||||
Message: &protop2p.PexRequest{},
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
|
||||
// remove the peer from the abvailable peers list and mark it in the requestsSent map
|
||||
// Move the peer from available to pending.
|
||||
delete(r.availablePeers, peerID)
|
||||
r.requestsSent[peerID] = struct{}{}
|
||||
|
||||
dur := r.calculateNextRequestTime()
|
||||
r.logger.Debug("peer request sent", "next_request_time", dur)
|
||||
return dur, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateNextRequestTime implements something of a proportional controller
|
||||
// to estimate how often the reactor should be requesting new peer addresses.
|
||||
// The dependent variable in this calculation is the ratio of new peers to
|
||||
// all peers that the reactor receives. The interval is thus calculated as the
|
||||
// inverse squared. In the beginning, all peers should be new peers.
|
||||
// We expect this ratio to be near 1 and thus the interval to be as short
|
||||
// as possible. As the node becomes more familiar with the network the ratio of
|
||||
// new nodes will plummet to a very small number, meaning the interval expands
|
||||
// to its upper bound.
|
||||
// calculateNextRequestTime selects how long we should wait before attempting
|
||||
// to send out another request for peer addresses.
|
||||
//
|
||||
// CONTRACT: The caller must hold r.mtx exclusively when calling this method.
|
||||
func (r *Reactor) calculateNextRequestTime() time.Duration {
|
||||
// check if the peer store is full. If so then there is no need
|
||||
// to send peer requests too often
|
||||
// This implements a simplified proportional control mechanism to poll more
|
||||
// often when our knowledge of the network is incomplete, and less often as our
|
||||
// knowledge grows. To estimate our knowledge of the network, we use the
|
||||
// fraction of "new" peers (addresses we have not previously seen) to the total
|
||||
// so far observed. When we first join the network, this fraction will be close
|
||||
// to 1, meaning most new peers are "new" to us, and as we discover more peers,
|
||||
// the fraction will go toward zero.
|
||||
//
|
||||
// The minimum interval will be minReceiveRequestInterval to ensure we will not
|
||||
// request from any peer more often than we would allow them to do from us.
|
||||
func (r *Reactor) calculateNextRequestTime(added int) time.Duration {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.totalPeers += added
|
||||
|
||||
// If the peer store is nearly full, wait the maximum interval.
|
||||
if ratio := r.peerManager.PeerRatio(); ratio >= 0.95 {
|
||||
r.logger.Debug("peer manager near full ratio, sleeping...",
|
||||
r.logger.Debug("Peer manager is nearly full",
|
||||
"sleep_period", fullCapacityInterval, "ratio", ratio)
|
||||
return fullCapacityInterval
|
||||
}
|
||||
|
||||
// baseTime represents the shortest interval that we can send peer requests
|
||||
// in. For example if we have 10 peers and we can't send a message to the
|
||||
// same peer every 500ms, then we can send a request every 50ms. In practice
|
||||
// we use a safety margin of 2, ergo 100ms
|
||||
peers := tmmath.MinInt(len(r.availablePeers), 50)
|
||||
baseTime := minReceiveRequestInterval
|
||||
if peers > 0 {
|
||||
baseTime = minReceiveRequestInterval * 2 / time.Duration(peers)
|
||||
// If there are no available peers to query, poll less aggressively.
|
||||
if len(r.availablePeers) == 0 {
|
||||
r.logger.Debug("No available peers to send a PEX request",
|
||||
"sleep_period", noAvailablePeersWaitPeriod)
|
||||
return noAvailablePeersWaitPeriod
|
||||
}
|
||||
|
||||
if r.totalPeers > 0 || r.discoveryRatio == 0 {
|
||||
// find the ratio of new peers. NOTE: We add 1 to both sides to avoid
|
||||
// divide by zero problems
|
||||
ratio := float32(r.totalPeers+1) / float32(r.newPeers+1)
|
||||
// square the ratio in order to get non linear time intervals
|
||||
// NOTE: The longest possible interval for a network with 100 or more peers
|
||||
// where a node is connected to 50 of them is 2 minutes.
|
||||
r.discoveryRatio = ratio * ratio
|
||||
r.newPeers = 0
|
||||
r.totalPeers = 0
|
||||
}
|
||||
// NOTE: As ratio is always >= 1, discovery ratio is >= 1. Therefore we don't need to worry
|
||||
// about the next request time being less than the minimum time
|
||||
return baseTime * time.Duration(r.discoveryRatio)
|
||||
// Reaching here, there are available peers to query and the peer store
|
||||
// still has space. Estimate our knowledge of the network from the latest
|
||||
// update and choose a new interval.
|
||||
base := float64(minReceiveRequestInterval) / float64(len(r.availablePeers))
|
||||
multiplier := float64(r.totalPeers+1) / float64(added+1) // +1 to avert zero division
|
||||
return time.Duration(base*multiplier*multiplier) + minReceiveRequestInterval
|
||||
}
|
||||
|
||||
func (r *Reactor) markPeerRequest(peer types.NodeID) error {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
if lastRequestTime, ok := r.lastReceivedRequests[peer]; ok {
|
||||
if time.Now().Before(lastRequestTime.Add(minReceiveRequestInterval)) {
|
||||
return fmt.Errorf("peer sent a request too close after a prior one. Minimum interval: %v",
|
||||
minReceiveRequestInterval)
|
||||
if d := time.Since(lastRequestTime); d < minReceiveRequestInterval {
|
||||
return fmt.Errorf("peer %v sent PEX request too soon (%v < minimum %v)",
|
||||
peer, d, minReceiveRequestInterval)
|
||||
}
|
||||
}
|
||||
r.lastReceivedRequests[peer] = time.Now()
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestReactorSendsRequestsTooOften(t *testing.T) {
|
||||
peerErr := <-r.pexErrCh
|
||||
require.Error(t, peerErr.Err)
|
||||
require.Empty(t, r.pexOutCh)
|
||||
require.Contains(t, peerErr.Err.Error(), "peer sent a request too close after a prior one")
|
||||
require.Contains(t, peerErr.Err.Error(), "sent PEX request too soon")
|
||||
require.Equal(t, badNode, peerErr.NodeID)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
//go:generate ../../scripts/mockery_generate.sh AppConnConsensus|AppConnMempool|AppConnQuery|AppConnSnapshot
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
// Enforce which abci msgs can be sent on a connection at the type level
|
||||
|
||||
type AppConnConsensus interface {
|
||||
Error() error
|
||||
|
||||
InitChain(context.Context, types.RequestInitChain) (*types.ResponseInitChain, error)
|
||||
|
||||
PrepareProposal(context.Context, types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error)
|
||||
ProcessProposal(context.Context, types.RequestProcessProposal) (*types.ResponseProcessProposal, error)
|
||||
ExtendVote(context.Context, types.RequestExtendVote) (*types.ResponseExtendVote, error)
|
||||
VerifyVoteExtension(context.Context, types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error)
|
||||
FinalizeBlock(context.Context, types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error)
|
||||
Commit(context.Context) (*types.ResponseCommit, error)
|
||||
}
|
||||
|
||||
type AppConnMempool interface {
|
||||
Error() error
|
||||
|
||||
CheckTx(context.Context, types.RequestCheckTx) (*types.ResponseCheckTx, error)
|
||||
|
||||
Flush(context.Context) error
|
||||
}
|
||||
|
||||
type AppConnQuery interface {
|
||||
Error() error
|
||||
|
||||
Echo(context.Context, string) (*types.ResponseEcho, error)
|
||||
Info(context.Context, types.RequestInfo) (*types.ResponseInfo, error)
|
||||
Query(context.Context, types.RequestQuery) (*types.ResponseQuery, error)
|
||||
}
|
||||
|
||||
type AppConnSnapshot interface {
|
||||
Error() error
|
||||
|
||||
ListSnapshots(context.Context, types.RequestListSnapshots) (*types.ResponseListSnapshots, error)
|
||||
OfferSnapshot(context.Context, types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error)
|
||||
LoadSnapshotChunk(context.Context, types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error)
|
||||
ApplySnapshotChunk(context.Context, types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------
|
||||
// Implements AppConnConsensus (subset of abciclient.Client)
|
||||
|
||||
type appConnConsensus struct {
|
||||
metrics *Metrics
|
||||
appConn abciclient.Client
|
||||
}
|
||||
|
||||
var _ AppConnConsensus = (*appConnConsensus)(nil)
|
||||
|
||||
func NewAppConnConsensus(appConn abciclient.Client, metrics *Metrics) AppConnConsensus {
|
||||
return &appConnConsensus{
|
||||
metrics: metrics,
|
||||
appConn: appConn,
|
||||
}
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) Error() error {
|
||||
return app.appConn.Error()
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) InitChain(
|
||||
ctx context.Context,
|
||||
req types.RequestInitChain,
|
||||
) (*types.ResponseInitChain, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "init_chain", "type", "sync"))()
|
||||
return app.appConn.InitChain(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) PrepareProposal(
|
||||
ctx context.Context,
|
||||
req types.RequestPrepareProposal,
|
||||
) (*types.ResponsePrepareProposal, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "prepare_proposal", "type", "sync"))()
|
||||
return app.appConn.PrepareProposal(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) ProcessProposal(
|
||||
ctx context.Context,
|
||||
req types.RequestProcessProposal,
|
||||
) (*types.ResponseProcessProposal, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "process_proposal", "type", "sync"))()
|
||||
return app.appConn.ProcessProposal(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) ExtendVote(
|
||||
ctx context.Context,
|
||||
req types.RequestExtendVote,
|
||||
) (*types.ResponseExtendVote, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "extend_vote", "type", "sync"))()
|
||||
return app.appConn.ExtendVote(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) VerifyVoteExtension(
|
||||
ctx context.Context,
|
||||
req types.RequestVerifyVoteExtension,
|
||||
) (*types.ResponseVerifyVoteExtension, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "verify_vote_extension", "type", "sync"))()
|
||||
return app.appConn.VerifyVoteExtension(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) FinalizeBlock(
|
||||
ctx context.Context,
|
||||
req types.RequestFinalizeBlock,
|
||||
) (*types.ResponseFinalizeBlock, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "finalize_block", "type", "sync"))()
|
||||
return app.appConn.FinalizeBlock(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnConsensus) Commit(ctx context.Context) (*types.ResponseCommit, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "commit", "type", "sync"))()
|
||||
return app.appConn.Commit(ctx)
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
// Implements AppConnMempool (subset of abciclient.Client)
|
||||
|
||||
type appConnMempool struct {
|
||||
metrics *Metrics
|
||||
appConn abciclient.Client
|
||||
}
|
||||
|
||||
func NewAppConnMempool(appConn abciclient.Client, metrics *Metrics) AppConnMempool {
|
||||
return &appConnMempool{
|
||||
metrics: metrics,
|
||||
appConn: appConn,
|
||||
}
|
||||
}
|
||||
|
||||
func (app *appConnMempool) Error() error {
|
||||
return app.appConn.Error()
|
||||
}
|
||||
|
||||
func (app *appConnMempool) Flush(ctx context.Context) error {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "flush", "type", "sync"))()
|
||||
return app.appConn.Flush(ctx)
|
||||
}
|
||||
|
||||
func (app *appConnMempool) CheckTx(ctx context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "check_tx", "type", "sync"))()
|
||||
return app.appConn.CheckTx(ctx, req)
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
// Implements AppConnQuery (subset of abciclient.Client)
|
||||
|
||||
type appConnQuery struct {
|
||||
metrics *Metrics
|
||||
appConn abciclient.Client
|
||||
}
|
||||
|
||||
func NewAppConnQuery(appConn abciclient.Client, metrics *Metrics) AppConnQuery {
|
||||
return &appConnQuery{
|
||||
metrics: metrics,
|
||||
appConn: appConn,
|
||||
}
|
||||
}
|
||||
|
||||
func (app *appConnQuery) Error() error {
|
||||
return app.appConn.Error()
|
||||
}
|
||||
|
||||
func (app *appConnQuery) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "echo", "type", "sync"))()
|
||||
return app.appConn.Echo(ctx, msg)
|
||||
}
|
||||
|
||||
func (app *appConnQuery) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "info", "type", "sync"))()
|
||||
return app.appConn.Info(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnQuery) Query(ctx context.Context, reqQuery types.RequestQuery) (*types.ResponseQuery, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "query", "type", "sync"))()
|
||||
return app.appConn.Query(ctx, reqQuery)
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
// Implements AppConnSnapshot (subset of abciclient.Client)
|
||||
|
||||
type appConnSnapshot struct {
|
||||
metrics *Metrics
|
||||
appConn abciclient.Client
|
||||
}
|
||||
|
||||
func NewAppConnSnapshot(appConn abciclient.Client, metrics *Metrics) AppConnSnapshot {
|
||||
return &appConnSnapshot{
|
||||
metrics: metrics,
|
||||
appConn: appConn,
|
||||
}
|
||||
}
|
||||
|
||||
func (app *appConnSnapshot) Error() error {
|
||||
return app.appConn.Error()
|
||||
}
|
||||
|
||||
func (app *appConnSnapshot) ListSnapshots(
|
||||
ctx context.Context,
|
||||
req types.RequestListSnapshots,
|
||||
) (*types.ResponseListSnapshots, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "list_snapshots", "type", "sync"))()
|
||||
return app.appConn.ListSnapshots(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnSnapshot) OfferSnapshot(
|
||||
ctx context.Context,
|
||||
req types.RequestOfferSnapshot,
|
||||
) (*types.ResponseOfferSnapshot, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "offer_snapshot", "type", "sync"))()
|
||||
return app.appConn.OfferSnapshot(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnSnapshot) LoadSnapshotChunk(
|
||||
ctx context.Context,
|
||||
req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "load_snapshot_chunk", "type", "sync"))()
|
||||
return app.appConn.LoadSnapshotChunk(ctx, req)
|
||||
}
|
||||
|
||||
func (app *appConnSnapshot) ApplySnapshotChunk(
|
||||
ctx context.Context,
|
||||
req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "apply_snapshot_chunk", "type", "sync"))()
|
||||
return app.appConn.ApplySnapshotChunk(ctx, req)
|
||||
}
|
||||
|
||||
// addTimeSample returns a function that, when called, adds an observation to m.
|
||||
// The observation added to m is the number of seconds ellapsed since addTimeSample
|
||||
// was initially called. addTimeSample is meant to be called in a defer to calculate
|
||||
// the amount of time a function takes to complete.
|
||||
func addTimeSample(m metrics.Histogram) func() {
|
||||
start := time.Now()
|
||||
return func() { m.Observe(time.Since(start).Seconds()) }
|
||||
}
|
||||
+182
-11
@@ -1,42 +1,213 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/libs/service"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/app"
|
||||
)
|
||||
|
||||
// DefaultClientCreator returns a default ClientCreator, which will create a
|
||||
// local client if addr is one of: 'kvstore',
|
||||
// 'persistent_kvstore', 'e2e', or 'noop', otherwise - a remote client.
|
||||
// ClientFactory returns a client object, which will create a local
|
||||
// client if addr is one of: 'kvstore', 'persistent_kvstore', 'e2e',
|
||||
// or 'noop', otherwise - a remote client.
|
||||
//
|
||||
// The Closer is a noop except for persistent_kvstore applications,
|
||||
// which will clean up the store.
|
||||
func DefaultClientCreator(logger log.Logger, addr, transport, dbDir string) (abciclient.Creator, io.Closer) {
|
||||
func ClientFactory(logger log.Logger, addr, transport, dbDir string) (abciclient.Client, io.Closer, error) {
|
||||
switch addr {
|
||||
case "kvstore":
|
||||
return abciclient.NewLocalCreator(kvstore.NewApplication()), noopCloser{}
|
||||
return abciclient.NewLocalClient(logger, kvstore.NewApplication()), noopCloser{}, nil
|
||||
case "persistent_kvstore":
|
||||
app := kvstore.NewPersistentKVStoreApplication(logger, dbDir)
|
||||
return abciclient.NewLocalCreator(app), app
|
||||
return abciclient.NewLocalClient(logger, app), app, nil
|
||||
case "e2e":
|
||||
app, err := e2e.NewApplication(e2e.DefaultConfig(dbDir))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, noopCloser{}, err
|
||||
}
|
||||
return abciclient.NewLocalCreator(app), noopCloser{}
|
||||
return abciclient.NewLocalClient(logger, app), noopCloser{}, nil
|
||||
case "noop":
|
||||
return abciclient.NewLocalCreator(types.NewBaseApplication()), noopCloser{}
|
||||
return abciclient.NewLocalClient(logger, types.NewBaseApplication()), noopCloser{}, nil
|
||||
default:
|
||||
mustConnect := false // loop retrying
|
||||
return abciclient.NewRemoteCreator(logger, addr, transport, mustConnect), noopCloser{}
|
||||
const mustConnect = false // loop retrying
|
||||
client, err := abciclient.NewClient(logger, addr, transport, mustConnect)
|
||||
if err != nil {
|
||||
return nil, noopCloser{}, err
|
||||
}
|
||||
|
||||
return client, noopCloser{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type noopCloser struct{}
|
||||
|
||||
func (noopCloser) Close() error { return nil }
|
||||
|
||||
// proxyClient provides the application connection.
|
||||
type proxyClient struct {
|
||||
service.BaseService
|
||||
logger log.Logger
|
||||
|
||||
client abciclient.Client
|
||||
metrics *Metrics
|
||||
}
|
||||
|
||||
// New creates a proxy application interface.
|
||||
func New(client abciclient.Client, logger log.Logger, metrics *Metrics) abciclient.Client {
|
||||
conn := &proxyClient{
|
||||
logger: logger,
|
||||
metrics: metrics,
|
||||
client: client,
|
||||
}
|
||||
conn.BaseService = *service.NewBaseService(logger, "proxyClient", conn)
|
||||
return conn
|
||||
}
|
||||
|
||||
func (app *proxyClient) OnStop() { tryCallStop(app.client) }
|
||||
func (app *proxyClient) Error() error { return app.client.Error() }
|
||||
|
||||
func tryCallStop(client abciclient.Client) {
|
||||
if c, ok := client.(interface{ Stop() }); ok {
|
||||
c.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (app *proxyClient) OnStart(ctx context.Context) error {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tryCallStop(app.client)
|
||||
}
|
||||
}()
|
||||
|
||||
// Kill Tendermint if the ABCI application crashes.
|
||||
go func() {
|
||||
if !app.client.IsRunning() {
|
||||
return
|
||||
}
|
||||
app.client.Wait()
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.client.Error(); err != nil {
|
||||
app.logger.Error("client connection terminated. Did the application crash? Please restart tendermint",
|
||||
"err", err)
|
||||
|
||||
if killErr := kill(); killErr != nil {
|
||||
app.logger.Error("Failed to kill this process - please do so manually",
|
||||
"err", killErr)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return app.client.Start(ctx)
|
||||
}
|
||||
|
||||
func kill() error {
|
||||
p, err := os.FindProcess(os.Getpid())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Signal(syscall.SIGABRT)
|
||||
}
|
||||
|
||||
func (app *proxyClient) InitChain(ctx context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "init_chain", "type", "sync"))()
|
||||
return app.client.InitChain(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) PrepareProposal(ctx context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "prepare_proposal", "type", "sync"))()
|
||||
return app.client.PrepareProposal(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) ProcessProposal(ctx context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "process_proposal", "type", "sync"))()
|
||||
return app.client.ProcessProposal(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) ExtendVote(ctx context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "extend_vote", "type", "sync"))()
|
||||
return app.client.ExtendVote(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) VerifyVoteExtension(ctx context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "verify_vote_extension", "type", "sync"))()
|
||||
return app.client.VerifyVoteExtension(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) FinalizeBlock(ctx context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "finalize_block", "type", "sync"))()
|
||||
return app.client.FinalizeBlock(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) Commit(ctx context.Context) (*types.ResponseCommit, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "commit", "type", "sync"))()
|
||||
return app.client.Commit(ctx)
|
||||
}
|
||||
|
||||
func (app *proxyClient) Flush(ctx context.Context) error {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "flush", "type", "sync"))()
|
||||
return app.client.Flush(ctx)
|
||||
}
|
||||
|
||||
func (app *proxyClient) CheckTx(ctx context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "check_tx", "type", "sync"))()
|
||||
return app.client.CheckTx(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "echo", "type", "sync"))()
|
||||
return app.client.Echo(ctx, msg)
|
||||
}
|
||||
|
||||
func (app *proxyClient) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "info", "type", "sync"))()
|
||||
return app.client.Info(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) Query(ctx context.Context, reqQuery types.RequestQuery) (*types.ResponseQuery, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "query", "type", "sync"))()
|
||||
return app.client.Query(ctx, reqQuery)
|
||||
}
|
||||
|
||||
func (app *proxyClient) ListSnapshots(ctx context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "list_snapshots", "type", "sync"))()
|
||||
return app.client.ListSnapshots(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) OfferSnapshot(ctx context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "offer_snapshot", "type", "sync"))()
|
||||
return app.client.OfferSnapshot(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) LoadSnapshotChunk(ctx context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "load_snapshot_chunk", "type", "sync"))()
|
||||
return app.client.LoadSnapshotChunk(ctx, req)
|
||||
}
|
||||
|
||||
func (app *proxyClient) ApplySnapshotChunk(ctx context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
|
||||
defer addTimeSample(app.metrics.MethodTiming.With("method", "apply_snapshot_chunk", "type", "sync"))()
|
||||
return app.client.ApplySnapshotChunk(ctx, req)
|
||||
}
|
||||
|
||||
// addTimeSample returns a function that, when called, adds an observation to m.
|
||||
// The observation added to m is the number of seconds ellapsed since addTimeSample
|
||||
// was initially called. addTimeSample is meant to be called in a defer to calculate
|
||||
// the amount of time a function takes to complete.
|
||||
func addTimeSample(m metrics.Histogram) func() {
|
||||
start := time.Now()
|
||||
return func() { m.Observe(time.Since(start).Seconds()) }
|
||||
}
|
||||
|
||||
@@ -2,18 +2,26 @@ package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abcimocks "github.com/tendermint/tendermint/abci/client/mocks"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/tendermint/abci/server"
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
//----------------------------------------
|
||||
@@ -51,7 +59,10 @@ var SOCKET = "socket"
|
||||
func TestEcho(t *testing.T) {
|
||||
sockPath := fmt.Sprintf("unix:///tmp/echo_%v.sock", tmrand.Str(6))
|
||||
logger := log.TestingLogger()
|
||||
clientCreator := abciclient.NewRemoteCreator(logger, sockPath, SOCKET, true)
|
||||
client, err := abciclient.NewClient(logger, sockPath, SOCKET, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -62,12 +73,9 @@ func TestEcho(t *testing.T) {
|
||||
t.Cleanup(func() { cancel(); s.Wait() })
|
||||
|
||||
// Start client
|
||||
cli, err := clientCreator(logger.With("module", "abci-client"))
|
||||
require.NoError(t, err, "Error creating ABCI client:")
|
||||
require.NoError(t, client.Start(ctx), "Error starting ABCI client")
|
||||
|
||||
require.NoError(t, cli.Start(ctx), "Error starting ABCI client")
|
||||
|
||||
proxy := newAppConnTest(cli)
|
||||
proxy := newAppConnTest(client)
|
||||
t.Log("Connected")
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
@@ -91,7 +99,10 @@ func BenchmarkEcho(b *testing.B) {
|
||||
b.StopTimer() // Initialize
|
||||
sockPath := fmt.Sprintf("unix:///tmp/echo_%v.sock", tmrand.Str(6))
|
||||
logger := log.TestingLogger()
|
||||
clientCreator := abciclient.NewRemoteCreator(logger, sockPath, SOCKET, true)
|
||||
client, err := abciclient.NewClient(logger, sockPath, SOCKET, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -102,12 +113,9 @@ func BenchmarkEcho(b *testing.B) {
|
||||
b.Cleanup(func() { cancel(); s.Wait() })
|
||||
|
||||
// Start client
|
||||
cli, err := clientCreator(logger.With("module", "abci-client"))
|
||||
require.NoError(b, err, "Error creating ABCI client")
|
||||
require.NoError(b, client.Start(ctx), "Error starting ABCI client")
|
||||
|
||||
require.NoError(b, cli.Start(ctx), "Error starting ABCI client")
|
||||
|
||||
proxy := newAppConnTest(cli)
|
||||
proxy := newAppConnTest(client)
|
||||
b.Log("Connected")
|
||||
echoString := strings.Repeat(" ", 200)
|
||||
b.StartTimer() // Start benchmarking tests
|
||||
@@ -139,7 +147,10 @@ func TestInfo(t *testing.T) {
|
||||
|
||||
sockPath := fmt.Sprintf("unix:///tmp/echo_%v.sock", tmrand.Str(6))
|
||||
logger := log.TestingLogger()
|
||||
clientCreator := abciclient.NewRemoteCreator(logger, sockPath, SOCKET, true)
|
||||
client, err := abciclient.NewClient(logger, sockPath, SOCKET, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Start server
|
||||
s := server.NewSocketServer(logger.With("module", "abci-server"), sockPath, kvstore.NewApplication())
|
||||
@@ -147,12 +158,9 @@ func TestInfo(t *testing.T) {
|
||||
t.Cleanup(func() { cancel(); s.Wait() })
|
||||
|
||||
// Start client
|
||||
cli, err := clientCreator(logger.With("module", "abci-client"))
|
||||
require.NoError(t, err, "Error creating ABCI client")
|
||||
require.NoError(t, client.Start(ctx), "Error starting ABCI client")
|
||||
|
||||
require.NoError(t, cli.Start(ctx), "Error starting ABCI client")
|
||||
|
||||
proxy := newAppConnTest(cli)
|
||||
proxy := newAppConnTest(client)
|
||||
t.Log("Connected")
|
||||
|
||||
resInfo, err := proxy.Info(ctx, RequestInfo)
|
||||
@@ -162,3 +170,65 @@ func TestInfo(t *testing.T) {
|
||||
t.Error("Expected ResponseInfo with one element '{\"size\":0}' but got something else")
|
||||
}
|
||||
}
|
||||
|
||||
type noopStoppableClientImpl struct {
|
||||
abciclient.Client
|
||||
count int
|
||||
}
|
||||
|
||||
func (c *noopStoppableClientImpl) Stop() { c.count++ }
|
||||
|
||||
func TestAppConns_Start_Stop(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
clientMock := &abcimocks.Client{}
|
||||
clientMock.On("Start", mock.Anything).Return(nil)
|
||||
clientMock.On("Error").Return(nil)
|
||||
clientMock.On("IsRunning").Return(true)
|
||||
clientMock.On("Wait").Return(nil).Times(1)
|
||||
cl := &noopStoppableClientImpl{Client: clientMock}
|
||||
|
||||
appConns := New(cl, log.TestingLogger(), NopMetrics())
|
||||
|
||||
err := appConns.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
cancel()
|
||||
appConns.Wait()
|
||||
|
||||
clientMock.AssertExpectations(t)
|
||||
assert.Equal(t, 1, cl.count)
|
||||
}
|
||||
|
||||
// Upon failure, we call tmos.Kill
|
||||
func TestAppConns_Failure(t *testing.T) {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGTERM, syscall.SIGABRT)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
clientMock := &abcimocks.Client{}
|
||||
clientMock.On("SetLogger", mock.Anything).Return()
|
||||
clientMock.On("Start", mock.Anything).Return(nil)
|
||||
clientMock.On("IsRunning").Return(true)
|
||||
clientMock.On("Wait").Return(nil)
|
||||
clientMock.On("Error").Return(errors.New("EOF"))
|
||||
cl := &noopStoppableClientImpl{Client: clientMock}
|
||||
|
||||
appConns := New(cl, log.TestingLogger(), NopMetrics())
|
||||
|
||||
err := appConns.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { cancel(); appConns.Wait() })
|
||||
|
||||
select {
|
||||
case sig := <-c:
|
||||
t.Logf("signal %q successfully received", sig)
|
||||
case <-ctx.Done():
|
||||
t.Fatal("expected process to receive SIGTERM signal")
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/libs/service"
|
||||
)
|
||||
|
||||
// AppConns is the Tendermint's interface to the application that consists of
|
||||
// multiple connections.
|
||||
type AppConns interface {
|
||||
service.Service
|
||||
|
||||
// Mempool connection
|
||||
Mempool() AppConnMempool
|
||||
// Consensus connection
|
||||
Consensus() AppConnConsensus
|
||||
// Query connection
|
||||
Query() AppConnQuery
|
||||
// Snapshot connection
|
||||
Snapshot() AppConnSnapshot
|
||||
}
|
||||
|
||||
// NewAppConns calls NewMultiAppConn.
|
||||
func NewAppConns(clientCreator abciclient.Creator, logger log.Logger, metrics *Metrics) AppConns {
|
||||
return NewMultiAppConn(clientCreator, logger, metrics)
|
||||
}
|
||||
|
||||
// multiAppConn implements AppConns.
|
||||
//
|
||||
// A multiAppConn is made of a few appConns and manages their underlying abci
|
||||
// clients.
|
||||
// TODO: on app restart, clients must reboot together
|
||||
type multiAppConn struct {
|
||||
service.BaseService
|
||||
logger log.Logger
|
||||
|
||||
metrics *Metrics
|
||||
consensusConn AppConnConsensus
|
||||
mempoolConn AppConnMempool
|
||||
queryConn AppConnQuery
|
||||
snapshotConn AppConnSnapshot
|
||||
|
||||
client stoppableClient
|
||||
|
||||
clientCreator abciclient.Creator
|
||||
}
|
||||
|
||||
// TODO: this is a totally internal and quasi permanent shim for
|
||||
// clients. eventually we can have a single client and have some kind
|
||||
// of reasonable lifecycle witout needing an explicit stop method.
|
||||
type stoppableClient interface {
|
||||
abciclient.Client
|
||||
Stop()
|
||||
}
|
||||
|
||||
// NewMultiAppConn makes all necessary abci connections to the application.
|
||||
func NewMultiAppConn(clientCreator abciclient.Creator, logger log.Logger, metrics *Metrics) AppConns {
|
||||
multiAppConn := &multiAppConn{
|
||||
logger: logger,
|
||||
metrics: metrics,
|
||||
clientCreator: clientCreator,
|
||||
}
|
||||
multiAppConn.BaseService = *service.NewBaseService(logger, "multiAppConn", multiAppConn)
|
||||
return multiAppConn
|
||||
}
|
||||
|
||||
func (app *multiAppConn) Mempool() AppConnMempool { return app.mempoolConn }
|
||||
func (app *multiAppConn) Consensus() AppConnConsensus { return app.consensusConn }
|
||||
func (app *multiAppConn) Query() AppConnQuery { return app.queryConn }
|
||||
func (app *multiAppConn) Snapshot() AppConnSnapshot { return app.snapshotConn }
|
||||
|
||||
func (app *multiAppConn) OnStart(ctx context.Context) error {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
app.client.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
var client abciclient.Client
|
||||
client, err = app.clientCreator(app.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app.queryConn = NewAppConnQuery(client, app.metrics)
|
||||
app.snapshotConn = NewAppConnSnapshot(client, app.metrics)
|
||||
app.mempoolConn = NewAppConnMempool(client, app.metrics)
|
||||
app.consensusConn = NewAppConnConsensus(client, app.metrics)
|
||||
|
||||
app.client = client.(stoppableClient)
|
||||
|
||||
// Kill Tendermint if the ABCI application crashes.
|
||||
go func() {
|
||||
if !client.IsRunning() {
|
||||
return
|
||||
}
|
||||
app.client.Wait()
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.client.Error(); err != nil {
|
||||
app.logger.Error("client connection terminated. Did the application crash? Please restart tendermint",
|
||||
"err", err)
|
||||
if killErr := kill(); killErr != nil {
|
||||
app.logger.Error("Failed to kill this process - please do so manually",
|
||||
"err", killErr)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return client.Start(ctx)
|
||||
}
|
||||
|
||||
func (app *multiAppConn) OnStop() { app.client.Stop() }
|
||||
|
||||
func kill() error {
|
||||
p, err := os.FindProcess(os.Getpid())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Signal(syscall.SIGTERM)
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abcimocks "github.com/tendermint/tendermint/abci/client/mocks"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
type noopStoppableClientImpl struct {
|
||||
abciclient.Client
|
||||
count int
|
||||
}
|
||||
|
||||
func (c *noopStoppableClientImpl) Stop() { c.count++ }
|
||||
|
||||
func TestAppConns_Start_Stop(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
clientMock := &abcimocks.Client{}
|
||||
clientMock.On("Start", mock.Anything).Return(nil)
|
||||
clientMock.On("Error").Return(nil)
|
||||
clientMock.On("IsRunning").Return(true)
|
||||
clientMock.On("Wait").Return(nil).Times(1)
|
||||
cl := &noopStoppableClientImpl{Client: clientMock}
|
||||
|
||||
creatorCallCount := 0
|
||||
creator := func(logger log.Logger) (abciclient.Client, error) {
|
||||
creatorCallCount++
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
appConns := NewAppConns(creator, log.TestingLogger(), NopMetrics())
|
||||
|
||||
err := appConns.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
cancel()
|
||||
appConns.Wait()
|
||||
|
||||
clientMock.AssertExpectations(t)
|
||||
assert.Equal(t, 1, cl.count)
|
||||
assert.Equal(t, 1, creatorCallCount)
|
||||
}
|
||||
|
||||
// Upon failure, we call tmos.Kill
|
||||
func TestAppConns_Failure(t *testing.T) {
|
||||
ok := make(chan struct{})
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGTERM)
|
||||
go func() {
|
||||
for range c {
|
||||
close(ok)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
clientMock := &abcimocks.Client{}
|
||||
clientMock.On("SetLogger", mock.Anything).Return()
|
||||
clientMock.On("Start", mock.Anything).Return(nil)
|
||||
clientMock.On("IsRunning").Return(true)
|
||||
clientMock.On("Wait").Return(nil)
|
||||
clientMock.On("Error").Return(errors.New("EOF"))
|
||||
cl := &noopStoppableClientImpl{Client: clientMock}
|
||||
|
||||
creator := func(log.Logger) (abciclient.Client, error) {
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
appConns := NewAppConns(creator, log.TestingLogger(), NopMetrics())
|
||||
|
||||
err := appConns.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { cancel(); appConns.Wait() })
|
||||
|
||||
select {
|
||||
case <-ok:
|
||||
t.Log("SIGTERM successfully received")
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("expected process to receive SIGTERM signal")
|
||||
}
|
||||
}
|
||||
@@ -153,26 +153,6 @@ func BufferCapacity(cap int) Option {
|
||||
// BufferCapacity returns capacity of the publication queue.
|
||||
func (s *Server) BufferCapacity() int { return cap(s.queue) }
|
||||
|
||||
// Subscribe creates a subscription for the given client ID and query.
|
||||
// If len(capacities) > 0, its first value is used as the queue capacity.
|
||||
//
|
||||
// Deprecated: Use SubscribeWithArgs. This method will be removed in v0.36.
|
||||
func (s *Server) Subscribe(ctx context.Context, clientID string, query *query.Query, capacities ...int) (*Subscription, error) {
|
||||
args := SubscribeArgs{
|
||||
ClientID: clientID,
|
||||
Query: query,
|
||||
Limit: 1,
|
||||
}
|
||||
if len(capacities) > 0 {
|
||||
args.Limit = capacities[0]
|
||||
if len(capacities) > 1 {
|
||||
args.Quota = capacities[1]
|
||||
}
|
||||
// bounds are checked below
|
||||
}
|
||||
return s.SubscribeWithArgs(ctx, args)
|
||||
}
|
||||
|
||||
// Observe registers an observer function that will be called synchronously
|
||||
// with each published message matching any of the given queries, prior to it
|
||||
// being forwarded to any subscriber. If no queries are specified, all
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestScanner(t *testing.T) {
|
||||
got = append(got, s.Token())
|
||||
}
|
||||
if err := s.Err(); err != io.EOF {
|
||||
t.Errorf("Next: unexpected error: %w", err)
|
||||
t.Errorf("Next: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
|
||||
@@ -18,7 +18,7 @@ func (env *Environment) ABCIQuery(
|
||||
height int64,
|
||||
prove bool,
|
||||
) (*coretypes.ResultABCIQuery, error) {
|
||||
resQuery, err := env.ProxyAppQuery.Query(ctx, abci.RequestQuery{
|
||||
resQuery, err := env.ProxyApp.Query(ctx, abci.RequestQuery{
|
||||
Path: path,
|
||||
Data: data,
|
||||
Height: height,
|
||||
@@ -34,7 +34,7 @@ func (env *Environment) ABCIQuery(
|
||||
// ABCIInfo gets some info about the application.
|
||||
// More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_info
|
||||
func (env *Environment) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) {
|
||||
resInfo, err := env.ProxyAppQuery.Info(ctx, proxy.RequestInfo)
|
||||
resInfo, err := env.ProxyApp.Info(ctx, proxy.RequestInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -23,9 +23,7 @@ import (
|
||||
// order (highest first).
|
||||
//
|
||||
// More: https://docs.tendermint.com/master/rpc/#/Info/blockchain
|
||||
func (env *Environment) BlockchainInfo(
|
||||
ctx context.Context,
|
||||
minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
|
||||
func (env *Environment) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
|
||||
|
||||
const limit int64 = 20
|
||||
|
||||
@@ -193,8 +191,6 @@ func (env *Environment) Commit(ctx context.Context, heightPtr *int64) (*coretype
|
||||
// If no height is provided, it will fetch results for the latest block.
|
||||
//
|
||||
// Results are for the height of the block containing the txs.
|
||||
// Thus response.results.deliver_tx[5] is the results of executing
|
||||
// getBlock(h).Txs[5]
|
||||
// More: https://docs.tendermint.com/master/rpc/#/Info/block_results
|
||||
func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlockResults, error) {
|
||||
height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
|
||||
@@ -208,13 +204,13 @@ func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*co
|
||||
}
|
||||
|
||||
var totalGasUsed int64
|
||||
for _, tx := range results.FinalizeBlock.GetTxs() {
|
||||
totalGasUsed += tx.GetGasUsed()
|
||||
for _, res := range results.FinalizeBlock.GetTxResults() {
|
||||
totalGasUsed += res.GetGasUsed()
|
||||
}
|
||||
|
||||
return &coretypes.ResultBlockResults{
|
||||
Height: height,
|
||||
TxsResults: results.FinalizeBlock.Txs,
|
||||
TxsResults: results.FinalizeBlock.TxResults,
|
||||
TotalGasUsed: totalGasUsed,
|
||||
FinalizeBlockEvents: results.FinalizeBlock.Events,
|
||||
ValidatorUpdates: results.FinalizeBlock.ValidatorUpdates,
|
||||
@@ -222,8 +218,8 @@ func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*co
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BlockSearch searches for a paginated set of blocks matching BeginBlock and
|
||||
// EndBlock event search criteria.
|
||||
// BlockSearch searches for a paginated set of blocks matching the provided
|
||||
// query.
|
||||
func (env *Environment) BlockSearch(
|
||||
ctx context.Context,
|
||||
query string,
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestBlockchainInfo(t *testing.T) {
|
||||
func TestBlockResults(t *testing.T) {
|
||||
results := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
Txs: []*abci.ResponseDeliverTx{
|
||||
TxResults: []*abci.ExecTxResult{
|
||||
{Code: 0, Data: []byte{0x01}, Log: "ok", GasUsed: 10},
|
||||
{Code: 0, Data: []byte{0x02}, Log: "ok", GasUsed: 5},
|
||||
{Code: 1, Log: "not ok", GasUsed: 0},
|
||||
@@ -99,7 +99,7 @@ func TestBlockResults(t *testing.T) {
|
||||
{101, true, nil},
|
||||
{100, false, &coretypes.ResultBlockResults{
|
||||
Height: 100,
|
||||
TxsResults: results.FinalizeBlock.Txs,
|
||||
TxsResults: results.FinalizeBlock.TxResults,
|
||||
TotalGasUsed: 15,
|
||||
FinalizeBlockEvents: results.FinalizeBlock.Events,
|
||||
ValidatorUpdates: results.FinalizeBlock.ValidatorUpdates,
|
||||
|
||||
@@ -14,10 +14,7 @@ import (
|
||||
// for the validators in the set as used in computing their Merkle root.
|
||||
//
|
||||
// More: https://docs.tendermint.com/master/rpc/#/Info/validators
|
||||
func (env *Environment) Validators(
|
||||
ctx context.Context,
|
||||
heightPtr *int64,
|
||||
pagePtr, perPagePtr *int) (*coretypes.ResultValidators, error) {
|
||||
func (env *Environment) Validators(ctx context.Context, heightPtr *int64, pagePtr, perPagePtr *int) (*coretypes.ResultValidators, error) {
|
||||
|
||||
// The latest validator that we know is the NextValidator of the last block.
|
||||
height, err := env.getHeight(env.latestUncommittedHeight(), heightPtr)
|
||||
@@ -86,7 +83,8 @@ func (env *Environment) DumpConsensusState(ctx context.Context) (*coretypes.Resu
|
||||
}
|
||||
return &coretypes.ResultDumpConsensusState{
|
||||
RoundState: roundState,
|
||||
Peers: peerStates}, nil
|
||||
Peers: peerStates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ConsensusState returns a concise summary of the consensus state.
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/rs/cors"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/internal/blocksync"
|
||||
@@ -19,7 +20,6 @@ import (
|
||||
"github.com/tendermint/tendermint/internal/eventlog"
|
||||
"github.com/tendermint/tendermint/internal/mempool"
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
tmpubsub "github.com/tendermint/tendermint/internal/pubsub"
|
||||
"github.com/tendermint/tendermint/internal/pubsub/query"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
@@ -57,12 +57,6 @@ type consensusState interface {
|
||||
GetRoundStateSimpleJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
type transport interface {
|
||||
Listeners() []string
|
||||
IsListening() bool
|
||||
NodeInfo() types.NodeInfo
|
||||
}
|
||||
|
||||
type peerManager interface {
|
||||
Peers() []types.NodeID
|
||||
Addresses(types.NodeID) []p2p.NodeAddress
|
||||
@@ -73,8 +67,7 @@ type peerManager interface {
|
||||
// to be setup once during startup.
|
||||
type Environment struct {
|
||||
// external, thread safe interfaces
|
||||
ProxyAppQuery proxy.AppConnQuery
|
||||
ProxyAppMempool proxy.AppConnMempool
|
||||
ProxyApp abciclient.Client
|
||||
|
||||
// interfaces defined in types and above
|
||||
StateStore sm.Store
|
||||
@@ -84,8 +77,9 @@ type Environment struct {
|
||||
ConsensusReactor *consensus.Reactor
|
||||
BlockSyncReactor *blocksync.Reactor
|
||||
|
||||
// Legacy p2p stack
|
||||
P2PTransport transport
|
||||
IsListening bool
|
||||
Listeners []string
|
||||
NodeInfo types.NodeInfo
|
||||
|
||||
// interfaces for new p2p interfaces
|
||||
PeerManager peerManager
|
||||
@@ -226,6 +220,10 @@ func (env *Environment) StartService(ctx context.Context, conf *config.Config) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env.Listeners = []string{
|
||||
fmt.Sprintf("Listener(@%v)", conf.P2P.ExternalAddress),
|
||||
}
|
||||
|
||||
listenAddrs := strings.SplitAndTrimEmpty(conf.RPC.ListenAddress, ",", " ")
|
||||
routes := NewRoutesMap(env, &RouteOptions{
|
||||
Unsafe: conf.RPC.Unsafe,
|
||||
|
||||
@@ -165,8 +165,11 @@ func (env *Environment) Events(ctx context.Context,
|
||||
maxItems = 100
|
||||
}
|
||||
|
||||
const minWaitTime = 1 * time.Second
|
||||
const maxWaitTime = 30 * time.Second
|
||||
if waitTime > maxWaitTime {
|
||||
if waitTime < minWaitTime {
|
||||
waitTime = minWaitTime
|
||||
} else if waitTime > maxWaitTime {
|
||||
waitTime = maxWaitTime
|
||||
}
|
||||
|
||||
@@ -185,7 +188,7 @@ func (env *Environment) Events(ctx context.Context,
|
||||
accept := func(itm *eventlog.Item) error {
|
||||
// N.B. We accept up to one item more than requested, so we can tell how
|
||||
// to set the "more" flag in the response.
|
||||
if len(items) > maxItems {
|
||||
if len(items) > maxItems || itm.Cursor.Before(after) {
|
||||
return eventlog.ErrStopScan
|
||||
}
|
||||
if cursorInRange(itm.Cursor, before, after) && query.Matches(itm.Events) {
|
||||
@@ -194,7 +197,7 @@ func (env *Environment) Events(ctx context.Context,
|
||||
return nil
|
||||
}
|
||||
|
||||
if waitTime > 0 && before.IsZero() {
|
||||
if before.IsZero() {
|
||||
ctx, cancel := context.WithTimeout(ctx, waitTime)
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -114,10 +114,10 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co
|
||||
}
|
||||
|
||||
return &coretypes.ResultBroadcastTxCommit{
|
||||
CheckTx: *r,
|
||||
DeliverTx: txres.TxResult,
|
||||
Hash: tx.Hash(),
|
||||
Height: txres.Height,
|
||||
CheckTx: *r,
|
||||
TxResult: txres.TxResult,
|
||||
Hash: tx.Hash(),
|
||||
Height: txres.Height,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ func (env *Environment) NumUnconfirmedTxs(ctx context.Context) (*coretypes.Resul
|
||||
// be added to the mempool either.
|
||||
// More: https://docs.tendermint.com/master/rpc/#/Tx/check_tx
|
||||
func (env *Environment) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) {
|
||||
res, err := env.ProxyAppMempool.CheckTx(ctx, abci.RequestCheckTx{Tx: tx})
|
||||
res, err := env.ProxyApp.CheckTx(ctx, abci.RequestCheckTx{Tx: tx})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ func (env *Environment) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo,
|
||||
}
|
||||
|
||||
return &coretypes.ResultNetInfo{
|
||||
Listening: env.P2PTransport.IsListening(),
|
||||
Listeners: env.P2PTransport.Listeners(),
|
||||
Listening: env.IsListening,
|
||||
Listeners: env.Listeners,
|
||||
NPeers: len(peers),
|
||||
Peers: peers,
|
||||
}, nil
|
||||
|
||||
@@ -66,7 +66,7 @@ func (env *Environment) Status(ctx context.Context) (*coretypes.ResultStatus, er
|
||||
}
|
||||
|
||||
result := &coretypes.ResultStatus{
|
||||
NodeInfo: env.P2PTransport.NodeInfo(),
|
||||
NodeInfo: env.NodeInfo,
|
||||
ApplicationInfo: applicationInfo,
|
||||
SyncInfo: coretypes.SyncInfo{
|
||||
LatestBlockHash: latestBlockHash,
|
||||
|
||||
@@ -36,19 +36,16 @@ func (env *Environment) Tx(ctx context.Context, hash bytes.HexBytes, prove bool)
|
||||
return nil, fmt.Errorf("tx (%X) not found, err: %w", hash, err)
|
||||
}
|
||||
|
||||
height := r.Height
|
||||
index := r.Index
|
||||
|
||||
var proof types.TxProof
|
||||
if prove {
|
||||
block := env.BlockStore.LoadBlock(height)
|
||||
proof = block.Data.Txs.Proof(int(index)) // XXX: overflow on 32-bit machines
|
||||
block := env.BlockStore.LoadBlock(r.Height)
|
||||
proof = block.Data.Txs.Proof(int(r.Index))
|
||||
}
|
||||
|
||||
return &coretypes.ResultTx{
|
||||
Hash: hash,
|
||||
Height: height,
|
||||
Index: index,
|
||||
Height: r.Height,
|
||||
Index: r.Index,
|
||||
TxResult: r.Result,
|
||||
Tx: r.Tx,
|
||||
Proof: proof,
|
||||
@@ -127,7 +124,7 @@ func (env *Environment) TxSearch(
|
||||
var proof types.TxProof
|
||||
if prove {
|
||||
block := env.BlockStore.LoadBlock(r.Height)
|
||||
proof = block.Data.Txs.Proof(int(r.Index)) // XXX: overflow on 32-bit machines
|
||||
proof = block.Data.Txs.Proof(int(r.Index))
|
||||
}
|
||||
|
||||
apiResults = append(apiResults, &coretypes.ResultTx{
|
||||
|
||||
+131
-123
@@ -2,17 +2,18 @@ package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/encoding"
|
||||
"github.com/tendermint/tendermint/crypto/merkle"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
"github.com/tendermint/tendermint/internal/mempool"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
|
||||
tmtypes "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ type BlockExecutor struct {
|
||||
blockStore BlockStore
|
||||
|
||||
// execute the app against this
|
||||
proxyApp proxy.AppConnConsensus
|
||||
appClient abciclient.Client
|
||||
|
||||
// events
|
||||
eventBus types.BlockEventPublisher
|
||||
@@ -60,16 +61,17 @@ func BlockExecutorWithMetrics(metrics *Metrics) BlockExecutorOption {
|
||||
func NewBlockExecutor(
|
||||
stateStore Store,
|
||||
logger log.Logger,
|
||||
proxyApp proxy.AppConnConsensus,
|
||||
appClient abciclient.Client,
|
||||
pool mempool.Mempool,
|
||||
evpool EvidencePool,
|
||||
blockStore BlockStore,
|
||||
eventBus *eventbus.EventBus,
|
||||
options ...BlockExecutorOption,
|
||||
) *BlockExecutor {
|
||||
res := &BlockExecutor{
|
||||
eventBus: eventBus,
|
||||
store: stateStore,
|
||||
proxyApp: proxyApp,
|
||||
eventBus: eventbus.NopEventBus{},
|
||||
appClient: appClient,
|
||||
mempool: pool,
|
||||
evpool: evpool,
|
||||
logger: logger,
|
||||
@@ -89,12 +91,6 @@ func (blockExec *BlockExecutor) Store() Store {
|
||||
return blockExec.store
|
||||
}
|
||||
|
||||
// SetEventBus - sets the event bus for publishing block related events.
|
||||
// If not called, it defaults to types.NopEventBus.
|
||||
func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) {
|
||||
blockExec.eventBus = eventBus
|
||||
}
|
||||
|
||||
// CreateProposalBlock calls state.MakeBlock with evidence from the evpool
|
||||
// and txs from the mempool. The max bytes must be big enough to fit the commit.
|
||||
// Up to 1/10th of the block space is allcoated for maximum sized evidence.
|
||||
@@ -104,10 +100,11 @@ func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher)
|
||||
func (blockExec *BlockExecutor) CreateProposalBlock(
|
||||
ctx context.Context,
|
||||
height int64,
|
||||
state State, commit *types.Commit,
|
||||
state State,
|
||||
commit *types.Commit,
|
||||
proposerAddr []byte,
|
||||
votes []*types.Vote,
|
||||
) (*types.Block, *types.PartSet, error) {
|
||||
) (*types.Block, error) {
|
||||
|
||||
maxBytes := state.ConsensusParams.Block.MaxBytes
|
||||
maxGas := state.ConsensusParams.Block.MaxGas
|
||||
@@ -118,13 +115,18 @@ func (blockExec *BlockExecutor) CreateProposalBlock(
|
||||
maxDataBytes := types.MaxDataBytes(maxBytes, evSize, state.Validators.Size())
|
||||
|
||||
txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas)
|
||||
block := state.MakeBlock(height, txs, commit, evidence, proposerAddr)
|
||||
|
||||
preparedProposal, err := blockExec.proxyApp.PrepareProposal(
|
||||
localLastCommit := buildLastCommitInfo(block, blockExec.store, state.InitialHeight)
|
||||
rpp, err := blockExec.appClient.PrepareProposal(
|
||||
ctx,
|
||||
abci.RequestPrepareProposal{
|
||||
BlockData: txs.ToSliceOfBytes(),
|
||||
BlockDataSize: maxDataBytes,
|
||||
Votes: types.VotesToProto(votes),
|
||||
Hash: block.Hash(),
|
||||
Header: *block.Header.ToProto(),
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
LocalLastCommit: extendedCommitInfo(localLastCommit, votes),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
MaxTxBytes: maxDataBytes,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -138,19 +140,28 @@ func (blockExec *BlockExecutor) CreateProposalBlock(
|
||||
// purpose for now.
|
||||
panic(err)
|
||||
}
|
||||
newTxs := preparedProposal.GetBlockData()
|
||||
var txSize int
|
||||
for _, tx := range newTxs {
|
||||
txSize += len(tx)
|
||||
|
||||
if maxDataBytes < int64(txSize) {
|
||||
panic("block data exceeds max amount of allowed bytes")
|
||||
}
|
||||
if !rpp.ModifiedTx {
|
||||
return block, nil
|
||||
}
|
||||
txrSet := types.NewTxRecordSet(rpp.TxRecords)
|
||||
|
||||
if err := txrSet.Validate(maxDataBytes, block.Txs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modifiedTxs := types.ToTxs(preparedProposal.GetBlockData())
|
||||
|
||||
return state.MakeBlock(height, modifiedTxs, commit, evidence, proposerAddr)
|
||||
for _, rtx := range txrSet.RemovedTxs() {
|
||||
if err := blockExec.mempool.RemoveTxByKey(rtx.Key()); err != nil {
|
||||
blockExec.logger.Debug("error removing transaction from the mempool", "error", err, "tx hash", rtx.Hash())
|
||||
}
|
||||
}
|
||||
for _, atx := range txrSet.AddedTxs() {
|
||||
if err := blockExec.mempool.CheckTx(ctx, atx, nil, mempool.TxInfo{}); err != nil {
|
||||
blockExec.logger.Error("error adding tx to the mempool", "error", err, "tx hash", atx.Hash())
|
||||
}
|
||||
}
|
||||
itxs := txrSet.IncludedTxs()
|
||||
return state.MakeBlock(height, itxs, commit, evidence, proposerAddr), nil
|
||||
}
|
||||
|
||||
func (blockExec *BlockExecutor) ProcessProposal(
|
||||
@@ -162,11 +173,11 @@ func (blockExec *BlockExecutor) ProcessProposal(
|
||||
Hash: block.Header.Hash(),
|
||||
Header: *block.Header.ToProto(),
|
||||
Txs: block.Data.Txs.ToSliceOfBytes(),
|
||||
LastCommitInfo: buildLastCommitInfo(block, blockExec.store, state.InitialHeight),
|
||||
ProposedLastCommit: buildLastCommitInfo(block, blockExec.store, state.InitialHeight),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
}
|
||||
|
||||
resp, err := blockExec.proxyApp.ProcessProposal(ctx, req)
|
||||
resp, err := blockExec.appClient.ProcessProposal(ctx, req)
|
||||
if err != nil {
|
||||
return false, ErrInvalidBlock(err)
|
||||
}
|
||||
@@ -207,18 +218,22 @@ func (blockExec *BlockExecutor) ValidateBlock(ctx context.Context, state State,
|
||||
func (blockExec *BlockExecutor) ApplyBlock(
|
||||
ctx context.Context,
|
||||
state State,
|
||||
blockID types.BlockID,
|
||||
block *types.Block,
|
||||
) (State, error) {
|
||||
|
||||
blockID types.BlockID, block *types.Block) (State, error) {
|
||||
// validate the block if we haven't already
|
||||
if err := blockExec.ValidateBlock(ctx, state, block); err != nil {
|
||||
return state, ErrInvalidBlock(err)
|
||||
}
|
||||
|
||||
startTime := time.Now().UnixNano()
|
||||
abciResponses, err := execBlockOnProxyApp(ctx,
|
||||
blockExec.logger, blockExec.proxyApp, block, blockExec.store, state.InitialHeight,
|
||||
pbh := block.Header.ToProto()
|
||||
finalizeBlockResponse, err := blockExec.appClient.FinalizeBlock(
|
||||
ctx,
|
||||
abci.RequestFinalizeBlock{
|
||||
Hash: block.Hash(),
|
||||
Header: *pbh,
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
DecidedLastCommit: buildLastCommitInfo(block, blockExec.store, state.InitialHeight),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
},
|
||||
)
|
||||
endTime := time.Now().UnixNano()
|
||||
blockExec.metrics.BlockProcessingTime.Observe(float64(endTime-startTime) / 1000000)
|
||||
@@ -226,19 +241,22 @@ func (blockExec *BlockExecutor) ApplyBlock(
|
||||
return state, ErrProxyAppConn(err)
|
||||
}
|
||||
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: finalizeBlockResponse,
|
||||
}
|
||||
|
||||
// Save the results before we commit.
|
||||
if err := blockExec.store.SaveABCIResponses(block.Height, abciResponses); err != nil {
|
||||
return state, err
|
||||
}
|
||||
|
||||
// validate the validator updates and convert to tendermint types
|
||||
abciValUpdates := abciResponses.FinalizeBlock.ValidatorUpdates
|
||||
err = validateValidatorUpdates(abciValUpdates, state.ConsensusParams.Validator)
|
||||
err = validateValidatorUpdates(finalizeBlockResponse.ValidatorUpdates, state.ConsensusParams.Validator)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("error in validator updates: %w", err)
|
||||
}
|
||||
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciValUpdates)
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(finalizeBlockResponse.ValidatorUpdates)
|
||||
if err != nil {
|
||||
return state, err
|
||||
}
|
||||
@@ -247,13 +265,18 @@ func (blockExec *BlockExecutor) ApplyBlock(
|
||||
}
|
||||
|
||||
// Update the state with the block and responses.
|
||||
state, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(finalizeBlockResponse.TxResults)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("marshaling TxResults: %w", err)
|
||||
}
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
state, err = state.Update(blockID, &block.Header, h, finalizeBlockResponse.ConsensusParamUpdates, validatorUpdates)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("commit failed for application: %w", err)
|
||||
}
|
||||
|
||||
// Lock mempool, commit app state, update mempoool.
|
||||
appHash, retainHeight, err := blockExec.Commit(ctx, state, block, abciResponses.FinalizeBlock.Txs)
|
||||
appHash, retainHeight, err := blockExec.Commit(ctx, state, block, finalizeBlockResponse.TxResults)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("commit failed for application: %w", err)
|
||||
}
|
||||
@@ -282,7 +305,7 @@ func (blockExec *BlockExecutor) ApplyBlock(
|
||||
|
||||
// Events are fired after everything else.
|
||||
// NOTE: if we crash between Commit and Save, events wont be fired during replay
|
||||
fireEvents(ctx, blockExec.logger, blockExec.eventBus, block, blockID, abciResponses, validatorUpdates)
|
||||
fireEvents(ctx, blockExec.logger, blockExec.eventBus, block, blockID, finalizeBlockResponse, validatorUpdates)
|
||||
|
||||
return state, nil
|
||||
}
|
||||
@@ -292,7 +315,7 @@ func (blockExec *BlockExecutor) ExtendVote(ctx context.Context, vote *types.Vote
|
||||
Vote: vote.ToProto(),
|
||||
}
|
||||
|
||||
resp, err := blockExec.proxyApp.ExtendVote(ctx, req)
|
||||
resp, err := blockExec.appClient.ExtendVote(ctx, req)
|
||||
if err != nil {
|
||||
return types.VoteExtension{}, err
|
||||
}
|
||||
@@ -304,7 +327,7 @@ func (blockExec *BlockExecutor) VerifyVoteExtension(ctx context.Context, vote *t
|
||||
Vote: vote.ToProto(),
|
||||
}
|
||||
|
||||
resp, err := blockExec.proxyApp.VerifyVoteExtension(ctx, req)
|
||||
resp, err := blockExec.appClient.VerifyVoteExtension(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -326,7 +349,7 @@ func (blockExec *BlockExecutor) Commit(
|
||||
ctx context.Context,
|
||||
state State,
|
||||
block *types.Block,
|
||||
deliverTxResponses []*abci.ResponseDeliverTx,
|
||||
txResults []*abci.ExecTxResult,
|
||||
) ([]byte, int64, error) {
|
||||
blockExec.mempool.Lock()
|
||||
defer blockExec.mempool.Unlock()
|
||||
@@ -340,7 +363,7 @@ func (blockExec *BlockExecutor) Commit(
|
||||
}
|
||||
|
||||
// Commit block, get hash back
|
||||
res, err := blockExec.proxyApp.Commit(ctx)
|
||||
res, err := blockExec.appClient.Commit(ctx)
|
||||
if err != nil {
|
||||
blockExec.logger.Error("client error during proxyAppConn.Commit", "err", err)
|
||||
return nil, 0, err
|
||||
@@ -359,63 +382,19 @@ func (blockExec *BlockExecutor) Commit(
|
||||
ctx,
|
||||
block.Height,
|
||||
block.Txs,
|
||||
deliverTxResponses,
|
||||
TxPreCheck(state),
|
||||
TxPostCheck(state),
|
||||
txResults,
|
||||
TxPreCheckForState(state),
|
||||
TxPostCheckForState(state),
|
||||
)
|
||||
|
||||
return res.Data, res.RetainHeight, err
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// Helper functions for executing blocks and updating state
|
||||
|
||||
// Executes block's transactions on proxyAppConn.
|
||||
// Returns a list of transaction results and updates to the validator set
|
||||
func execBlockOnProxyApp(
|
||||
ctx context.Context,
|
||||
logger log.Logger,
|
||||
proxyAppConn proxy.AppConnConsensus,
|
||||
block *types.Block,
|
||||
store Store,
|
||||
initialHeight int64,
|
||||
) (*tmstate.ABCIResponses, error) {
|
||||
abciResponses := new(tmstate.ABCIResponses)
|
||||
abciResponses.FinalizeBlock = &abci.ResponseFinalizeBlock{}
|
||||
dtxs := make([]*abci.ResponseDeliverTx, len(block.Txs))
|
||||
abciResponses.FinalizeBlock.Txs = dtxs
|
||||
|
||||
// Begin block
|
||||
var err error
|
||||
pbh := block.Header.ToProto()
|
||||
if pbh == nil {
|
||||
return nil, errors.New("nil header")
|
||||
}
|
||||
|
||||
abciResponses.FinalizeBlock, err = proxyAppConn.FinalizeBlock(
|
||||
ctx,
|
||||
abci.RequestFinalizeBlock{
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
Hash: block.Hash(),
|
||||
Header: *pbh,
|
||||
Height: block.Height,
|
||||
LastCommitInfo: buildLastCommitInfo(block, store, initialHeight),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("error in proxyAppConn.FinalizeBlock", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("executed block", "height", block.Height)
|
||||
return abciResponses, nil
|
||||
}
|
||||
|
||||
func buildLastCommitInfo(block *types.Block, store Store, initialHeight int64) abci.LastCommitInfo {
|
||||
func buildLastCommitInfo(block *types.Block, store Store, initialHeight int64) abci.CommitInfo {
|
||||
if block.Height == initialHeight {
|
||||
// there is no last commmit for the initial height.
|
||||
// return an empty value.
|
||||
return abci.LastCommitInfo{}
|
||||
return abci.CommitInfo{}
|
||||
}
|
||||
|
||||
lastValSet, err := store.LoadValidators(block.Height - 1)
|
||||
@@ -446,12 +425,30 @@ func buildLastCommitInfo(block *types.Block, store Store, initialHeight int64) a
|
||||
}
|
||||
}
|
||||
|
||||
return abci.LastCommitInfo{
|
||||
return abci.CommitInfo{
|
||||
Round: block.LastCommit.Round,
|
||||
Votes: votes,
|
||||
}
|
||||
}
|
||||
|
||||
func extendedCommitInfo(c abci.CommitInfo, votes []*types.Vote) abci.ExtendedCommitInfo {
|
||||
vs := make([]abci.ExtendedVoteInfo, len(c.Votes))
|
||||
for i := range vs {
|
||||
vs[i] = abci.ExtendedVoteInfo{
|
||||
Validator: c.Votes[i].Validator,
|
||||
SignedLastBlock: c.Votes[i].SignedLastBlock,
|
||||
/*
|
||||
TODO: Include vote extensions information when implementing vote extensions.
|
||||
VoteExtension: []byte{},
|
||||
*/
|
||||
}
|
||||
}
|
||||
return abci.ExtendedCommitInfo{
|
||||
Round: c.Round,
|
||||
Votes: vs,
|
||||
}
|
||||
}
|
||||
|
||||
func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate,
|
||||
params types.ValidatorParams) error {
|
||||
for _, valUpdate := range abciUpdates {
|
||||
@@ -477,16 +474,16 @@ func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate,
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateState returns a new State updated according to the header and responses.
|
||||
func updateState(
|
||||
state State,
|
||||
// Update returns a copy of state with the fields set using the arguments passed in.
|
||||
func (state State) Update(
|
||||
blockID types.BlockID,
|
||||
header *types.Header,
|
||||
abciResponses *tmstate.ABCIResponses,
|
||||
resultsHash []byte,
|
||||
consensusParamUpdates *tmtypes.ConsensusParams,
|
||||
validatorUpdates []*types.Validator,
|
||||
) (State, error) {
|
||||
|
||||
// Copy the valset so we can apply changes from EndBlock
|
||||
// Copy the valset so we can apply changes from FinalizeBlock
|
||||
// and update s.LastValidators and s.Validators.
|
||||
nValSet := state.NextValidators.Copy()
|
||||
|
||||
@@ -507,9 +504,9 @@ func updateState(
|
||||
// Update the params with the latest abciResponses.
|
||||
nextParams := state.ConsensusParams
|
||||
lastHeightParamsChanged := state.LastHeightConsensusParamsChanged
|
||||
if abciResponses.FinalizeBlock.ConsensusParamUpdates != nil {
|
||||
// NOTE: must not mutate s.ConsensusParams
|
||||
nextParams = state.ConsensusParams.UpdateConsensusParams(abciResponses.FinalizeBlock.ConsensusParamUpdates)
|
||||
if consensusParamUpdates != nil {
|
||||
// NOTE: must not mutate state.ConsensusParams
|
||||
nextParams = state.ConsensusParams.UpdateConsensusParams(consensusParamUpdates)
|
||||
err := nextParams.ValidateConsensusParams()
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("error updating consensus params: %w", err)
|
||||
@@ -538,7 +535,7 @@ func updateState(
|
||||
LastHeightValidatorsChanged: lastHeightValsChanged,
|
||||
ConsensusParams: nextParams,
|
||||
LastHeightConsensusParamsChanged: lastHeightParamsChanged,
|
||||
LastResultsHash: ABCIResponsesResultsHash(abciResponses),
|
||||
LastResultsHash: resultsHash,
|
||||
AppHash: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -552,13 +549,13 @@ func fireEvents(
|
||||
eventBus types.BlockEventPublisher,
|
||||
block *types.Block,
|
||||
blockID types.BlockID,
|
||||
abciResponses *tmstate.ABCIResponses,
|
||||
finalizeBlockResponse *abci.ResponseFinalizeBlock,
|
||||
validatorUpdates []*types.Validator,
|
||||
) {
|
||||
if err := eventBus.PublishEventNewBlock(ctx, types.EventDataNewBlock{
|
||||
Block: block,
|
||||
BlockID: blockID,
|
||||
ResultFinalizeBlock: *abciResponses.FinalizeBlock,
|
||||
ResultFinalizeBlock: *finalizeBlockResponse,
|
||||
}); err != nil {
|
||||
logger.Error("failed publishing new block", "err", err)
|
||||
}
|
||||
@@ -566,7 +563,7 @@ func fireEvents(
|
||||
if err := eventBus.PublishEventNewBlockHeader(ctx, types.EventDataNewBlockHeader{
|
||||
Header: block.Header,
|
||||
NumTxs: int64(len(block.Txs)),
|
||||
ResultFinalizeBlock: *abciResponses.FinalizeBlock,
|
||||
ResultFinalizeBlock: *finalizeBlockResponse,
|
||||
}); err != nil {
|
||||
logger.Error("failed publishing new block header", "err", err)
|
||||
}
|
||||
@@ -583,9 +580,9 @@ func fireEvents(
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if len(abciResponses.FinalizeBlock.Txs) != len(block.Data.Txs) {
|
||||
if len(finalizeBlockResponse.TxResults) != len(block.Data.Txs) {
|
||||
panic(fmt.Sprintf("number of TXs (%d) and ABCI TX responses (%d) do not match",
|
||||
len(block.Data.Txs), len(abciResponses.FinalizeBlock.Txs)))
|
||||
len(block.Data.Txs), len(finalizeBlockResponse.TxResults)))
|
||||
}
|
||||
|
||||
for i, tx := range block.Data.Txs {
|
||||
@@ -594,14 +591,14 @@ func fireEvents(
|
||||
Height: block.Height,
|
||||
Index: uint32(i),
|
||||
Tx: tx,
|
||||
Result: *(abciResponses.FinalizeBlock.Txs[i]),
|
||||
Result: *(finalizeBlockResponse.TxResults[i]),
|
||||
},
|
||||
}); err != nil {
|
||||
logger.Error("failed publishing event TX", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(validatorUpdates) > 0 {
|
||||
if len(finalizeBlockResponse.ValidatorUpdates) > 0 {
|
||||
if err := eventBus.PublishEventValidatorSetUpdates(ctx,
|
||||
types.EventDataValidatorSetUpdates{ValidatorUpdates: validatorUpdates}); err != nil {
|
||||
logger.Error("failed publishing event", "err", err)
|
||||
@@ -617,30 +614,41 @@ func fireEvents(
|
||||
func ExecCommitBlock(
|
||||
ctx context.Context,
|
||||
be *BlockExecutor,
|
||||
appConnConsensus proxy.AppConnConsensus,
|
||||
appConn abciclient.Client,
|
||||
block *types.Block,
|
||||
logger log.Logger,
|
||||
store Store,
|
||||
initialHeight int64,
|
||||
s State,
|
||||
) ([]byte, error) {
|
||||
abciResponses, err := execBlockOnProxyApp(ctx, logger, appConnConsensus, block, store, initialHeight)
|
||||
pbh := block.Header.ToProto()
|
||||
finalizeBlockResponse, err := appConn.FinalizeBlock(
|
||||
ctx,
|
||||
abci.RequestFinalizeBlock{
|
||||
Hash: block.Hash(),
|
||||
Header: *pbh,
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
DecidedLastCommit: buildLastCommitInfo(block, store, initialHeight),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("failed executing block on proxy app", "height", block.Height, "err", err)
|
||||
logger.Error("executing block", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("executed block", "height", block.Height)
|
||||
|
||||
// the BlockExecutor condition is using for the final block replay process.
|
||||
if be != nil {
|
||||
abciValUpdates := abciResponses.FinalizeBlock.ValidatorUpdates
|
||||
err = validateValidatorUpdates(abciValUpdates, s.ConsensusParams.Validator)
|
||||
err = validateValidatorUpdates(finalizeBlockResponse.ValidatorUpdates, s.ConsensusParams.Validator)
|
||||
if err != nil {
|
||||
logger.Error("err", err)
|
||||
logger.Error("validating validator updates", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciValUpdates)
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(finalizeBlockResponse.ValidatorUpdates)
|
||||
if err != nil {
|
||||
logger.Error("err", err)
|
||||
logger.Error("converting validator updates to native types", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -650,11 +658,11 @@ func ExecCommitBlock(
|
||||
}
|
||||
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
fireEvents(ctx, be.logger, be.eventBus, block, blockID, abciResponses, validatorUpdates)
|
||||
fireEvents(ctx, be.logger, be.eventBus, block, blockID, finalizeBlockResponse, validatorUpdates)
|
||||
}
|
||||
|
||||
// Commit block, get hash back
|
||||
res, err := appConnConsensus.Commit(ctx)
|
||||
res, err := appConn.Commit(ctx)
|
||||
if err != nil {
|
||||
logger.Error("client error during proxyAppConn.Commit", "err", res)
|
||||
return nil, err
|
||||
|
||||
+440
-101
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/tendermint/tendermint/crypto/encoding"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
mmock "github.com/tendermint/tendermint/internal/mempool/mock"
|
||||
mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
"github.com/tendermint/tendermint/internal/pubsub"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/tendermint/tendermint/internal/store"
|
||||
"github.com/tendermint/tendermint/internal/test/factory"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtime "github.com/tendermint/tendermint/libs/time"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
)
|
||||
@@ -39,24 +38,35 @@ var (
|
||||
|
||||
func TestApplyBlock(t *testing.T) {
|
||||
app := &testApp{}
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
logger := log.TestingLogger()
|
||||
proxyApp := proxy.NewAppConns(cc, logger, proxy.NopMetrics())
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, proxyApp.Start(ctx))
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
state, stateDB, _ := makeState(t, 1, 1)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
||||
blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp.Consensus(),
|
||||
mmock.Mempool{}, sm.EmptyEvidencePool{}, blockStore)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp, mp, sm.EmptyEvidencePool{}, blockStore, eventBus)
|
||||
|
||||
block, err := sf.MakeBlock(state, 1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := sf.MakeBlock(state, 1, new(types.Commit))
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
@@ -68,85 +78,91 @@ func TestApplyBlock(t *testing.T) {
|
||||
assert.EqualValues(t, 1, state.Version.Consensus.App, "App version wasn't updated")
|
||||
}
|
||||
|
||||
// TestBeginBlockValidators ensures we send absent validators list.
|
||||
func TestBeginBlockValidators(t *testing.T) {
|
||||
// TestFinalizeBlockDecidedLastCommit ensures we correctly send the DecidedLastCommit to the
|
||||
// application. The test ensures that the DecidedLastCommit properly reflects
|
||||
// which validators signed the preceding block.
|
||||
func TestFinalizeBlockDecidedLastCommit(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.TestingLogger()
|
||||
app := &testApp{}
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
proxyApp := proxy.NewAppConns(cc, log.TestingLogger(), proxy.NopMetrics())
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
appClient := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
|
||||
err := proxyApp.Start(ctx)
|
||||
err := appClient.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
state, stateDB, _ := makeState(t, 2, 2)
|
||||
state, stateDB, privVals := makeState(t, 7, 1)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
|
||||
prevHash := state.LastBlockID.Hash
|
||||
prevParts := types.PartSetHeader{}
|
||||
prevBlockID := types.BlockID{Hash: prevHash, PartSetHeader: prevParts}
|
||||
|
||||
var (
|
||||
now = tmtime.Now()
|
||||
commitSig0 = types.NewCommitSigForBlock(
|
||||
[]byte("Signature1"),
|
||||
state.Validators.Validators[0].Address,
|
||||
now,
|
||||
types.VoteExtensionToSign{},
|
||||
)
|
||||
commitSig1 = types.NewCommitSigForBlock(
|
||||
[]byte("Signature2"),
|
||||
state.Validators.Validators[1].Address,
|
||||
now,
|
||||
types.VoteExtensionToSign{},
|
||||
)
|
||||
absentSig = types.NewCommitSigAbsent()
|
||||
)
|
||||
absentSig := types.NewCommitSigAbsent()
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
lastCommitSigs []types.CommitSig
|
||||
expectedAbsentValidators []int
|
||||
name string
|
||||
absentCommitSigs map[int]bool
|
||||
}{
|
||||
{"none absent", []types.CommitSig{commitSig0, commitSig1}, []int{}},
|
||||
{"one absent", []types.CommitSig{commitSig0, absentSig}, []int{1}},
|
||||
{"multiple absent", []types.CommitSig{absentSig, absentSig}, []int{0, 1}},
|
||||
{"none absent", map[int]bool{}},
|
||||
{"one absent", map[int]bool{1: true}},
|
||||
{"multiple absent", map[int]bool{1: true, 3: true}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
lastCommit := types.NewCommit(1, 0, prevBlockID, tc.lastCommitSigs)
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
||||
evpool := &mocks.EvidencePool{}
|
||||
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, 0)
|
||||
evpool.On("Update", ctx, mock.Anything, mock.Anything).Return()
|
||||
evpool.On("CheckEvidence", ctx, mock.Anything).Return(nil)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
|
||||
// block for height 2
|
||||
block, err := sf.MakeBlock(state, 2, lastCommit)
|
||||
require.NoError(t, err)
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
_, err = sm.ExecCommitBlock(ctx, nil, proxyApp.Consensus(), block, log.TestingLogger(), stateStore, 1, state)
|
||||
require.NoError(t, err, tc.desc)
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), appClient, mp, evpool, blockStore, eventBus)
|
||||
state, _, lastCommit := makeAndCommitGoodBlock(ctx, t, state, 1, new(types.Commit), state.NextValidators.Validators[0].Address, blockExec, privVals, nil)
|
||||
|
||||
// -> app receives a list of validators with a bool indicating if they signed
|
||||
ctr := 0
|
||||
for i, v := range app.CommitVotes {
|
||||
if ctr < len(tc.expectedAbsentValidators) &&
|
||||
tc.expectedAbsentValidators[ctr] == i {
|
||||
|
||||
assert.False(t, v.SignedLastBlock)
|
||||
ctr++
|
||||
} else {
|
||||
assert.True(t, v.SignedLastBlock)
|
||||
for idx, isAbsent := range tc.absentCommitSigs {
|
||||
if isAbsent {
|
||||
lastCommit.Signatures[idx] = absentSig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// block for height 2
|
||||
block := sf.MakeBlock(state, 2, lastCommit)
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
_, err = blockExec.ApplyBlock(ctx, state, blockID, block)
|
||||
require.NoError(t, err)
|
||||
|
||||
// -> app receives a list of validators with a bool indicating if they signed
|
||||
for i, v := range app.CommitVotes {
|
||||
_, absent := tc.absentCommitSigs[i]
|
||||
assert.Equal(t, !absent, v.SignedLastBlock)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBeginBlockByzantineValidators ensures we send byzantine validators list.
|
||||
func TestBeginBlockByzantineValidators(t *testing.T) {
|
||||
// TestFinalizeBlockByzantineValidators ensures we send byzantine validators list.
|
||||
func TestFinalizeBlockByzantineValidators(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
app := &testApp{}
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
proxyApp := proxy.NewAppConns(cc, log.TestingLogger(), proxy.NopMetrics())
|
||||
logger := log.TestingLogger()
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -219,14 +235,27 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
|
||||
evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return(ev, int64(100))
|
||||
evpool.On("Update", ctx, mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return()
|
||||
evpool.On("CheckEvidence", ctx, mock.AnythingOfType("types.EvidenceList")).Return(nil)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
||||
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(),
|
||||
mmock.Mempool{}, evpool, blockStore)
|
||||
blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp,
|
||||
mp, evpool, blockStore, eventBus)
|
||||
|
||||
block, err := sf.MakeBlock(state, 1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := sf.MakeBlock(state, 1, new(types.Commit))
|
||||
block.Evidence = ev
|
||||
block.Header.EvidenceHash = block.Evidence.Hash()
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
@@ -248,9 +277,9 @@ func TestProcessProposal(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
app := abcimocks.NewBaseMock()
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
logger := log.TestingLogger()
|
||||
proxyApp := proxy.NewAppConns(cc, logger, proxy.NopMetrics())
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -258,17 +287,20 @@ func TestProcessProposal(t *testing.T) {
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
logger,
|
||||
proxyApp.Consensus(),
|
||||
mmock.Mempool{},
|
||||
proxyApp,
|
||||
new(mpmocks.Mempool),
|
||||
sm.EmptyEvidencePool{},
|
||||
blockStore,
|
||||
eventBus,
|
||||
)
|
||||
|
||||
block0, err := sf.MakeBlock(state, height-1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block0 := sf.MakeBlock(state, height-1, new(types.Commit))
|
||||
lastCommitSig := []types.CommitSig{}
|
||||
partSet, err := block0.MakePartSet(types.BlockPartSizeBytes)
|
||||
require.NoError(t, err)
|
||||
@@ -292,8 +324,7 @@ func TestProcessProposal(t *testing.T) {
|
||||
}
|
||||
|
||||
lastCommit := types.NewCommit(height-1, 0, types.BlockID{}, lastCommitSig)
|
||||
block1, err := sf.MakeBlock(state, height, lastCommit)
|
||||
require.NoError(t, err)
|
||||
block1 := sf.MakeBlock(state, height, lastCommit)
|
||||
block1.Txs = txs
|
||||
|
||||
expectedRpp := abci.RequestProcessProposal{
|
||||
@@ -301,7 +332,7 @@ func TestProcessProposal(t *testing.T) {
|
||||
Header: *block1.Header.ToProto(),
|
||||
Txs: block1.Txs.ToSliceOfBytes(),
|
||||
ByzantineValidators: block1.Evidence.ToABCI(),
|
||||
LastCommitInfo: abci.LastCommitInfo{
|
||||
ProposedLastCommit: abci.CommitInfo{
|
||||
Round: 0,
|
||||
Votes: voteInfos,
|
||||
},
|
||||
@@ -445,46 +476,54 @@ func TestUpdateValidators(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestEndBlockValidatorUpdates ensures we update validator set and send an event.
|
||||
func TestEndBlockValidatorUpdates(t *testing.T) {
|
||||
// TestFinalizeBlockValidatorUpdates ensures we update validator set and send an event.
|
||||
func TestFinalizeBlockValidatorUpdates(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
app := &testApp{}
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
logger := log.TestingLogger()
|
||||
proxyApp := proxy.NewAppConns(cc, logger, proxy.NopMetrics())
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
state, stateDB, _ := makeState(t, 1, 1)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{})
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
logger,
|
||||
proxyApp.Consensus(),
|
||||
mmock.Mempool{},
|
||||
proxyApp,
|
||||
mp,
|
||||
sm.EmptyEvidencePool{},
|
||||
blockStore,
|
||||
eventBus,
|
||||
)
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
err = eventBus.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
defer eventBus.Stop()
|
||||
|
||||
blockExec.SetEventBus(eventBus)
|
||||
|
||||
updatesSub, err := eventBus.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{
|
||||
ClientID: "TestEndBlockValidatorUpdates",
|
||||
ClientID: "TestFinalizeBlockValidatorUpdates",
|
||||
Query: types.EventQueryValidatorSetUpdates,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
block, err := sf.MakeBlock(state, 1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := sf.MakeBlock(state, 1, new(types.Commit))
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
@@ -519,33 +558,36 @@ func TestEndBlockValidatorUpdates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestEndBlockValidatorUpdatesResultingInEmptySet checks that processing validator updates that
|
||||
// TestFinalizeBlockValidatorUpdatesResultingInEmptySet checks that processing validator updates that
|
||||
// would result in empty set causes no panic, an error is raised and NextValidators is not updated
|
||||
func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) {
|
||||
func TestFinalizeBlockValidatorUpdatesResultingInEmptySet(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
app := &testApp{}
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
logger := log.TestingLogger()
|
||||
proxyApp := proxy.NewAppConns(cc, logger, proxy.NopMetrics())
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
state, stateDB, _ := makeState(t, 1, 1)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
log.TestingLogger(),
|
||||
proxyApp.Consensus(),
|
||||
mmock.Mempool{},
|
||||
proxyApp,
|
||||
new(mpmocks.Mempool),
|
||||
sm.EmptyEvidencePool{},
|
||||
blockStore,
|
||||
eventBus,
|
||||
)
|
||||
|
||||
block, err := sf.MakeBlock(state, 1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := sf.MakeBlock(state, 1, new(types.Commit))
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
@@ -562,6 +604,292 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) {
|
||||
assert.NotEmpty(t, state.NextValidators.Validators)
|
||||
}
|
||||
|
||||
func TestEmptyPrepareProposal(t *testing.T) {
|
||||
const height = 2
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.TestingLogger()
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
app := abcimocks.NewBaseMock()
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
state, stateDB, privVals := makeState(t, 1, height)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{})
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
logger,
|
||||
proxyApp,
|
||||
mp,
|
||||
sm.EmptyEvidencePool{},
|
||||
nil,
|
||||
eventBus,
|
||||
)
|
||||
pa, _ := state.Validators.GetByIndex(0)
|
||||
commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals)
|
||||
_, err = blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestPrepareProposalRemoveTxs tests that any transactions marked as REMOVED
|
||||
// are not included in the block produced by CreateProposalBlock. The test also
|
||||
// ensures that any transactions removed are also removed from the mempool.
|
||||
func TestPrepareProposalRemoveTxs(t *testing.T) {
|
||||
const height = 2
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.TestingLogger()
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
state, stateDB, privVals := makeState(t, 1, height)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
|
||||
evpool := &mocks.EvidencePool{}
|
||||
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0))
|
||||
|
||||
txs := factory.MakeTenTxs(height)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs))
|
||||
|
||||
trs := txsToTxRecords(types.Txs(txs))
|
||||
trs[0].Action = abci.TxRecord_REMOVED
|
||||
trs[1].Action = abci.TxRecord_REMOVED
|
||||
mp.On("RemoveTxByKey", mock.Anything).Return(nil).Twice()
|
||||
|
||||
app := abcimocks.NewBaseMock()
|
||||
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
|
||||
ModifiedTx: true,
|
||||
TxRecords: trs,
|
||||
}, nil)
|
||||
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
logger,
|
||||
proxyApp,
|
||||
mp,
|
||||
evpool,
|
||||
nil,
|
||||
eventBus,
|
||||
)
|
||||
pa, _ := state.Validators.GetByIndex(0)
|
||||
commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals)
|
||||
block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, block.Data.Txs.ToSliceOfBytes(), len(trs)-2)
|
||||
|
||||
require.Equal(t, -1, block.Data.Txs.Index(types.Tx(trs[0].Tx)))
|
||||
require.Equal(t, -1, block.Data.Txs.Index(types.Tx(trs[1].Tx)))
|
||||
|
||||
mp.AssertCalled(t, "RemoveTxByKey", types.Tx(trs[0].Tx).Key())
|
||||
mp.AssertCalled(t, "RemoveTxByKey", types.Tx(trs[1].Tx).Key())
|
||||
mp.AssertExpectations(t)
|
||||
}
|
||||
|
||||
// TestPrepareProposalAddedTxsIncluded tests that any transactions marked as ADDED
|
||||
// in the prepare proposal response are included in the block. The test also
|
||||
// ensures that any transactions added are also checked into the mempool.
|
||||
func TestPrepareProposalAddedTxsIncluded(t *testing.T) {
|
||||
const height = 2
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.TestingLogger()
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
state, stateDB, privVals := makeState(t, 1, height)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
|
||||
evpool := &mocks.EvidencePool{}
|
||||
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0))
|
||||
|
||||
txs := factory.MakeTenTxs(height)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs[2:]))
|
||||
mp.On("CheckTx", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice()
|
||||
|
||||
trs := txsToTxRecords(types.Txs(txs))
|
||||
trs[0].Action = abci.TxRecord_ADDED
|
||||
trs[1].Action = abci.TxRecord_ADDED
|
||||
|
||||
app := abcimocks.NewBaseMock()
|
||||
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
|
||||
ModifiedTx: true,
|
||||
TxRecords: trs,
|
||||
}, nil)
|
||||
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
logger,
|
||||
proxyApp,
|
||||
mp,
|
||||
evpool,
|
||||
nil,
|
||||
eventBus,
|
||||
)
|
||||
pa, _ := state.Validators.GetByIndex(0)
|
||||
commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals)
|
||||
block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, txs[0], block.Data.Txs[0])
|
||||
require.Equal(t, txs[1], block.Data.Txs[1])
|
||||
|
||||
mp.AssertExpectations(t)
|
||||
mp.AssertCalled(t, "CheckTx", mock.Anything, types.Tx(trs[0].Tx), mock.Anything, mock.Anything)
|
||||
mp.AssertCalled(t, "CheckTx", mock.Anything, types.Tx(trs[1].Tx), mock.Anything, mock.Anything)
|
||||
}
|
||||
|
||||
// TestPrepareProposalReorderTxs tests that CreateBlock produces a block with transactions
|
||||
// in the order matching the order they are returned from PrepareProposal.
|
||||
func TestPrepareProposalReorderTxs(t *testing.T) {
|
||||
const height = 2
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.TestingLogger()
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
state, stateDB, privVals := makeState(t, 1, height)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
|
||||
evpool := &mocks.EvidencePool{}
|
||||
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0))
|
||||
|
||||
txs := factory.MakeTenTxs(height)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs))
|
||||
|
||||
trs := txsToTxRecords(types.Txs(txs))
|
||||
trs = trs[2:]
|
||||
trs = append(trs[len(trs)/2:], trs[:len(trs)/2]...)
|
||||
|
||||
app := abcimocks.NewBaseMock()
|
||||
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
|
||||
ModifiedTx: true,
|
||||
TxRecords: trs,
|
||||
}, nil)
|
||||
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
logger,
|
||||
proxyApp,
|
||||
mp,
|
||||
evpool,
|
||||
nil,
|
||||
eventBus,
|
||||
)
|
||||
pa, _ := state.Validators.GetByIndex(0)
|
||||
commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals)
|
||||
block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil)
|
||||
require.NoError(t, err)
|
||||
for i, tx := range block.Data.Txs {
|
||||
require.Equal(t, types.Tx(trs[i].Tx), tx)
|
||||
}
|
||||
|
||||
mp.AssertExpectations(t)
|
||||
|
||||
}
|
||||
|
||||
// TestPrepareProposalModifiedTxFalse tests that CreateBlock correctly ignores
|
||||
// the ResponsePrepareProposal TxRecords if ResponsePrepareProposal does not
|
||||
// set ModifiedTx to true.
|
||||
func TestPrepareProposalModifiedTxFalse(t *testing.T) {
|
||||
const height = 2
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.TestingLogger()
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
state, stateDB, privVals := makeState(t, 1, height)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
|
||||
evpool := &mocks.EvidencePool{}
|
||||
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0))
|
||||
|
||||
txs := factory.MakeTenTxs(height)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs))
|
||||
|
||||
trs := txsToTxRecords(types.Txs(txs))
|
||||
trs = append(trs[len(trs)/2:], trs[:len(trs)/2]...)
|
||||
trs = trs[1:]
|
||||
trs[0].Action = abci.TxRecord_REMOVED
|
||||
trs[1] = &abci.TxRecord{
|
||||
Tx: []byte("new"),
|
||||
Action: abci.TxRecord_ADDED,
|
||||
}
|
||||
|
||||
app := abcimocks.NewBaseMock()
|
||||
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
|
||||
ModifiedTx: false,
|
||||
TxRecords: trs,
|
||||
}, nil)
|
||||
|
||||
cc := abciclient.NewLocalClient(logger, app)
|
||||
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
|
||||
err := proxyApp.Start(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
logger,
|
||||
proxyApp,
|
||||
mp,
|
||||
evpool,
|
||||
nil,
|
||||
eventBus,
|
||||
)
|
||||
pa, _ := state.Validators.GetByIndex(0)
|
||||
commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals)
|
||||
block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil)
|
||||
require.NoError(t, err)
|
||||
for i, tx := range block.Data.Txs {
|
||||
require.Equal(t, txs[i], tx)
|
||||
}
|
||||
|
||||
mp.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID {
|
||||
var (
|
||||
h = make([]byte, tmhash.Size)
|
||||
@@ -577,3 +905,14 @@ func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.Bloc
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func txsToTxRecords(txs []types.Tx) []*abci.TxRecord {
|
||||
trs := make([]*abci.TxRecord, len(txs))
|
||||
for i, tx := range txs {
|
||||
trs[i] = &abci.TxRecord{
|
||||
Action: abci.TxRecord_UNMODIFIED,
|
||||
Tx: tx,
|
||||
}
|
||||
}
|
||||
return trs
|
||||
}
|
||||
|
||||
@@ -2,33 +2,9 @@ package state
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
//
|
||||
// TODO: Remove dependence on all entities exported from this file.
|
||||
//
|
||||
// Every entity exported here is dependent on a private entity from the `state`
|
||||
// package. Currently, these functions are only made available to tests in the
|
||||
// `state_test` package, but we should not be relying on them for our testing.
|
||||
// Instead, we should be exclusively relying on exported entities for our
|
||||
// testing, and should be refactoring exported entities to make them more
|
||||
// easily testable from outside of the package.
|
||||
//
|
||||
|
||||
// UpdateState is an alias for updateState exported from execution.go,
|
||||
// exclusively and explicitly for testing.
|
||||
func UpdateState(
|
||||
state State,
|
||||
blockID types.BlockID,
|
||||
header *types.Header,
|
||||
abciResponses *tmstate.ABCIResponses,
|
||||
validatorUpdates []*types.Validator,
|
||||
) (State, error) {
|
||||
return updateState(state, blockID, header, abciResponses, validatorUpdates)
|
||||
}
|
||||
|
||||
// ValidateValidatorUpdates is an alias for validateValidatorUpdates exported
|
||||
// from execution.go, exclusively and explicitly for testing.
|
||||
func ValidateValidatorUpdates(abciUpdates []abci.ValidatorUpdate, params types.ValidatorParams) error {
|
||||
|
||||
@@ -11,17 +11,13 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/encoding"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
sf "github.com/tendermint/tendermint/internal/state/test/factory"
|
||||
"github.com/tendermint/tendermint/internal/test/factory"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
tmtime "github.com/tendermint/tendermint/libs/time"
|
||||
tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
@@ -33,12 +29,6 @@ type paramsChangeTestCase struct {
|
||||
params types.ConsensusParams
|
||||
}
|
||||
|
||||
func newTestApp() proxy.AppConns {
|
||||
app := &testApp{}
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
return proxy.NewAppConns(cc, log.NewNopLogger(), proxy.NopMetrics())
|
||||
}
|
||||
|
||||
func makeAndCommitGoodBlock(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
@@ -72,12 +62,13 @@ func makeAndApplyGoodBlock(
|
||||
evidence []types.Evidence,
|
||||
) (sm.State, types.BlockID) {
|
||||
t.Helper()
|
||||
block, _, err := state.MakeBlock(height, factory.MakeTenTxs(height), lastCommit, evidence, proposerAddr)
|
||||
block := state.MakeBlock(height, factory.MakeTenTxs(height), lastCommit, evidence, proposerAddr)
|
||||
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, blockExec.ValidateBlock(ctx, state, block))
|
||||
blockID := types.BlockID{Hash: block.Hash(),
|
||||
PartSetHeader: types.PartSetHeader{Total: 3, Hash: tmrand.Bytes(32)}}
|
||||
PartSetHeader: partSet.Header()}
|
||||
state, err = blockExec.ApplyBlock(ctx, state, blockID, block)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -153,11 +144,8 @@ func makeHeaderPartsResponsesValPubKeyChange(
|
||||
pubkey crypto.PubKey,
|
||||
) (types.Header, types.BlockID, *tmstate.ABCIResponses) {
|
||||
|
||||
block, err := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{ValidatorUpdates: nil},
|
||||
}
|
||||
block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
abciResponses := &tmstate.ABCIResponses{}
|
||||
// If the pubkey is new, remove the old and add the new.
|
||||
_, val := state.NextValidators.GetByIndex(0)
|
||||
if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) {
|
||||
@@ -184,13 +172,11 @@ func makeHeaderPartsResponsesValPowerChange(
|
||||
) (types.Header, types.BlockID, *tmstate.ABCIResponses) {
|
||||
t.Helper()
|
||||
|
||||
block, err := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{ValidatorUpdates: nil},
|
||||
}
|
||||
abciResponses := &tmstate.ABCIResponses{}
|
||||
|
||||
abciResponses.FinalizeBlock = &abci.ResponseFinalizeBlock{}
|
||||
// If the pubkey is new, remove the old and add the new.
|
||||
_, val := state.NextValidators.GetByIndex(0)
|
||||
if val.VotingPower != power {
|
||||
@@ -214,8 +200,7 @@ func makeHeaderPartsResponsesParams(
|
||||
) (types.Header, types.BlockID, *tmstate.ABCIResponses) {
|
||||
t.Helper()
|
||||
|
||||
block, err := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := sf.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
pbParams := params.ToProto()
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{ConsensusParamUpdates: &pbParams},
|
||||
@@ -296,15 +281,15 @@ func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) {
|
||||
}
|
||||
|
||||
func (app *testApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock {
|
||||
app.CommitVotes = req.LastCommitInfo.Votes
|
||||
app.CommitVotes = req.DecidedLastCommit.Votes
|
||||
app.ByzantineValidators = req.ByzantineValidators
|
||||
|
||||
resTxs := make([]*abci.ResponseDeliverTx, len(req.Txs))
|
||||
resTxs := make([]*abci.ExecTxResult, len(req.Txs))
|
||||
for i, tx := range req.Txs {
|
||||
if len(tx) > 0 {
|
||||
resTxs[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
|
||||
resTxs[i] = &abci.ExecTxResult{Code: abci.CodeTypeOK}
|
||||
} else {
|
||||
resTxs[i] = &abci.ResponseDeliverTx{Code: abci.CodeTypeOK + 10} // error
|
||||
resTxs[i] = &abci.ExecTxResult{Code: abci.CodeTypeOK + 10} // error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,8 +300,8 @@ func (app *testApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFi
|
||||
AppVersion: 1,
|
||||
},
|
||||
},
|
||||
Events: []abci.Event{},
|
||||
Txs: resTxs,
|
||||
Events: []abci.Event{},
|
||||
TxResults: resTxs,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
var _ indexer.BlockIndexer = (*BlockerIndexer)(nil)
|
||||
|
||||
// BlockerIndexer implements a block indexer, indexing BeginBlock and EndBlock
|
||||
// BlockerIndexer implements a block indexer, indexing FinalizeBlock
|
||||
// events with an underlying KV store. Block events are indexed by their height,
|
||||
// such that matching search criteria returns the respective block height(s).
|
||||
type BlockerIndexer struct {
|
||||
@@ -44,12 +44,11 @@ func (idx *BlockerIndexer) Has(height int64) (bool, error) {
|
||||
return idx.store.Has(key)
|
||||
}
|
||||
|
||||
// Index indexes BeginBlock and EndBlock events for a given block by its height.
|
||||
// Index indexes FinalizeBlock events for a given block by its height.
|
||||
// The following is indexed:
|
||||
//
|
||||
// primary key: encode(block.height | height) => encode(height)
|
||||
// BeginBlock events: encode(eventType.eventAttr|eventValue|height|begin_block) => encode(height)
|
||||
// EndBlock events: encode(eventType.eventAttr|eventValue|height|end_block) => encode(height)
|
||||
// FinalizeBlock events: encode(eventType.eventAttr|eventValue|height|finalize_block) => encode(height)
|
||||
func (idx *BlockerIndexer) Index(bh types.EventDataNewBlockHeader) error {
|
||||
batch := idx.store.NewBatch()
|
||||
defer batch.Close()
|
||||
@@ -65,19 +64,19 @@ func (idx *BlockerIndexer) Index(bh types.EventDataNewBlockHeader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. index BeginBlock events
|
||||
if err := idx.indexEvents(batch, bh.ResultFinalizeBlock.Events, "finalize_block", height); err != nil {
|
||||
// 2. index FinalizeBlock events
|
||||
if err := idx.indexEvents(batch, bh.ResultFinalizeBlock.Events, types.EventTypeFinalizeBlock, height); err != nil {
|
||||
return fmt.Errorf("failed to index FinalizeBlock events: %w", err)
|
||||
}
|
||||
|
||||
return batch.WriteSync()
|
||||
}
|
||||
|
||||
// Search performs a query for block heights that match a given BeginBlock
|
||||
// and Endblock event search criteria. The given query can match against zero,
|
||||
// one or more block heights. In the case of height queries, i.e. block.height=H,
|
||||
// if the height is indexed, that height alone will be returned. An error and
|
||||
// nil slice is returned. Otherwise, a non-nil slice and nil error is returned.
|
||||
// Search performs a query for block heights that match a given FinalizeBlock
|
||||
// The given query can match against zero or more block heights. In the case
|
||||
// of height queries, i.e. block.height=H, if the height is indexed, that height
|
||||
// alone will be returned. An error and nil slice is returned. Otherwise, a
|
||||
// non-nil slice and nil error is returned.
|
||||
func (idx *BlockerIndexer) Search(ctx context.Context, q *query.Query) ([]int64, error) {
|
||||
results := make([]int64, 0)
|
||||
select {
|
||||
|
||||
@@ -92,19 +92,19 @@ func TestBlockIndexer(t *testing.T) {
|
||||
q: query.MustCompile(`block.height = 5`),
|
||||
results: []int64{5},
|
||||
},
|
||||
"begin_event.key1 = 'value1'": {
|
||||
"finalize_event.key1 = 'value1'": {
|
||||
q: query.MustCompile(`finalize_event1.key1 = 'value1'`),
|
||||
results: []int64{},
|
||||
},
|
||||
"begin_event.proposer = 'FCAA001'": {
|
||||
"finalize_event.proposer = 'FCAA001'": {
|
||||
q: query.MustCompile(`finalize_event1.proposer = 'FCAA001'`),
|
||||
results: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||||
},
|
||||
"end_event.foo <= 5": {
|
||||
"finalize_event.foo <= 5": {
|
||||
q: query.MustCompile(`finalize_event2.foo <= 5`),
|
||||
results: []int64{2, 4},
|
||||
},
|
||||
"end_event.foo >= 100": {
|
||||
"finalize_event.foo >= 100": {
|
||||
q: query.MustCompile(`finalize_event2.foo >= 100`),
|
||||
results: []int64{1},
|
||||
},
|
||||
@@ -112,11 +112,11 @@ func TestBlockIndexer(t *testing.T) {
|
||||
q: query.MustCompile(`block.height > 2 AND finalize_event2.foo <= 8`),
|
||||
results: []int64{4, 6, 8},
|
||||
},
|
||||
"begin_event.proposer CONTAINS 'FFFFFFF'": {
|
||||
"finalize_event.proposer CONTAINS 'FFFFFFF'": {
|
||||
q: query.MustCompile(`finalize_event1.proposer CONTAINS 'FFFFFFF'`),
|
||||
results: []int64{},
|
||||
},
|
||||
"begin_event.proposer CONTAINS 'FCAA001'": {
|
||||
"finalize_event.proposer CONTAINS 'FCAA001'": {
|
||||
q: query.MustCompile(`finalize_event1.proposer CONTAINS 'FCAA001'`),
|
||||
results: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||||
},
|
||||
|
||||
@@ -30,11 +30,11 @@ type BlockIndexer interface {
|
||||
// upon database query failure.
|
||||
Has(height int64) (bool, error)
|
||||
|
||||
// Index indexes BeginBlock and EndBlock events for a given block by its height.
|
||||
// Index indexes FinalizeBlock events for a given block by its height.
|
||||
Index(types.EventDataNewBlockHeader) error
|
||||
|
||||
// Search performs a query for block heights that match a given BeginBlock
|
||||
// and Endblock event search criteria.
|
||||
// Search performs a query for block heights that match a given FinalizeBlock
|
||||
// event search criteria.
|
||||
Search(ctx context.Context, q *query.Query) ([]int64, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) {
|
||||
Height: 1,
|
||||
Index: uint32(0),
|
||||
Tx: types.Tx("foo"),
|
||||
Result: abci.ResponseDeliverTx{Code: 0},
|
||||
Result: abci.ExecTxResult{Code: 0},
|
||||
}
|
||||
err = eventBus.PublishEventTx(ctx, types.EventDataTx{TxResult: *txResult1})
|
||||
require.NoError(t, err)
|
||||
@@ -88,7 +88,7 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) {
|
||||
Height: 1,
|
||||
Index: uint32(1),
|
||||
Tx: types.Tx("bar"),
|
||||
Result: abci.ResponseDeliverTx{Code: 0},
|
||||
Result: abci.ExecTxResult{Code: 0},
|
||||
}
|
||||
err = eventBus.PublishEventTx(ctx, types.EventDataTx{TxResult: *txResult2})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -338,7 +338,7 @@ func txResultWithEvents(events []abci.Event) *abci.TxResult {
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: tx,
|
||||
Result: abci.ResponseDeliverTx{
|
||||
Result: abci.ExecTxResult{
|
||||
Data: []byte{0},
|
||||
Code: abci.CodeTypeOK,
|
||||
Log: "",
|
||||
|
||||
@@ -46,8 +46,7 @@ const (
|
||||
dbName = "postgres"
|
||||
chainID = "test-chainID"
|
||||
|
||||
viewBlockEvents = "block_events"
|
||||
viewTxEvents = "tx_events"
|
||||
viewTxEvents = "tx_events"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -266,7 +265,7 @@ func txResultWithEvents(events []abci.Event) *abci.TxResult {
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: types.Tx("HELLO WORLD"),
|
||||
Result: abci.ResponseDeliverTx{
|
||||
Result: abci.ExecTxResult{
|
||||
Data: []byte{0},
|
||||
Code: abci.CodeTypeOK,
|
||||
Log: "",
|
||||
@@ -309,25 +308,6 @@ SELECT height FROM `+tableBlocks+` WHERE height = $1;
|
||||
} else if err != nil {
|
||||
t.Fatalf("Database query failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify the presence of begin_block and end_block events.
|
||||
if err := testDB().QueryRow(`
|
||||
SELECT type, height, chain_id FROM `+viewBlockEvents+`
|
||||
WHERE height = $1 AND type = $2 AND chain_id = $3;
|
||||
`, height, types.EventTypeBeginBlock, chainID).Err(); err == sql.ErrNoRows {
|
||||
t.Errorf("No %q event found for height=%d", types.EventTypeBeginBlock, height)
|
||||
} else if err != nil {
|
||||
t.Fatalf("Database query failed: %c", err)
|
||||
}
|
||||
|
||||
if err := testDB().QueryRow(`
|
||||
SELECT type, height, chain_id FROM `+viewBlockEvents+`
|
||||
WHERE height = $1 AND type = $2 AND chain_id = $3;
|
||||
`, height, types.EventTypeEndBlock, chainID).Err(); err == sql.ErrNoRows {
|
||||
t.Errorf("No %q event found for height=%d", types.EventTypeEndBlock, height)
|
||||
} else if err != nil {
|
||||
t.Fatalf("Database query failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyNotImplemented calls f and verifies that it returns both a
|
||||
|
||||
@@ -43,7 +43,7 @@ func BenchmarkTxSearch(b *testing.B) {
|
||||
Height: int64(i),
|
||||
Index: 0,
|
||||
Tx: types.Tx(string(txBz)),
|
||||
Result: abci.ResponseDeliverTx{
|
||||
Result: abci.ExecTxResult{
|
||||
Data: []byte{0},
|
||||
Code: abci.CodeTypeOK,
|
||||
Log: "",
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestTxIndex(t *testing.T) {
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: tx,
|
||||
Result: abci.ResponseDeliverTx{
|
||||
Result: abci.ExecTxResult{
|
||||
Data: []byte{0},
|
||||
Code: abci.CodeTypeOK, Log: "", Events: nil,
|
||||
},
|
||||
@@ -48,7 +48,7 @@ func TestTxIndex(t *testing.T) {
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: tx2,
|
||||
Result: abci.ResponseDeliverTx{
|
||||
Result: abci.ExecTxResult{
|
||||
Data: []byte{0},
|
||||
Code: abci.CodeTypeOK, Log: "", Events: nil,
|
||||
},
|
||||
@@ -322,7 +322,7 @@ func txResultWithEvents(events []abci.Event) *abci.TxResult {
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: tx,
|
||||
Result: abci.ResponseDeliverTx{
|
||||
Result: abci.ExecTxResult{
|
||||
Data: []byte{0},
|
||||
Code: abci.CodeTypeOK,
|
||||
Log: "",
|
||||
@@ -346,7 +346,7 @@ func benchmarkTxIndex(txsCount int64, b *testing.B) {
|
||||
Height: 1,
|
||||
Index: txIndex,
|
||||
Tx: tx,
|
||||
Result: abci.ResponseDeliverTx{
|
||||
Result: abci.ExecTxResult{
|
||||
Data: []byte{0},
|
||||
Code: abci.CodeTypeOK,
|
||||
Log: "",
|
||||
|
||||
+19
-17
@@ -91,7 +91,7 @@ type State struct {
|
||||
LastHeightValidatorsChanged int64
|
||||
|
||||
// Consensus parameters used for validating blocks.
|
||||
// Changes returned by EndBlock and updated after Commit.
|
||||
// Changes returned by FinalizeBlock and updated after Commit.
|
||||
ConsensusParams types.ConsensusParams
|
||||
LastHeightConsensusParamsChanged int64
|
||||
|
||||
@@ -129,23 +129,30 @@ func (state State) Copy() State {
|
||||
}
|
||||
|
||||
// Equals returns true if the States are identical.
|
||||
func (state State) Equals(state2 State) bool {
|
||||
sbz, s2bz := state.Bytes(), state2.Bytes()
|
||||
return bytes.Equal(sbz, s2bz)
|
||||
func (state State) Equals(state2 State) (bool, error) {
|
||||
sbz, err := state.Bytes()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
s2bz, err := state2.Bytes()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return bytes.Equal(sbz, s2bz), nil
|
||||
}
|
||||
|
||||
// Bytes serializes the State using protobuf.
|
||||
// It panics if either casting to protobuf or serialization fails.
|
||||
func (state State) Bytes() []byte {
|
||||
// Bytes serializes the State using protobuf, propagating marshaling
|
||||
// errors
|
||||
func (state State) Bytes() ([]byte, error) {
|
||||
sm, err := state.ToProto()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
bz, err := proto.Marshal(sm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
return bz
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the State is equal to the empty State.
|
||||
@@ -260,7 +267,7 @@ func (state State) MakeBlock(
|
||||
commit *types.Commit,
|
||||
evidence []types.Evidence,
|
||||
proposerAddress []byte,
|
||||
) (*types.Block, *types.PartSet, error) {
|
||||
) *types.Block {
|
||||
|
||||
// Build base block with block data.
|
||||
block := types.MakeBlock(height, txs, commit, evidence)
|
||||
@@ -274,12 +281,7 @@ func (state State) MakeBlock(
|
||||
proposerAddress,
|
||||
)
|
||||
|
||||
bps, err := block.MakePartSet(types.BlockPartSizeBytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return block, bps, nil
|
||||
return block
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
+147
-118
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/encoding"
|
||||
"github.com/tendermint/tendermint/crypto/merkle"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
statefactory "github.com/tendermint/tendermint/internal/state/test/factory"
|
||||
tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
|
||||
@@ -54,13 +55,18 @@ func TestStateCopy(t *testing.T) {
|
||||
|
||||
stateCopy := state.Copy()
|
||||
|
||||
assert.True(t, state.Equals(stateCopy),
|
||||
seq, err := state.Equals(stateCopy)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, seq,
|
||||
"expected state and its copy to be identical.\ngot: %v\nexpected: %v",
|
||||
stateCopy, state)
|
||||
|
||||
stateCopy.LastBlockHeight++
|
||||
stateCopy.LastValidators = state.Validators
|
||||
assert.False(t, state.Equals(stateCopy), "expected states to be different. got same %v", state)
|
||||
|
||||
seq, err = state.Equals(stateCopy)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, seq, "expected states to be different. got same %v", state)
|
||||
}
|
||||
|
||||
// TestMakeGenesisStateNilValidators tests state's consistency when genesis file's validators field is nil.
|
||||
@@ -89,7 +95,9 @@ func TestStateSaveLoad(t *testing.T) {
|
||||
|
||||
loadedState, err := stateStore.Load()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, state.Equals(loadedState),
|
||||
seq, err := state.Equals(loadedState)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, seq,
|
||||
"expected state and its copy to be identical.\ngot: %v\nexpected: %v",
|
||||
loadedState, state)
|
||||
}
|
||||
@@ -103,16 +111,15 @@ func TestABCIResponsesSaveLoad1(t *testing.T) {
|
||||
state.LastBlockHeight++
|
||||
|
||||
// Build mock responses.
|
||||
block, err := statefactory.MakeBlock(state, 2, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(state, 2, new(types.Commit))
|
||||
|
||||
abciResponses := new(tmstate.ABCIResponses)
|
||||
dtxs := make([]*abci.ResponseDeliverTx, 2)
|
||||
dtxs := make([]*abci.ExecTxResult, 2)
|
||||
abciResponses.FinalizeBlock = new(abci.ResponseFinalizeBlock)
|
||||
abciResponses.FinalizeBlock.Txs = dtxs
|
||||
abciResponses.FinalizeBlock.TxResults = dtxs
|
||||
|
||||
abciResponses.FinalizeBlock.Txs[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Events: nil}
|
||||
abciResponses.FinalizeBlock.Txs[1] = &abci.ResponseDeliverTx{Data: []byte("bar"), Log: "ok", Events: nil}
|
||||
abciResponses.FinalizeBlock.TxResults[0] = &abci.ExecTxResult{Data: []byte("foo"), Events: nil}
|
||||
abciResponses.FinalizeBlock.TxResults[1] = &abci.ExecTxResult{Data: []byte("bar"), Log: "ok", Events: nil}
|
||||
pbpk, err := encoding.PubKeyToProto(ed25519.GenPrivKey().PubKey())
|
||||
require.NoError(t, err)
|
||||
abciResponses.FinalizeBlock.ValidatorUpdates = []abci.ValidatorUpdate{{PubKey: pbpk, Power: 10}}
|
||||
@@ -136,23 +143,23 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
||||
cases := [...]struct {
|
||||
// Height is implied to equal index+2,
|
||||
// as block 1 is created from genesis.
|
||||
added []*abci.ResponseDeliverTx
|
||||
expected []*abci.ResponseDeliverTx
|
||||
added []*abci.ExecTxResult
|
||||
expected []*abci.ExecTxResult
|
||||
}{
|
||||
0: {
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
1: {
|
||||
[]*abci.ResponseDeliverTx{
|
||||
[]*abci.ExecTxResult{
|
||||
{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
|
||||
},
|
||||
[]*abci.ResponseDeliverTx{
|
||||
[]*abci.ExecTxResult{
|
||||
{Code: 32, Data: []byte("Hello")},
|
||||
},
|
||||
},
|
||||
2: {
|
||||
[]*abci.ResponseDeliverTx{
|
||||
[]*abci.ExecTxResult{
|
||||
{Code: 383},
|
||||
{
|
||||
Data: []byte("Gotcha!"),
|
||||
@@ -162,7 +169,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
[]*abci.ResponseDeliverTx{
|
||||
[]*abci.ExecTxResult{
|
||||
{Code: 383, Data: nil},
|
||||
{Code: 0, Data: []byte("Gotcha!"), Events: []abci.Event{
|
||||
{Type: "type1", Attributes: []abci.EventAttribute{{Key: "a", Value: "1"}}},
|
||||
@@ -175,7 +182,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
||||
nil,
|
||||
},
|
||||
4: {
|
||||
[]*abci.ResponseDeliverTx{nil},
|
||||
[]*abci.ExecTxResult{nil},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
@@ -192,7 +199,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
||||
h := int64(i + 1) // last block height, one below what we save
|
||||
responses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
Txs: tc.added,
|
||||
TxResults: tc.added,
|
||||
},
|
||||
}
|
||||
err := stateStore.SaveABCIResponses(h, responses)
|
||||
@@ -205,14 +212,13 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
||||
res, err := stateStore.LoadABCIResponses(h)
|
||||
if assert.NoError(t, err, "%d", i) {
|
||||
t.Log(res)
|
||||
responses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
Txs: tc.expected,
|
||||
},
|
||||
}
|
||||
sm.ABCIResponsesResultsHash(res)
|
||||
sm.ABCIResponsesResultsHash(responses)
|
||||
assert.Equal(t, sm.ABCIResponsesResultsHash(responses), sm.ABCIResponsesResultsHash(res), "%d", i)
|
||||
e, err := abci.MarshalTxResults(tc.expected)
|
||||
require.NoError(t, err)
|
||||
he := merkle.HashFromByteSlices(e)
|
||||
rs, err := abci.MarshalTxResults(res.FinalizeBlock.TxResults)
|
||||
hrs := merkle.HashFromByteSlices(rs)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, he, hrs, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,9 +284,12 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
||||
header, blockID, responses := makeHeaderPartsResponsesValPowerChange(t, state, power)
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.FinalizeBlock.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(responses.FinalizeBlock.TxResults)
|
||||
require.NoError(t, err)
|
||||
err := stateStore.Save(state)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
state, err = state.Update(blockID, &header, h, responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
err = stateStore.Save(state)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -451,19 +460,19 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
|
||||
// NewValidatorSet calls IncrementProposerPriority but uses on a copy of val1
|
||||
assert.EqualValues(t, 0, val1.ProposerPriority)
|
||||
|
||||
block, err := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
},
|
||||
fb := &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
}
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
updatedState, err := sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
updatedState, err := state.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
assert.NoError(t, err)
|
||||
curTotal := val1VotingPower
|
||||
// one increment step and one validator: 0 + power - total_power == 0
|
||||
@@ -478,7 +487,10 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
|
||||
updateAddVal := abci.ValidatorUpdate{PubKey: fvp, Power: val2VotingPower}
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal})
|
||||
assert.NoError(t, err)
|
||||
updatedState2, err := sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err = abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h = merkle.HashFromByteSlices(rs)
|
||||
updatedState2, err := updatedState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(updatedState2.NextValidators.Validators), 2)
|
||||
@@ -517,7 +529,10 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) {
|
||||
|
||||
// this will cause the diff of priorities (77)
|
||||
// to be larger than threshold == 2*totalVotingPower (22):
|
||||
updatedState3, err := sm.UpdateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err = abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h = merkle.HashFromByteSlices(rs)
|
||||
updatedState3, err := updatedState2.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(updatedState3.NextValidators.Validators), 2)
|
||||
@@ -569,21 +584,21 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
|
||||
// we only have one validator:
|
||||
assert.Equal(t, val1PubKey.Address(), state.Validators.Proposer.Address)
|
||||
|
||||
block, err := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(state, state.LastBlockHeight+1, new(types.Commit))
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
// no updates:
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
},
|
||||
fb := &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
}
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedState, err := sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
updatedState, err := state.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10
|
||||
@@ -600,7 +615,10 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal})
|
||||
assert.NoError(t, err)
|
||||
|
||||
updatedState2, err := sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err = abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h = merkle.HashFromByteSlices(rs)
|
||||
updatedState2, err := updatedState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(updatedState2.NextValidators.Validators), 2)
|
||||
@@ -640,10 +658,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
|
||||
updatedVal2,
|
||||
)
|
||||
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedState3, err := sm.UpdateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err = abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h = merkle.HashFromByteSlices(rs)
|
||||
updatedState3, err := updatedState2.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address)
|
||||
@@ -679,15 +700,16 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
|
||||
// no changes in voting power and both validators have same voting power
|
||||
// -> proposers should alternate:
|
||||
oldState := updatedState3
|
||||
abciResponses = &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
},
|
||||
fb = &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
}
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
|
||||
oldState, err = sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err = abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h = merkle.HashFromByteSlices(rs)
|
||||
oldState, err = oldState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
assert.NoError(t, err)
|
||||
expectedVal1Prio2 = 1
|
||||
expectedVal2Prio2 = -1
|
||||
@@ -696,15 +718,16 @@ func TestProposerPriorityProposerAlternates(t *testing.T) {
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
// no validator updates:
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
},
|
||||
fb := &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
}
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
updatedState, err := oldState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
assert.NoError(t, err)
|
||||
// alternate (and cyclic priorities):
|
||||
assert.NotEqual(
|
||||
@@ -755,21 +778,21 @@ func TestLargeGenesisValidator(t *testing.T) {
|
||||
oldState := state
|
||||
for i := 0; i < 10; i++ {
|
||||
// no updates:
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
},
|
||||
fb := &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
}
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
|
||||
block, err := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit))
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
|
||||
updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
updatedState, err := oldState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
// no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0,
|
||||
// than -Total == -Voting)
|
||||
@@ -791,41 +814,41 @@ func TestLargeGenesisValidator(t *testing.T) {
|
||||
firstAddedVal := abci.ValidatorUpdate{PubKey: fvp, Power: firstAddedValVotingPower}
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal})
|
||||
assert.NoError(t, err)
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal},
|
||||
},
|
||||
fb := &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal},
|
||||
}
|
||||
block, err := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit))
|
||||
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
updatedState, err := oldState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
|
||||
lastState := updatedState
|
||||
for i := 0; i < 200; i++ {
|
||||
// no updates:
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
},
|
||||
fb := &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
}
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
|
||||
block, err := statefactory.MakeBlock(lastState, lastState.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(lastState, lastState.LastBlockHeight+1, new(types.Commit))
|
||||
|
||||
bps, err = block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
|
||||
updatedStateInner, err := sm.UpdateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
updatedStateInner, err := lastState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
lastState = updatedStateInner
|
||||
}
|
||||
@@ -851,18 +874,18 @@ func TestLargeGenesisValidator(t *testing.T) {
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal})
|
||||
assert.NoError(t, err)
|
||||
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: []abci.ValidatorUpdate{addedVal},
|
||||
},
|
||||
fb := &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: []abci.ValidatorUpdate{addedVal},
|
||||
}
|
||||
block, err := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit))
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
state, err = sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
state, err = state.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, 10+2, len(state.NextValidators.Validators))
|
||||
@@ -871,22 +894,23 @@ func TestLargeGenesisValidator(t *testing.T) {
|
||||
gp, err := encoding.PubKeyToProto(genesisPubKey)
|
||||
require.NoError(t, err)
|
||||
removeGenesisVal := abci.ValidatorUpdate{PubKey: gp, Power: 0}
|
||||
abciResponses = &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal},
|
||||
},
|
||||
fb = &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal},
|
||||
}
|
||||
|
||||
block, err = statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit))
|
||||
block = statefactory.MakeBlock(oldState, oldState.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
|
||||
bps, err = block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
updatedState, err = sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err = abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h = merkle.HashFromByteSlices(rs)
|
||||
updatedState, err = state.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
// only the first added val (not the genesis val) should be left
|
||||
assert.Equal(t, 11, len(updatedState.NextValidators.Validators))
|
||||
@@ -897,21 +921,21 @@ func TestLargeGenesisValidator(t *testing.T) {
|
||||
count := 0
|
||||
isProposerUnchanged := true
|
||||
for isProposerUnchanged {
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
},
|
||||
fb = &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
}
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
block, err = statefactory.MakeBlock(curState, curState.LastBlockHeight+1, new(types.Commit))
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
block = statefactory.MakeBlock(curState, curState.LastBlockHeight+1, new(types.Commit))
|
||||
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
curState, err = sm.UpdateState(curState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
curState, err = curState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) {
|
||||
isProposerUnchanged = false
|
||||
@@ -927,23 +951,23 @@ func TestLargeGenesisValidator(t *testing.T) {
|
||||
proposers := make([]*types.Validator, numVals)
|
||||
for i := 0; i < 100; i++ {
|
||||
// no updates:
|
||||
abciResponses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
},
|
||||
fb := &abci.ResponseFinalizeBlock{
|
||||
ValidatorUpdates: nil,
|
||||
}
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.FinalizeBlock.ValidatorUpdates)
|
||||
validatorUpdates, err := types.PB2TM.ValidatorUpdates(fb.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
|
||||
block, err := statefactory.MakeBlock(updatedState, updatedState.LastBlockHeight+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(updatedState, updatedState.LastBlockHeight+1, new(types.Commit))
|
||||
|
||||
bps, err := block.MakePartSet(testPartSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()}
|
||||
|
||||
updatedState, err = sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(fb.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
updatedState, err = updatedState.Update(blockID, &block.Header, h, fb.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks):
|
||||
if proposers[i%numVals] == nil {
|
||||
@@ -1002,7 +1026,10 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) {
|
||||
var validatorUpdates []*types.Validator
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.FinalizeBlock.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(responses.FinalizeBlock.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
state, err = state.Update(blockID, &header, h, responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates)
|
||||
require.NoError(t, err)
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
err = stateStore.Save(state)
|
||||
@@ -1035,8 +1062,7 @@ func TestStateMakeBlock(t *testing.T) {
|
||||
|
||||
proposerAddress := state.Validators.GetProposer().Address
|
||||
stateVersion := state.Version.Consensus
|
||||
block, err := statefactory.MakeBlock(state, 2, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(state, 2, new(types.Commit))
|
||||
|
||||
// test we set some fields
|
||||
assert.Equal(t, stateVersion, block.Version)
|
||||
@@ -1080,10 +1106,13 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
||||
header, blockID, responses := makeHeaderPartsResponsesParams(t, state, &cp)
|
||||
validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.FinalizeBlock.ValidatorUpdates)
|
||||
require.NoError(t, err)
|
||||
state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates)
|
||||
rs, err := abci.MarshalTxResults(responses.FinalizeBlock.TxResults)
|
||||
require.NoError(t, err)
|
||||
h := merkle.HashFromByteSlices(rs)
|
||||
state, err = state.Update(blockID, &header, h, responses.FinalizeBlock.ConsensusParamUpdates, validatorUpdates)
|
||||
|
||||
require.NoError(t, err)
|
||||
err := stateStore.Save(state)
|
||||
err = stateStore.Save(state)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
||||
+15
-13
@@ -170,7 +170,12 @@ func (store dbStore) save(state State, key []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Set(key, state.Bytes()); err != nil {
|
||||
stateBz, err := state.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Set(key, stateBz); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -206,7 +211,12 @@ func (store dbStore) Bootstrap(state State) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Set(stateKey, state.Bytes()); err != nil {
|
||||
stateBz, err := state.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Set(stateKey, stateBz); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -396,14 +406,6 @@ func (store dbStore) reverseBatchDelete(batch dbm.Batch, start, end []byte) ([]b
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
// ABCIResponsesResultsHash returns the root hash of a Merkle tree of
|
||||
// ResponseDeliverTx responses (see ABCIResults.Hash)
|
||||
//
|
||||
// See merkle.SimpleHashFromByteSlices
|
||||
func ABCIResponsesResultsHash(ar *tmstate.ABCIResponses) []byte {
|
||||
return types.NewResults(ar.FinalizeBlock.Txs).Hash()
|
||||
}
|
||||
|
||||
// LoadABCIResponses loads the ABCIResponses for the given height from the
|
||||
// database. If not found, ErrNoABCIResponsesForHeight is returned.
|
||||
//
|
||||
@@ -442,15 +444,15 @@ func (store dbStore) SaveABCIResponses(height int64, abciResponses *tmstate.ABCI
|
||||
}
|
||||
|
||||
func (store dbStore) saveABCIResponses(height int64, abciResponses *tmstate.ABCIResponses) error {
|
||||
var dtxs []*abci.ResponseDeliverTx
|
||||
var dtxs []*abci.ExecTxResult
|
||||
// strip nil values,
|
||||
for _, tx := range abciResponses.FinalizeBlock.Txs {
|
||||
for _, tx := range abciResponses.FinalizeBlock.TxResults {
|
||||
if tx != nil {
|
||||
dtxs = append(dtxs, tx)
|
||||
}
|
||||
}
|
||||
|
||||
abciResponses.FinalizeBlock.Txs = dtxs
|
||||
abciResponses.FinalizeBlock.TxResults = dtxs
|
||||
|
||||
bz, err := abciResponses.Marshal()
|
||||
if err != nil {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
@@ -239,7 +238,7 @@ func TestPruneStates(t *testing.T) {
|
||||
|
||||
err = stateStore.SaveABCIResponses(h, &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
Txs: []*abci.ResponseDeliverTx{
|
||||
TxResults: []*abci.ExecTxResult{
|
||||
{Data: []byte{1}},
|
||||
{Data: []byte{2}},
|
||||
{Data: []byte{3}},
|
||||
@@ -299,25 +298,3 @@ func TestPruneStates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestABCIResponsesResultsHash(t *testing.T) {
|
||||
responses := &tmstate.ABCIResponses{
|
||||
FinalizeBlock: &abci.ResponseFinalizeBlock{
|
||||
Txs: []*abci.ResponseDeliverTx{
|
||||
{Code: 32, Data: []byte("Hello"), Log: "Huh?"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
root := sm.ABCIResponsesResultsHash(responses)
|
||||
|
||||
// root should be Merkle tree root of FinalizeBlock tx responses
|
||||
results := types.NewResults(responses.FinalizeBlock.Txs)
|
||||
assert.Equal(t, root, results.Hash())
|
||||
|
||||
// test we can prove first tx in FinalizeBlock
|
||||
proof := results.ProveResult(0)
|
||||
bz, err := results[0].Marshal()
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, proof.Verify(root, bz))
|
||||
}
|
||||
|
||||
@@ -42,19 +42,14 @@ func MakeBlocks(ctx context.Context, t *testing.T, n int, state *sm.State, privV
|
||||
return blocks
|
||||
}
|
||||
|
||||
func MakeBlock(state sm.State, height int64, c *types.Commit) (*types.Block, error) {
|
||||
block, _, err := state.MakeBlock(
|
||||
func MakeBlock(state sm.State, height int64, c *types.Commit) *types.Block {
|
||||
return state.MakeBlock(
|
||||
height,
|
||||
factory.MakeTenTxs(state.LastBlockHeight),
|
||||
c,
|
||||
nil,
|
||||
state.Validators.GetProposer().Address,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func makeBlockAndPartSet(
|
||||
@@ -82,7 +77,8 @@ func makeBlockAndPartSet(
|
||||
lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()})
|
||||
}
|
||||
|
||||
block, partSet, err := state.MakeBlock(height, []types.Tx{}, lastCommit, nil, state.Validators.GetProposer().Address)
|
||||
block := state.MakeBlock(height, []types.Tx{}, lastCommit, nil, state.Validators.GetProposer().Address)
|
||||
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
return block, partSet
|
||||
|
||||
+73
-10
@@ -1,22 +1,85 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/internal/mempool"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// TxPreCheck returns a function to filter transactions before processing.
|
||||
// The function limits the size of a transaction to the block's maximum data size.
|
||||
func TxPreCheck(state State) mempool.PreCheckFunc {
|
||||
maxDataBytes := types.MaxDataBytesNoEvidence(
|
||||
state.ConsensusParams.Block.MaxBytes,
|
||||
state.Validators.Size(),
|
||||
func cachingStateFetcher(store Store) func() (State, error) {
|
||||
const ttl = time.Second
|
||||
|
||||
var (
|
||||
last time.Time
|
||||
mutex = &sync.Mutex{}
|
||||
cache State
|
||||
err error
|
||||
)
|
||||
return mempool.PreCheckMaxBytes(maxDataBytes)
|
||||
|
||||
return func() (State, error) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if time.Since(last) < ttl && cache.ChainID != "" {
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
cache, err = store.Load()
|
||||
if err != nil {
|
||||
return State{}, err
|
||||
}
|
||||
last = time.Now()
|
||||
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TxPostCheck returns a function to filter transactions after processing.
|
||||
// TxPreCheckFromStore returns a function to filter transactions before processing.
|
||||
// The function limits the size of a transaction to the block's maximum data size.
|
||||
func TxPreCheckFromStore(store Store) mempool.PreCheckFunc {
|
||||
fetch := cachingStateFetcher(store)
|
||||
|
||||
return func(tx types.Tx) error {
|
||||
state, err := fetch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return TxPreCheckForState(state)(tx)
|
||||
}
|
||||
}
|
||||
|
||||
func TxPreCheckForState(state State) mempool.PreCheckFunc {
|
||||
return func(tx types.Tx) error {
|
||||
maxDataBytes := types.MaxDataBytesNoEvidence(
|
||||
state.ConsensusParams.Block.MaxBytes,
|
||||
state.Validators.Size(),
|
||||
)
|
||||
return mempool.PreCheckMaxBytes(maxDataBytes)(tx)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TxPostCheckFromStore returns a function to filter transactions after processing.
|
||||
// The function limits the gas wanted by a transaction to the block's maximum total gas.
|
||||
func TxPostCheck(state State) mempool.PostCheckFunc {
|
||||
return mempool.PostCheckMaxGas(state.ConsensusParams.Block.MaxGas)
|
||||
func TxPostCheckFromStore(store Store) mempool.PostCheckFunc {
|
||||
fetch := cachingStateFetcher(store)
|
||||
|
||||
return func(tx types.Tx, resp *abci.ResponseCheckTx) error {
|
||||
state, err := fetch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mempool.PostCheckMaxGas(state.ConsensusParams.Block.MaxGas)(tx, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TxPostCheckForState(state State) mempool.PostCheckFunc {
|
||||
return func(tx types.Tx, resp *abci.ResponseCheckTx) error {
|
||||
return mempool.PostCheckMaxGas(state.ConsensusParams.Block.MaxGas)(tx, resp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestTxFilter(t *testing.T) {
|
||||
state, err := sm.MakeGenesisState(genDoc)
|
||||
require.NoError(t, err)
|
||||
|
||||
f := sm.TxPreCheck(state)
|
||||
f := sm.TxPreCheckForState(state)
|
||||
if tc.isErr {
|
||||
assert.NotNil(t, f(tc.tx), "#%v", i)
|
||||
} else {
|
||||
|
||||
@@ -10,10 +10,13 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
memmock "github.com/tendermint/tendermint/internal/mempool/mock"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
"github.com/tendermint/tendermint/internal/state/mocks"
|
||||
statefactory "github.com/tendermint/tendermint/internal/state/test/factory"
|
||||
@@ -30,20 +33,36 @@ const validationTestsStopHeight int64 = 10
|
||||
func TestValidateBlockHeader(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
proxyApp := newTestApp()
|
||||
logger := log.TestingLogger()
|
||||
proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics())
|
||||
require.NoError(t, proxyApp.Start(ctx))
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
state, stateDB, privVals := makeState(t, 3, 1)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
log.TestingLogger(),
|
||||
proxyApp.Consensus(),
|
||||
memmock.Mempool{},
|
||||
logger,
|
||||
proxyApp,
|
||||
mp,
|
||||
sm.EmptyEvidencePool{},
|
||||
blockStore,
|
||||
eventBus,
|
||||
)
|
||||
lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil)
|
||||
|
||||
@@ -91,10 +110,9 @@ func TestValidateBlockHeader(t *testing.T) {
|
||||
Invalid blocks don't pass
|
||||
*/
|
||||
for _, tc := range testCases {
|
||||
block, err := statefactory.MakeBlock(state, height, lastCommit)
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(state, height, lastCommit)
|
||||
tc.malleateBlock(block)
|
||||
err = blockExec.ValidateBlock(ctx, state, block)
|
||||
err := blockExec.ValidateBlock(ctx, state, block)
|
||||
t.Logf("%s: %v", tc.name, err)
|
||||
require.Error(t, err, tc.name)
|
||||
}
|
||||
@@ -107,10 +125,9 @@ func TestValidateBlockHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
nextHeight := validationTestsStopHeight
|
||||
block, err := statefactory.MakeBlock(state, nextHeight, lastCommit)
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(state, nextHeight, lastCommit)
|
||||
state.InitialHeight = nextHeight + 1
|
||||
err = blockExec.ValidateBlock(ctx, state, block)
|
||||
err := blockExec.ValidateBlock(ctx, state, block)
|
||||
require.Error(t, err, "expected an error when state is ahead of block")
|
||||
assert.Contains(t, err.Error(), "lower than initial height")
|
||||
}
|
||||
@@ -119,19 +136,36 @@ func TestValidateBlockCommit(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
proxyApp := newTestApp()
|
||||
logger := log.TestingLogger()
|
||||
proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics())
|
||||
require.NoError(t, proxyApp.Start(ctx))
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
|
||||
state, stateDB, privVals := makeState(t, 1, 1)
|
||||
stateStore := sm.NewStore(stateDB)
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
|
||||
blockStore := store.NewBlockStore(dbm.NewMemDB())
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
log.TestingLogger(),
|
||||
proxyApp.Consensus(),
|
||||
memmock.Mempool{},
|
||||
logger,
|
||||
proxyApp,
|
||||
mp,
|
||||
sm.EmptyEvidencePool{},
|
||||
blockStore,
|
||||
eventBus,
|
||||
)
|
||||
lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil)
|
||||
wrongSigsCommit := types.NewCommit(1, 0, types.BlockID{}, nil)
|
||||
@@ -162,8 +196,7 @@ func TestValidateBlockCommit(t *testing.T) {
|
||||
state.LastBlockID,
|
||||
[]types.CommitSig{wrongHeightVote.CommitSig()},
|
||||
)
|
||||
block, err := statefactory.MakeBlock(state, height, wrongHeightCommit)
|
||||
require.NoError(t, err)
|
||||
block := statefactory.MakeBlock(state, height, wrongHeightCommit)
|
||||
err = blockExec.ValidateBlock(ctx, state, block)
|
||||
_, isErrInvalidCommitHeight := err.(types.ErrInvalidCommitHeight)
|
||||
require.True(t, isErrInvalidCommitHeight, "expected ErrInvalidCommitHeight at height %d but got: %v", height, err)
|
||||
@@ -171,8 +204,7 @@ func TestValidateBlockCommit(t *testing.T) {
|
||||
/*
|
||||
#2589: test len(block.LastCommit.Signatures) == state.LastValidators.Size()
|
||||
*/
|
||||
block, err = statefactory.MakeBlock(state, height, wrongSigsCommit)
|
||||
require.NoError(t, err)
|
||||
block = statefactory.MakeBlock(state, height, wrongSigsCommit)
|
||||
err = blockExec.ValidateBlock(ctx, state, block)
|
||||
_, isErrInvalidCommitSignatures := err.(types.ErrInvalidCommitSignatures)
|
||||
require.True(t, isErrInvalidCommitSignatures,
|
||||
@@ -245,7 +277,8 @@ func TestValidateBlockEvidence(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
proxyApp := newTestApp()
|
||||
logger := log.TestingLogger()
|
||||
proxyApp := proxy.New(abciclient.NewLocalClient(logger, &testApp{}), logger, proxy.NopMetrics())
|
||||
require.NoError(t, proxyApp.Start(ctx))
|
||||
|
||||
state, stateDB, privVals := makeState(t, 4, 1)
|
||||
@@ -259,14 +292,29 @@ func TestValidateBlockEvidence(t *testing.T) {
|
||||
evpool.On("ABCIEvidence", mock.AnythingOfType("int64"), mock.AnythingOfType("[]types.Evidence")).Return(
|
||||
[]abci.Evidence{})
|
||||
|
||||
eventBus := eventbus.NewDefault(logger)
|
||||
require.NoError(t, eventBus.Start(ctx))
|
||||
mp := &mpmocks.Mempool{}
|
||||
mp.On("Lock").Return()
|
||||
mp.On("Unlock").Return()
|
||||
mp.On("FlushAppConn", mock.Anything).Return(nil)
|
||||
mp.On("Update",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything).Return(nil)
|
||||
|
||||
state.ConsensusParams.Evidence.MaxBytes = 1000
|
||||
blockExec := sm.NewBlockExecutor(
|
||||
stateStore,
|
||||
log.TestingLogger(),
|
||||
proxyApp.Consensus(),
|
||||
memmock.Mempool{},
|
||||
proxyApp,
|
||||
mp,
|
||||
evpool,
|
||||
blockStore,
|
||||
eventBus,
|
||||
)
|
||||
lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil)
|
||||
|
||||
@@ -287,10 +335,9 @@ func TestValidateBlockEvidence(t *testing.T) {
|
||||
evidence = append(evidence, newEv)
|
||||
currentBytes += int64(len(newEv.Bytes()))
|
||||
}
|
||||
block, _, err := state.MakeBlock(height, testfactory.MakeTenTxs(height), lastCommit, evidence, proposerAddr)
|
||||
require.NoError(t, err)
|
||||
block := state.MakeBlock(height, testfactory.MakeTenTxs(height), lastCommit, evidence, proposerAddr)
|
||||
|
||||
err = blockExec.ValidateBlock(ctx, state, block)
|
||||
err := blockExec.ValidateBlock(ctx, state, block)
|
||||
if assert.Error(t, err) {
|
||||
_, ok := err.(*types.ErrEvidenceOverflow)
|
||||
require.True(t, ok, "expected error to be of type ErrEvidenceOverflow at height %d but got %v", height, err)
|
||||
|
||||
@@ -11,11 +11,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
"github.com/tendermint/tendermint/internal/store"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -135,8 +135,7 @@ type Reactor struct {
|
||||
stateStore sm.Store
|
||||
blockStore *store.BlockStore
|
||||
|
||||
conn proxy.AppConnSnapshot
|
||||
connQuery proxy.AppConnQuery
|
||||
conn abciclient.Client
|
||||
tempDir string
|
||||
snapshotCh *p2p.Channel
|
||||
chunkCh *p2p.Channel
|
||||
@@ -173,8 +172,7 @@ func NewReactor(
|
||||
initialHeight int64,
|
||||
cfg config.StateSyncConfig,
|
||||
logger log.Logger,
|
||||
conn proxy.AppConnSnapshot,
|
||||
connQuery proxy.AppConnQuery,
|
||||
conn abciclient.Client,
|
||||
channelCreator p2p.ChannelCreator,
|
||||
peerUpdates *p2p.PeerUpdates,
|
||||
stateStore sm.Store,
|
||||
@@ -209,7 +207,6 @@ func NewReactor(
|
||||
initialHeight: initialHeight,
|
||||
cfg: cfg,
|
||||
conn: conn,
|
||||
connQuery: connQuery,
|
||||
snapshotCh: snapshotCh,
|
||||
chunkCh: chunkCh,
|
||||
blockCh: blockCh,
|
||||
@@ -287,7 +284,6 @@ func (r *Reactor) Sync(ctx context.Context) (sm.State, error) {
|
||||
r.cfg,
|
||||
r.logger,
|
||||
r.conn,
|
||||
r.connQuery,
|
||||
r.stateProvider,
|
||||
r.snapshotCh,
|
||||
r.chunkCh,
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
clientmocks "github.com/tendermint/tendermint/abci/client/mocks"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
proxymocks "github.com/tendermint/tendermint/internal/proxy/mocks"
|
||||
smmocks "github.com/tendermint/tendermint/internal/state/mocks"
|
||||
"github.com/tendermint/tendermint/internal/statesync/mocks"
|
||||
"github.com/tendermint/tendermint/internal/store"
|
||||
@@ -37,8 +37,7 @@ type reactorTestSuite struct {
|
||||
reactor *Reactor
|
||||
syncer *syncer
|
||||
|
||||
conn *proxymocks.AppConnSnapshot
|
||||
connQuery *proxymocks.AppConnQuery
|
||||
conn *clientmocks.Client
|
||||
stateProvider *mocks.StateProvider
|
||||
|
||||
snapshotChannel *p2p.Channel
|
||||
@@ -71,21 +70,14 @@ type reactorTestSuite struct {
|
||||
func setup(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
conn *proxymocks.AppConnSnapshot,
|
||||
connQuery *proxymocks.AppConnQuery,
|
||||
conn *clientmocks.Client,
|
||||
stateProvider *mocks.StateProvider,
|
||||
chBuf uint,
|
||||
) *reactorTestSuite {
|
||||
t.Helper()
|
||||
|
||||
if conn == nil {
|
||||
conn = &proxymocks.AppConnSnapshot{}
|
||||
}
|
||||
if connQuery == nil {
|
||||
connQuery = &proxymocks.AppConnQuery{}
|
||||
}
|
||||
if stateProvider == nil {
|
||||
stateProvider = &mocks.StateProvider{}
|
||||
conn = &clientmocks.Client{}
|
||||
}
|
||||
|
||||
rts := &reactorTestSuite{
|
||||
@@ -102,7 +94,6 @@ func setup(
|
||||
paramsOutCh: make(chan p2p.Envelope, chBuf),
|
||||
paramsPeerErrCh: make(chan p2p.PeerError, chBuf),
|
||||
conn: conn,
|
||||
connQuery: connQuery,
|
||||
stateProvider: stateProvider,
|
||||
}
|
||||
|
||||
@@ -171,7 +162,6 @@ func setup(
|
||||
*cfg,
|
||||
logger.With("component", "reactor"),
|
||||
conn,
|
||||
connQuery,
|
||||
chCreator,
|
||||
rts.peerUpdates,
|
||||
rts.stateStore,
|
||||
@@ -186,7 +176,6 @@ func setup(
|
||||
*cfg,
|
||||
logger.With("component", "syncer"),
|
||||
conn,
|
||||
connQuery,
|
||||
stateProvider,
|
||||
rts.snapshotChannel,
|
||||
rts.chunkChannel,
|
||||
@@ -211,7 +200,7 @@ func TestReactor_Sync(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
const snapshotHeight = 7
|
||||
rts := setup(ctx, t, nil, nil, nil, 2)
|
||||
rts := setup(ctx, t, nil, nil, 2)
|
||||
chain := buildLightBlockChain(ctx, t, 1, 10, time.Now())
|
||||
// app accepts any snapshot
|
||||
rts.conn.On("OfferSnapshot", ctx, mock.AnythingOfType("types.RequestOfferSnapshot")).
|
||||
@@ -222,7 +211,7 @@ func TestReactor_Sync(t *testing.T) {
|
||||
Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
|
||||
|
||||
// app query returns valid state app hash
|
||||
rts.connQuery.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{
|
||||
rts.conn.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{
|
||||
AppVersion: testAppVersion,
|
||||
LastBlockHeight: snapshotHeight,
|
||||
LastBlockAppHash: chain[snapshotHeight+1].AppHash,
|
||||
@@ -237,8 +226,8 @@ func TestReactor_Sync(t *testing.T) {
|
||||
defer close(closeCh)
|
||||
go handleLightBlockRequests(ctx, t, chain, rts.blockOutCh,
|
||||
rts.blockInCh, closeCh, 0)
|
||||
go graduallyAddPeers(t, rts.peerUpdateCh, closeCh, 1*time.Second)
|
||||
go handleSnapshotRequests(t, rts.snapshotOutCh, rts.snapshotInCh, closeCh, []snapshot{
|
||||
go graduallyAddPeers(ctx, t, rts.peerUpdateCh, closeCh, 1*time.Second)
|
||||
go handleSnapshotRequests(ctx, t, rts.snapshotOutCh, rts.snapshotInCh, closeCh, []snapshot{
|
||||
{
|
||||
Height: uint64(snapshotHeight),
|
||||
Format: 1,
|
||||
@@ -246,7 +235,7 @@ func TestReactor_Sync(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
go handleChunkRequests(t, rts.chunkOutCh, rts.chunkInCh, closeCh, []byte("abc"))
|
||||
go handleChunkRequests(ctx, t, rts.chunkOutCh, rts.chunkInCh, closeCh, []byte("abc"))
|
||||
|
||||
go handleConsensusParamsRequest(ctx, t, rts.paramsOutCh, rts.paramsInCh, closeCh)
|
||||
|
||||
@@ -265,7 +254,7 @@ func TestReactor_ChunkRequest_InvalidRequest(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, nil, 2)
|
||||
rts := setup(ctx, t, nil, nil, 2)
|
||||
|
||||
rts.chunkInCh <- p2p.Envelope{
|
||||
From: types.NodeID("aa"),
|
||||
@@ -316,14 +305,14 @@ func TestReactor_ChunkRequest(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
// mock ABCI connection to return local snapshots
|
||||
conn := &proxymocks.AppConnSnapshot{}
|
||||
conn := &clientmocks.Client{}
|
||||
conn.On("LoadSnapshotChunk", mock.Anything, abci.RequestLoadSnapshotChunk{
|
||||
Height: tc.request.Height,
|
||||
Format: tc.request.Format,
|
||||
Chunk: tc.request.Index,
|
||||
}).Return(&abci.ResponseLoadSnapshotChunk{Chunk: tc.chunk}, nil)
|
||||
|
||||
rts := setup(ctx, t, conn, nil, nil, 2)
|
||||
rts := setup(ctx, t, conn, nil, 2)
|
||||
|
||||
rts.chunkInCh <- p2p.Envelope{
|
||||
From: types.NodeID("aa"),
|
||||
@@ -343,7 +332,7 @@ func TestReactor_SnapshotsRequest_InvalidRequest(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, nil, 2)
|
||||
rts := setup(ctx, t, nil, nil, 2)
|
||||
|
||||
rts.snapshotInCh <- p2p.Envelope{
|
||||
From: types.NodeID("aa"),
|
||||
@@ -403,12 +392,12 @@ func TestReactor_SnapshotsRequest(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
// mock ABCI connection to return local snapshots
|
||||
conn := &proxymocks.AppConnSnapshot{}
|
||||
conn := &clientmocks.Client{}
|
||||
conn.On("ListSnapshots", mock.Anything, abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{
|
||||
Snapshots: tc.snapshots,
|
||||
}, nil)
|
||||
|
||||
rts := setup(ctx, t, conn, nil, nil, 100)
|
||||
rts := setup(ctx, t, conn, nil, 100)
|
||||
|
||||
rts.snapshotInCh <- p2p.Envelope{
|
||||
From: types.NodeID("aa"),
|
||||
@@ -435,7 +424,7 @@ func TestReactor_LightBlockResponse(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, nil, 2)
|
||||
rts := setup(ctx, t, nil, nil, 2)
|
||||
|
||||
var height int64 = 10
|
||||
// generates a random header
|
||||
@@ -492,7 +481,7 @@ func TestReactor_BlockProviders(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, nil, 2)
|
||||
rts := setup(ctx, t, nil, nil, 2)
|
||||
rts.peerUpdateCh <- p2p.PeerUpdate{
|
||||
NodeID: types.NodeID("aa"),
|
||||
Status: p2p.PeerStatusUp,
|
||||
@@ -559,7 +548,7 @@ func TestReactor_StateProviderP2P(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, nil, 2)
|
||||
rts := setup(ctx, t, nil, nil, 2)
|
||||
// make syncer non nil else test won't think we are state syncing
|
||||
rts.reactor.syncer = rts.syncer
|
||||
peerA := types.NodeID(strings.Repeat("a", 2*types.NodeIDByteLength))
|
||||
@@ -636,7 +625,7 @@ func TestReactor_Backfill(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
t.Cleanup(leaktest.CheckTimeout(t, 1*time.Minute))
|
||||
rts := setup(ctx, t, nil, nil, nil, 21)
|
||||
rts := setup(ctx, t, nil, nil, 21)
|
||||
|
||||
var (
|
||||
startHeight int64 = 20
|
||||
@@ -742,11 +731,15 @@ func handleLightBlockRequests(
|
||||
if requests%10 >= failureRate {
|
||||
lb, err := chain[int64(msg.Height)].ToProto()
|
||||
require.NoError(t, err)
|
||||
sending <- p2p.Envelope{
|
||||
select {
|
||||
case sending <- p2p.Envelope{
|
||||
From: envelope.To,
|
||||
Message: &ssproto.LightBlockResponse{
|
||||
LightBlock: lb,
|
||||
},
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
} else {
|
||||
switch errorCount % 3 {
|
||||
@@ -755,18 +748,26 @@ func handleLightBlockRequests(
|
||||
_, _, lb := mockLB(ctx, t, int64(msg.Height), factory.DefaultTestTime, factory.MakeBlockID(), vals, pv)
|
||||
differntLB, err := lb.ToProto()
|
||||
require.NoError(t, err)
|
||||
sending <- p2p.Envelope{
|
||||
select {
|
||||
case sending <- p2p.Envelope{
|
||||
From: envelope.To,
|
||||
Message: &ssproto.LightBlockResponse{
|
||||
LightBlock: differntLB,
|
||||
},
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case 1: // send nil block i.e. pretend we don't have it
|
||||
sending <- p2p.Envelope{
|
||||
select {
|
||||
case sending <- p2p.Envelope{
|
||||
From: envelope.To,
|
||||
Message: &ssproto.LightBlockResponse{
|
||||
LightBlock: nil,
|
||||
},
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case 2: // don't do anything
|
||||
}
|
||||
@@ -794,19 +795,23 @@ func handleConsensusParamsRequest(
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case envelope := <-receiving:
|
||||
if ctx.Err() != nil {
|
||||
msg, ok := envelope.Message.(*ssproto.ParamsRequest)
|
||||
if !ok {
|
||||
t.Errorf("message was %T which is not a params request", envelope.Message)
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("received consensus params request")
|
||||
msg, ok := envelope.Message.(*ssproto.ParamsRequest)
|
||||
require.True(t, ok)
|
||||
sending <- p2p.Envelope{
|
||||
select {
|
||||
case sending <- p2p.Envelope{
|
||||
From: envelope.To,
|
||||
Message: &ssproto.ParamsResponse{
|
||||
Height: msg.Height,
|
||||
ConsensusParams: paramsProto,
|
||||
},
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-closeCh:
|
||||
return
|
||||
}
|
||||
|
||||
case <-closeCh:
|
||||
@@ -860,6 +865,7 @@ func mockLB(ctx context.Context, t *testing.T, height int64, time time.Time, las
|
||||
// graduallyAddPeers delivers a new randomly-generated peer update on peerUpdateCh once
|
||||
// per interval, until closeCh is closed. Each peer update is assigned a random node ID.
|
||||
func graduallyAddPeers(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
peerUpdateCh chan p2p.PeerUpdate,
|
||||
closeCh chan struct{},
|
||||
@@ -868,6 +874,10 @@ func graduallyAddPeers(
|
||||
ticker := time.NewTicker(interval)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
peerUpdateCh <- p2p.PeerUpdate{
|
||||
NodeID: factory.RandomNodeID(t),
|
||||
@@ -879,13 +889,12 @@ func graduallyAddPeers(
|
||||
ParamsChannel: struct{}{},
|
||||
},
|
||||
}
|
||||
case <-closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleSnapshotRequests(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
receivingCh chan p2p.Envelope,
|
||||
sendingCh chan p2p.Envelope,
|
||||
@@ -895,6 +904,10 @@ func handleSnapshotRequests(
|
||||
t.Helper()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-closeCh:
|
||||
return
|
||||
case envelope := <-receivingCh:
|
||||
_, ok := envelope.Message.(*ssproto.SnapshotsRequest)
|
||||
require.True(t, ok)
|
||||
@@ -910,13 +923,12 @@ func handleSnapshotRequests(
|
||||
},
|
||||
}
|
||||
}
|
||||
case <-closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleChunkRequests(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
receivingCh chan p2p.Envelope,
|
||||
sendingCh chan p2p.Envelope,
|
||||
@@ -926,6 +938,10 @@ func handleChunkRequests(
|
||||
t.Helper()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-closeCh:
|
||||
return
|
||||
case envelope := <-receivingCh:
|
||||
msg, ok := envelope.Message.(*ssproto.ChunkRequest)
|
||||
require.True(t, ok)
|
||||
@@ -940,8 +956,6 @@ func handleChunkRequests(
|
||||
},
|
||||
}
|
||||
|
||||
case <-closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
@@ -54,8 +55,7 @@ var (
|
||||
type syncer struct {
|
||||
logger log.Logger
|
||||
stateProvider StateProvider
|
||||
conn proxy.AppConnSnapshot
|
||||
connQuery proxy.AppConnQuery
|
||||
conn abciclient.Client
|
||||
snapshots *snapshotPool
|
||||
snapshotCh *p2p.Channel
|
||||
chunkCh *p2p.Channel
|
||||
@@ -76,8 +76,7 @@ type syncer struct {
|
||||
func newSyncer(
|
||||
cfg config.StateSyncConfig,
|
||||
logger log.Logger,
|
||||
conn proxy.AppConnSnapshot,
|
||||
connQuery proxy.AppConnQuery,
|
||||
conn abciclient.Client,
|
||||
stateProvider StateProvider,
|
||||
snapshotCh *p2p.Channel,
|
||||
chunkCh *p2p.Channel,
|
||||
@@ -88,7 +87,6 @@ func newSyncer(
|
||||
logger: logger,
|
||||
stateProvider: stateProvider,
|
||||
conn: conn,
|
||||
connQuery: connQuery,
|
||||
snapshots: newSnapshotPool(),
|
||||
snapshotCh: snapshotCh,
|
||||
chunkCh: chunkCh,
|
||||
@@ -547,7 +545,7 @@ func (s *syncer) requestChunk(ctx context.Context, snapshot *snapshot, chunk uin
|
||||
|
||||
// verifyApp verifies the sync, checking the app hash, last block height and app version
|
||||
func (s *syncer) verifyApp(ctx context.Context, snapshot *snapshot, appVersion uint64) error {
|
||||
resp, err := s.connQuery.Info(ctx, proxy.RequestInfo)
|
||||
resp, err := s.conn.Info(ctx, proxy.RequestInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to query ABCI app for appHash: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
clientmocks "github.com/tendermint/tendermint/abci/client/mocks"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/internal/proxy"
|
||||
proxymocks "github.com/tendermint/tendermint/internal/proxy/mocks"
|
||||
sm "github.com/tendermint/tendermint/internal/state"
|
||||
"github.com/tendermint/tendermint/internal/statesync/mocks"
|
||||
ssproto "github.com/tendermint/tendermint/proto/tendermint/statesync"
|
||||
@@ -62,13 +62,12 @@ func TestSyncer_SyncAny(t *testing.T) {
|
||||
stateProvider.On("AppHash", mock.Anything, uint64(2)).Return([]byte("app_hash_2"), nil)
|
||||
stateProvider.On("Commit", mock.Anything, uint64(1)).Return(commit, nil)
|
||||
stateProvider.On("State", mock.Anything, uint64(1)).Return(state, nil)
|
||||
connSnapshot := &proxymocks.AppConnSnapshot{}
|
||||
connQuery := &proxymocks.AppConnQuery{}
|
||||
conn := &clientmocks.Client{}
|
||||
|
||||
peerAID := types.NodeID("aa")
|
||||
peerBID := types.NodeID("bb")
|
||||
peerCID := types.NodeID("cc")
|
||||
rts := setup(ctx, t, connSnapshot, connQuery, stateProvider, 4)
|
||||
rts := setup(ctx, t, conn, stateProvider, 4)
|
||||
|
||||
rts.reactor.syncer = rts.syncer
|
||||
|
||||
@@ -110,7 +109,7 @@ func TestSyncer_SyncAny(t *testing.T) {
|
||||
|
||||
// We start a sync, with peers sending back chunks when requested. We first reject the snapshot
|
||||
// with height 2 format 2, and accept the snapshot at height 1.
|
||||
connSnapshot.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
|
||||
conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
|
||||
Snapshot: &abci.Snapshot{
|
||||
Height: 2,
|
||||
Format: 2,
|
||||
@@ -119,7 +118,7 @@ func TestSyncer_SyncAny(t *testing.T) {
|
||||
},
|
||||
AppHash: []byte("app_hash_2"),
|
||||
}).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil)
|
||||
connSnapshot.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
|
||||
conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
|
||||
Snapshot: &abci.Snapshot{
|
||||
Height: s.Height,
|
||||
Format: s.Format,
|
||||
@@ -171,7 +170,7 @@ func TestSyncer_SyncAny(t *testing.T) {
|
||||
// The first time we're applying chunk 2 we tell it to retry the snapshot and discard chunk 1,
|
||||
// which should cause it to keep the existing chunk 0 and 2, and restart restoration from
|
||||
// beginning. We also wait for a little while, to exercise the retry logic in fetchChunks().
|
||||
connSnapshot.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
|
||||
conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
|
||||
Index: 2, Chunk: []byte{1, 1, 2},
|
||||
}).Once().Run(func(args mock.Arguments) { time.Sleep(1 * time.Second) }).Return(
|
||||
&abci.ResponseApplySnapshotChunk{
|
||||
@@ -179,16 +178,16 @@ func TestSyncer_SyncAny(t *testing.T) {
|
||||
RefetchChunks: []uint32{1},
|
||||
}, nil)
|
||||
|
||||
connSnapshot.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
|
||||
conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
|
||||
Index: 0, Chunk: []byte{1, 1, 0},
|
||||
}).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
|
||||
connSnapshot.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
|
||||
conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
|
||||
Index: 1, Chunk: []byte{1, 1, 1},
|
||||
}).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
|
||||
connSnapshot.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
|
||||
conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{
|
||||
Index: 2, Chunk: []byte{1, 1, 2},
|
||||
}).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil)
|
||||
connQuery.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{
|
||||
conn.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{
|
||||
AppVersion: testAppVersion,
|
||||
LastBlockHeight: 1,
|
||||
LastBlockAppHash: []byte("app_hash"),
|
||||
@@ -217,8 +216,7 @@ func TestSyncer_SyncAny(t *testing.T) {
|
||||
require.Equal(t, int64(len(rts.syncer.snapshots.snapshots)), rts.reactor.TotalSnapshots())
|
||||
require.Equal(t, int64(0), rts.reactor.SnapshotChunksCount())
|
||||
|
||||
connSnapshot.AssertExpectations(t)
|
||||
connQuery.AssertExpectations(t)
|
||||
conn.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestSyncer_SyncAny_noSnapshots(t *testing.T) {
|
||||
@@ -228,7 +226,7 @@ func TestSyncer_SyncAny_noSnapshots(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
_, _, err := rts.syncer.SyncAny(ctx, 0, func() error { return nil })
|
||||
require.Equal(t, errNoSnapshots, err)
|
||||
@@ -241,7 +239,7 @@ func TestSyncer_SyncAny_abort(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
|
||||
peerID := types.NodeID("aa")
|
||||
@@ -265,7 +263,7 @@ func TestSyncer_SyncAny_reject(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
// s22 is tried first, then s12, then s11, then errNoSnapshots
|
||||
s22 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
|
||||
@@ -307,7 +305,7 @@ func TestSyncer_SyncAny_reject_format(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
// s22 is tried first, which reject s22 and s12, then s11 will abort.
|
||||
s22 := &snapshot{Height: 2, Format: 2, Chunks: 3, Hash: []byte{1, 2, 3}}
|
||||
@@ -345,7 +343,7 @@ func TestSyncer_SyncAny_reject_sender(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
peerAID := types.NodeID("aa")
|
||||
peerBID := types.NodeID("bb")
|
||||
@@ -394,7 +392,7 @@ func TestSyncer_SyncAny_abciError(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}}
|
||||
@@ -444,7 +442,7 @@ func TestSyncer_offerSnapshot(t *testing.T) {
|
||||
stateProvider := &mocks.StateProvider{}
|
||||
stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")}
|
||||
rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{
|
||||
@@ -497,7 +495,7 @@ func TestSyncer_applyChunks_Results(t *testing.T) {
|
||||
stateProvider := &mocks.StateProvider{}
|
||||
stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
body := []byte{1, 2, 3}
|
||||
chunks, err := newChunkQueue(&snapshot{Height: 1, Format: 1, Chunks: 1}, t.TempDir())
|
||||
@@ -557,7 +555,7 @@ func TestSyncer_applyChunks_RefetchChunks(t *testing.T) {
|
||||
stateProvider := &mocks.StateProvider{}
|
||||
stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
chunks, err := newChunkQueue(&snapshot{Height: 1, Format: 1, Chunks: 3}, t.TempDir())
|
||||
require.NoError(t, err)
|
||||
@@ -628,7 +626,7 @@ func TestSyncer_applyChunks_RejectSenders(t *testing.T) {
|
||||
stateProvider := &mocks.StateProvider{}
|
||||
stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
|
||||
|
||||
rts := setup(ctx, t, nil, nil, stateProvider, 2)
|
||||
rts := setup(ctx, t, nil, stateProvider, 2)
|
||||
|
||||
// Set up three peers across two snapshots, and ask for one of them to be banned.
|
||||
// It should be banned from all snapshots.
|
||||
@@ -761,9 +759,9 @@ func TestSyncer_verifyApp(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
rts := setup(ctx, t, nil, nil, nil, 2)
|
||||
rts := setup(ctx, t, nil, nil, 2)
|
||||
|
||||
rts.connQuery.On("Info", mock.Anything, proxy.RequestInfo).Return(tc.response, tc.err)
|
||||
rts.conn.On("Info", mock.Anything, proxy.RequestInfo).Return(tc.response, tc.err)
|
||||
err := rts.syncer.verifyApp(ctx, s, appVersion)
|
||||
unwrapped := errors.Unwrap(err)
|
||||
if unwrapped != nil {
|
||||
|
||||
@@ -86,11 +86,8 @@ func TestMain(m *testing.M) {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
block, err = factory.MakeBlock(state, 1, new(types.Commit))
|
||||
block = factory.MakeBlock(state, 1, new(types.Commit))
|
||||
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
partSet, err = block.MakePartSet(2)
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
@@ -121,8 +118,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
// save a block
|
||||
block, err := factory.MakeBlock(state, bs.Height()+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := factory.MakeBlock(state, bs.Height()+1, new(types.Commit))
|
||||
validPartSet, err := block.MakePartSet(2)
|
||||
require.NoError(t, err)
|
||||
seenCommit := makeTestCommit(10, tmtime.Now())
|
||||
@@ -326,8 +322,7 @@ func TestLoadBaseMeta(t *testing.T) {
|
||||
bs := NewBlockStore(dbm.NewMemDB())
|
||||
|
||||
for h := int64(1); h <= 10; h++ {
|
||||
block, err := factory.MakeBlock(state, h, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := factory.MakeBlock(state, h, new(types.Commit))
|
||||
partSet, err := block.MakePartSet(2)
|
||||
require.NoError(t, err)
|
||||
seenCommit := makeTestCommit(h, tmtime.Now())
|
||||
@@ -394,8 +389,7 @@ func TestPruneBlocks(t *testing.T) {
|
||||
|
||||
// make more than 1000 blocks, to test batch deletions
|
||||
for h := int64(1); h <= 1500; h++ {
|
||||
block, err := factory.MakeBlock(state, h, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := factory.MakeBlock(state, h, new(types.Commit))
|
||||
partSet, err := block.MakePartSet(2)
|
||||
require.NoError(t, err)
|
||||
seenCommit := makeTestCommit(h, tmtime.Now())
|
||||
@@ -502,8 +496,7 @@ func TestBlockFetchAtHeight(t *testing.T) {
|
||||
defer cleanup()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
|
||||
block, err := factory.MakeBlock(state, bs.Height()+1, new(types.Commit))
|
||||
require.NoError(t, err)
|
||||
block := factory.MakeBlock(state, bs.Height()+1, new(types.Commit))
|
||||
|
||||
partSet, err := block.MakePartSet(2)
|
||||
require.NoError(t, err)
|
||||
@@ -545,8 +538,7 @@ func TestSeenAndCanonicalCommit(t *testing.T) {
|
||||
// are persisted.
|
||||
for h := int64(3); h <= 5; h++ {
|
||||
blockCommit := makeTestCommit(h-1, tmtime.Now())
|
||||
block, err := factory.MakeBlock(state, h, blockCommit)
|
||||
require.NoError(t, err)
|
||||
block := factory.MakeBlock(state, h, blockCommit)
|
||||
partSet, err := block.MakePartSet(2)
|
||||
require.NoError(t, err)
|
||||
seenCommit := makeTestCommit(h, tmtime.Now())
|
||||
|
||||
Reference in New Issue
Block a user