You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
14 KiB

7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
new pubsub package comment out failing consensus tests for now rewrite rpc httpclient to use new pubsub package import pubsub as tmpubsub, query as tmquery make event IDs constants EventKey -> EventTypeKey rename EventsPubsub to PubSub mempool does not use pubsub rename eventsSub to pubsub new subscribe API fix channel size issues and consensus tests bugs refactor rpc client add missing discardFromChan method add mutex rename pubsub to eventBus remove IsRunning from WSRPCConnection interface (not needed) add a comment in broadcastNewRoundStepsAndVotes rename registerEventCallbacks to broadcastNewRoundStepsAndVotes See https://dave.cheney.net/2014/03/19/channel-axioms stop eventBuses after reactor tests remove unnecessary Unsubscribe return subscribe helper function move discardFromChan to where it is used subscribe now returns an err this gives us ability to refuse to subscribe if pubsub is at its max capacity. use context for control overflow cache queries handle err when subscribing in replay_test rename testClientID to testSubscriber extract var set channel buffer capacity to 1 in replay_file fix byzantine_test unsubscribe from single event, not all events refactor httpclient to return events to appropriate channels return failing testReplayCrashBeforeWriteVote test fix TestValidatorSetChanges refactor code a bit fix testReplayCrashBeforeWriteVote add comment fix TestValidatorSetChanges fixes from Bucky's review update comment [ci skip] test TxEventBuffer update changelog fix TestValidatorSetChanges (2nd attempt) only do wg.Done when no errors benchmark event bus create pubsub server inside NewEventBus only expose config params (later if needed) set buffer capacity to 0 so we are not testing cache new tx event format: key = "Tx" plus a tag {"tx.hash": XYZ} This should allow to subscribe to all transactions! or a specific one using a query: "tm.events.type = Tx and tx.hash = '013ABF99434...'" use TimeoutCommit instead of afterPublishEventNewBlockTimeout TimeoutCommit is the time a node waits after committing a block, before it goes into the next height. So it will finish everything from the last block, but then wait a bit. The idea is this gives it time to hear more votes from other validators, to strengthen the commit it includes in the next block. But it also gives it time to hear about new transactions. waitForBlockWithUpdatedVals rewrite WAL crash tests Task: test that we can recover from any WAL crash. Solution: the old tests were relying on event hub being run in the same thread (we were injecting the private validator's last signature). when considering a rewrite, we considered two possible solutions: write a "fuzzy" testing system where WAL is crashing upon receiving a new message, or inject failures and trigger them in tests using something like https://github.com/coreos/gofail. remove sleep no cs.Lock around wal.Save test different cases (empty block, non-empty block, ...) comments add comments test 4 cases: empty block, non-empty block, non-empty block with smaller part size, many blocks fixes as per Bucky's last review reset subscriptions on UnsubscribeAll use a simple counter to track message for which we panicked also, set a smaller part size for all test cases
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
8 years ago
8 years ago
8 years ago
new pubsub package comment out failing consensus tests for now rewrite rpc httpclient to use new pubsub package import pubsub as tmpubsub, query as tmquery make event IDs constants EventKey -> EventTypeKey rename EventsPubsub to PubSub mempool does not use pubsub rename eventsSub to pubsub new subscribe API fix channel size issues and consensus tests bugs refactor rpc client add missing discardFromChan method add mutex rename pubsub to eventBus remove IsRunning from WSRPCConnection interface (not needed) add a comment in broadcastNewRoundStepsAndVotes rename registerEventCallbacks to broadcastNewRoundStepsAndVotes See https://dave.cheney.net/2014/03/19/channel-axioms stop eventBuses after reactor tests remove unnecessary Unsubscribe return subscribe helper function move discardFromChan to where it is used subscribe now returns an err this gives us ability to refuse to subscribe if pubsub is at its max capacity. use context for control overflow cache queries handle err when subscribing in replay_test rename testClientID to testSubscriber extract var set channel buffer capacity to 1 in replay_file fix byzantine_test unsubscribe from single event, not all events refactor httpclient to return events to appropriate channels return failing testReplayCrashBeforeWriteVote test fix TestValidatorSetChanges refactor code a bit fix testReplayCrashBeforeWriteVote add comment fix TestValidatorSetChanges fixes from Bucky's review update comment [ci skip] test TxEventBuffer update changelog fix TestValidatorSetChanges (2nd attempt) only do wg.Done when no errors benchmark event bus create pubsub server inside NewEventBus only expose config params (later if needed) set buffer capacity to 0 so we are not testing cache new tx event format: key = "Tx" plus a tag {"tx.hash": XYZ} This should allow to subscribe to all transactions! or a specific one using a query: "tm.events.type = Tx and tx.hash = '013ABF99434...'" use TimeoutCommit instead of afterPublishEventNewBlockTimeout TimeoutCommit is the time a node waits after committing a block, before it goes into the next height. So it will finish everything from the last block, but then wait a bit. The idea is this gives it time to hear more votes from other validators, to strengthen the commit it includes in the next block. But it also gives it time to hear about new transactions. waitForBlockWithUpdatedVals rewrite WAL crash tests Task: test that we can recover from any WAL crash. Solution: the old tests were relying on event hub being run in the same thread (we were injecting the private validator's last signature). when considering a rewrite, we considered two possible solutions: write a "fuzzy" testing system where WAL is crashing upon receiving a new message, or inject failures and trigger them in tests using something like https://github.com/coreos/gofail. remove sleep no cs.Lock around wal.Save test different cases (empty block, non-empty block, ...) comments add comments test 4 cases: empty block, non-empty block, non-empty block with smaller part size, many blocks fixes as per Bucky's last review reset subscriptions on UnsubscribeAll use a simple counter to track message for which we panicked also, set a smaller part size for all test cases
8 years ago
  1. package consensus
  2. import (
  3. "bytes"
  4. "fmt"
  5. "hash/crc32"
  6. "io"
  7. "reflect"
  8. //"strconv"
  9. //"strings"
  10. "time"
  11. abci "github.com/tendermint/abci/types"
  12. //auto "github.com/tendermint/tmlibs/autofile"
  13. cmn "github.com/tendermint/tmlibs/common"
  14. "github.com/tendermint/tmlibs/log"
  15. "github.com/tendermint/tendermint/proxy"
  16. sm "github.com/tendermint/tendermint/state"
  17. "github.com/tendermint/tendermint/types"
  18. "github.com/tendermint/tendermint/version"
  19. )
  20. var crc32c = crc32.MakeTable(crc32.Castagnoli)
  21. // Functionality to replay blocks and messages on recovery from a crash.
  22. // There are two general failure scenarios: failure during consensus, and failure while applying the block.
  23. // The former is handled by the WAL, the latter by the proxyApp Handshake on restart,
  24. // which ultimately hands off the work to the WAL.
  25. //-----------------------------------------
  26. // recover from failure during consensus
  27. // by replaying messages from the WAL
  28. // Unmarshal and apply a single message to the consensus state
  29. // as if it were received in receiveRoutine
  30. // Lines that start with "#" are ignored.
  31. // NOTE: receiveRoutine should not be running
  32. func (cs *ConsensusState) readReplayMessage(msg *TimedWALMessage, newStepCh chan interface{}) error {
  33. // skip meta messages
  34. if _, ok := msg.Msg.(EndHeightMessage); ok {
  35. return nil
  36. }
  37. // for logging
  38. switch m := msg.Msg.(type) {
  39. case types.EventDataRoundState:
  40. cs.Logger.Info("Replay: New Step", "height", m.Height, "round", m.Round, "step", m.Step)
  41. // these are playback checks
  42. ticker := time.After(time.Second * 2)
  43. if newStepCh != nil {
  44. select {
  45. case mi := <-newStepCh:
  46. m2 := mi.(types.EventDataRoundState)
  47. if m.Height != m2.Height || m.Round != m2.Round || m.Step != m2.Step {
  48. return fmt.Errorf("RoundState mismatch. Got %v; Expected %v", m2, m)
  49. }
  50. case <-ticker:
  51. return fmt.Errorf("Failed to read off newStepCh")
  52. }
  53. }
  54. case msgInfo:
  55. peerKey := m.PeerKey
  56. if peerKey == "" {
  57. peerKey = "local"
  58. }
  59. switch msg := m.Msg.(type) {
  60. case *ProposalMessage:
  61. p := msg.Proposal
  62. cs.Logger.Info("Replay: Proposal", "height", p.Height, "round", p.Round, "header",
  63. p.BlockPartsHeader, "pol", p.POLRound, "peer", peerKey)
  64. case *BlockPartMessage:
  65. cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerKey)
  66. case *VoteMessage:
  67. v := msg.Vote
  68. cs.Logger.Info("Replay: Vote", "height", v.Height, "round", v.Round, "type", v.Type,
  69. "blockID", v.BlockID, "peer", peerKey)
  70. }
  71. cs.handleMsg(m)
  72. case timeoutInfo:
  73. cs.Logger.Info("Replay: Timeout", "height", m.Height, "round", m.Round, "step", m.Step, "dur", m.Duration)
  74. cs.handleTimeout(m, cs.RoundState)
  75. default:
  76. return fmt.Errorf("Replay: Unknown TimedWALMessage type: %v", reflect.TypeOf(msg.Msg))
  77. }
  78. return nil
  79. }
  80. // replay only those messages since the last block.
  81. // timeoutRoutine should run concurrently to read off tickChan
  82. func (cs *ConsensusState) catchupReplay(csHeight int64) error {
  83. // set replayMode
  84. cs.replayMode = true
  85. defer func() { cs.replayMode = false }()
  86. // Ensure that ENDHEIGHT for this height doesn't exist.
  87. // NOTE: This is just a sanity check. As far as we know things work fine
  88. // without it, and Handshake could reuse ConsensusState if it weren't for
  89. // this check (since we can crash after writing ENDHEIGHT).
  90. //
  91. // Ignore data corruption errors since this is a sanity check.
  92. gr, found, err := cs.wal.SearchForEndHeight(csHeight, &WALSearchOptions{IgnoreDataCorruptionErrors: true})
  93. if err != nil {
  94. return err
  95. }
  96. if gr != nil {
  97. if err := gr.Close(); err != nil {
  98. return err
  99. }
  100. }
  101. if found {
  102. return fmt.Errorf("WAL should not contain #ENDHEIGHT %d.", csHeight)
  103. }
  104. // Search for last height marker
  105. //
  106. // Ignore data corruption errors in previous heights because we only care about last height
  107. gr, found, err = cs.wal.SearchForEndHeight(csHeight-1, &WALSearchOptions{IgnoreDataCorruptionErrors: true})
  108. if err == io.EOF {
  109. cs.Logger.Error("Replay: wal.group.Search returned EOF", "#ENDHEIGHT", csHeight-1)
  110. } else if err != nil {
  111. return err
  112. }
  113. if !found {
  114. return fmt.Errorf("Cannot replay height %d. WAL does not contain #ENDHEIGHT for %d.", csHeight, csHeight-1)
  115. }
  116. defer gr.Close() // nolint: errcheck
  117. cs.Logger.Info("Catchup by replaying consensus messages", "height", csHeight)
  118. var msg *TimedWALMessage
  119. dec := WALDecoder{gr}
  120. for {
  121. msg, err = dec.Decode()
  122. if err == io.EOF {
  123. break
  124. } else if IsDataCorruptionError(err) {
  125. cs.Logger.Debug("data has been corrupted in last height of consensus WAL", "err", err, "height", csHeight)
  126. panic(fmt.Sprintf("data has been corrupted (%v) in last height %d of consensus WAL", err, csHeight))
  127. } else if err != nil {
  128. return err
  129. }
  130. // NOTE: since the priv key is set when the msgs are received
  131. // it will attempt to eg double sign but we can just ignore it
  132. // since the votes will be replayed and we'll get to the next step
  133. if err := cs.readReplayMessage(msg, nil); err != nil {
  134. return err
  135. }
  136. }
  137. cs.Logger.Info("Replay: Done")
  138. return nil
  139. }
  140. //--------------------------------------------------------------------------------
  141. // Parses marker lines of the form:
  142. // #ENDHEIGHT: 12345
  143. /*
  144. func makeHeightSearchFunc(height int64) auto.SearchFunc {
  145. return func(line string) (int, error) {
  146. line = strings.TrimRight(line, "\n")
  147. parts := strings.Split(line, " ")
  148. if len(parts) != 2 {
  149. return -1, errors.New("Line did not have 2 parts")
  150. }
  151. i, err := strconv.Atoi(parts[1])
  152. if err != nil {
  153. return -1, errors.New("Failed to parse INFO: " + err.Error())
  154. }
  155. if height < i {
  156. return 1, nil
  157. } else if height == i {
  158. return 0, nil
  159. } else {
  160. return -1, nil
  161. }
  162. }
  163. }*/
  164. //----------------------------------------------
  165. // Recover from failure during block processing
  166. // by handshaking with the app to figure out where
  167. // we were last and using the WAL to recover there
  168. type Handshaker struct {
  169. state *sm.State
  170. store types.BlockStore
  171. logger log.Logger
  172. nBlocks int // number of blocks applied to the state
  173. }
  174. func NewHandshaker(state *sm.State, store types.BlockStore) *Handshaker {
  175. return &Handshaker{state, store, log.NewNopLogger(), 0}
  176. }
  177. func (h *Handshaker) SetLogger(l log.Logger) {
  178. h.logger = l
  179. }
  180. func (h *Handshaker) NBlocks() int {
  181. return h.nBlocks
  182. }
  183. // TODO: retry the handshake/replay if it fails ?
  184. func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
  185. // handshake is done via info request on the query conn
  186. res, err := proxyApp.Query().InfoSync(abci.RequestInfo{version.Version})
  187. if err != nil {
  188. return fmt.Errorf("Error calling Info: %v", err)
  189. }
  190. blockHeight := int64(res.LastBlockHeight)
  191. if blockHeight < 0 {
  192. return fmt.Errorf("Got a negative last block height (%d) from the app", blockHeight)
  193. }
  194. appHash := res.LastBlockAppHash
  195. h.logger.Info("ABCI Handshake", "appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash))
  196. // TODO: check version
  197. // replay blocks up to the latest in the blockstore
  198. _, err = h.ReplayBlocks(appHash, blockHeight, proxyApp)
  199. if err != nil {
  200. return fmt.Errorf("Error on replay: %v", err)
  201. }
  202. h.logger.Info("Completed ABCI Handshake - Tendermint and App are synced", "appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash))
  203. // TODO: (on restart) replay mempool
  204. return nil
  205. }
  206. // Replay all blocks since appBlockHeight and ensure the result matches the current state.
  207. // Returns the final AppHash or an error
  208. func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int64, proxyApp proxy.AppConns) ([]byte, error) {
  209. storeBlockHeight := h.store.Height()
  210. stateBlockHeight := h.state.LastBlockHeight
  211. h.logger.Info("ABCI Replay Blocks", "appHeight", appBlockHeight, "storeHeight", storeBlockHeight, "stateHeight", stateBlockHeight)
  212. // If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain
  213. if appBlockHeight == 0 {
  214. validators := types.TM2PB.Validators(h.state.Validators)
  215. if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{validators}); err != nil {
  216. return nil, err
  217. }
  218. }
  219. // First handle edge cases and constraints on the storeBlockHeight
  220. if storeBlockHeight == 0 {
  221. return appHash, h.checkAppHash(appHash)
  222. } else if storeBlockHeight < appBlockHeight {
  223. // the app should never be ahead of the store (but this is under app's control)
  224. return appHash, sm.ErrAppBlockHeightTooHigh{storeBlockHeight, appBlockHeight}
  225. } else if storeBlockHeight < stateBlockHeight {
  226. // the state should never be ahead of the store (this is under tendermint's control)
  227. cmn.PanicSanity(cmn.Fmt("StateBlockHeight (%d) > StoreBlockHeight (%d)", stateBlockHeight, storeBlockHeight))
  228. } else if storeBlockHeight > stateBlockHeight+1 {
  229. // store should be at most one ahead of the state (this is under tendermint's control)
  230. cmn.PanicSanity(cmn.Fmt("StoreBlockHeight (%d) > StateBlockHeight + 1 (%d)", storeBlockHeight, stateBlockHeight+1))
  231. }
  232. // Now either store is equal to state, or one ahead.
  233. // For each, consider all cases of where the app could be, given app <= store
  234. if storeBlockHeight == stateBlockHeight {
  235. // Tendermint ran Commit and saved the state.
  236. // Either the app is asking for replay, or we're all synced up.
  237. if appBlockHeight < storeBlockHeight {
  238. // the app is behind, so replay blocks, but no need to go through WAL (state is already synced to store)
  239. return h.replayBlocks(proxyApp, appBlockHeight, storeBlockHeight, false)
  240. } else if appBlockHeight == storeBlockHeight {
  241. // We're good!
  242. return appHash, h.checkAppHash(appHash)
  243. }
  244. } else if storeBlockHeight == stateBlockHeight+1 {
  245. // We saved the block in the store but haven't updated the state,
  246. // so we'll need to replay a block using the WAL.
  247. if appBlockHeight < stateBlockHeight {
  248. // the app is further behind than it should be, so replay blocks
  249. // but leave the last block to go through the WAL
  250. return h.replayBlocks(proxyApp, appBlockHeight, storeBlockHeight, true)
  251. } else if appBlockHeight == stateBlockHeight {
  252. // We haven't run Commit (both the state and app are one block behind),
  253. // so replayBlock with the real app.
  254. // NOTE: We could instead use the cs.WAL on cs.Start,
  255. // but we'd have to allow the WAL to replay a block that wrote it's ENDHEIGHT
  256. h.logger.Info("Replay last block using real app")
  257. return h.replayBlock(storeBlockHeight, proxyApp.Consensus())
  258. } else if appBlockHeight == storeBlockHeight {
  259. // We ran Commit, but didn't save the state, so replayBlock with mock app
  260. abciResponses, err := h.state.LoadABCIResponses(storeBlockHeight)
  261. if err != nil {
  262. return nil, err
  263. }
  264. mockApp := newMockProxyApp(appHash, abciResponses)
  265. h.logger.Info("Replay last block using mock app")
  266. return h.replayBlock(storeBlockHeight, mockApp)
  267. }
  268. }
  269. cmn.PanicSanity("Should never happen")
  270. return nil, nil
  271. }
  272. func (h *Handshaker) replayBlocks(proxyApp proxy.AppConns, appBlockHeight, storeBlockHeight int64, mutateState bool) ([]byte, error) {
  273. // App is further behind than it should be, so we need to replay blocks.
  274. // We replay all blocks from appBlockHeight+1.
  275. //
  276. // Note that we don't have an old version of the state,
  277. // so we by-pass state validation/mutation using sm.ExecCommitBlock.
  278. // This also means we won't be saving validator sets if they change during this period.
  279. //
  280. // If mutateState == true, the final block is replayed with h.replayBlock()
  281. var appHash []byte
  282. var err error
  283. finalBlock := storeBlockHeight
  284. if mutateState {
  285. finalBlock -= 1
  286. }
  287. for i := appBlockHeight + 1; i <= finalBlock; i++ {
  288. h.logger.Info("Applying block", "height", i)
  289. block := h.store.LoadBlock(i)
  290. appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger, h.state.LastValidators)
  291. if err != nil {
  292. return nil, err
  293. }
  294. h.nBlocks += 1
  295. }
  296. if mutateState {
  297. // sync the final block
  298. return h.replayBlock(storeBlockHeight, proxyApp.Consensus())
  299. }
  300. return appHash, h.checkAppHash(appHash)
  301. }
  302. // ApplyBlock on the proxyApp with the last block.
  303. func (h *Handshaker) replayBlock(height int64, proxyApp proxy.AppConnConsensus) ([]byte, error) {
  304. mempool := types.MockMempool{}
  305. block := h.store.LoadBlock(height)
  306. meta := h.store.LoadBlockMeta(height)
  307. if err := h.state.ApplyBlock(types.NopEventBus{}, proxyApp, block, meta.BlockID.PartsHeader, mempool); err != nil {
  308. return nil, err
  309. }
  310. h.nBlocks += 1
  311. return h.state.AppHash, nil
  312. }
  313. func (h *Handshaker) checkAppHash(appHash []byte) error {
  314. if !bytes.Equal(h.state.AppHash, appHash) {
  315. panic(fmt.Errorf("Tendermint state.AppHash does not match AppHash after replay. Got %X, expected %X", appHash, h.state.AppHash).Error())
  316. }
  317. return nil
  318. }
  319. //--------------------------------------------------------------------------------
  320. // mockProxyApp uses ABCIResponses to give the right results
  321. // Useful because we don't want to call Commit() twice for the same block on the real app.
  322. func newMockProxyApp(appHash []byte, abciResponses *sm.ABCIResponses) proxy.AppConnConsensus {
  323. clientCreator := proxy.NewLocalClientCreator(&mockProxyApp{
  324. appHash: appHash,
  325. abciResponses: abciResponses,
  326. })
  327. cli, _ := clientCreator.NewABCIClient()
  328. err := cli.Start()
  329. if err != nil {
  330. panic(err)
  331. }
  332. return proxy.NewAppConnConsensus(cli)
  333. }
  334. type mockProxyApp struct {
  335. abci.BaseApplication
  336. appHash []byte
  337. txCount int
  338. abciResponses *sm.ABCIResponses
  339. }
  340. func (mock *mockProxyApp) DeliverTx(tx []byte) abci.ResponseDeliverTx {
  341. r := mock.abciResponses.DeliverTx[mock.txCount]
  342. mock.txCount += 1
  343. return *r
  344. }
  345. func (mock *mockProxyApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
  346. mock.txCount = 0
  347. return *mock.abciResponses.EndBlock
  348. }
  349. func (mock *mockProxyApp) Commit() abci.ResponseCommit {
  350. return abci.ResponseCommit{Code: abci.CodeTypeOK, Data: mock.appHash}
  351. }