github.com/evdatsion/aphelion-dpos-bft@v0.32.1/consensus/wal_generator.go (about) 1 package consensus 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "path/filepath" 9 "testing" 10 "time" 11 12 "github.com/pkg/errors" 13 14 "github.com/evdatsion/aphelion-dpos-bft/abci/example/kvstore" 15 bc "github.com/evdatsion/aphelion-dpos-bft/blockchain" 16 cfg "github.com/evdatsion/aphelion-dpos-bft/config" 17 cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common" 18 "github.com/evdatsion/aphelion-dpos-bft/libs/db" 19 "github.com/evdatsion/aphelion-dpos-bft/libs/log" 20 "github.com/evdatsion/aphelion-dpos-bft/mock" 21 "github.com/evdatsion/aphelion-dpos-bft/privval" 22 "github.com/evdatsion/aphelion-dpos-bft/proxy" 23 sm "github.com/evdatsion/aphelion-dpos-bft/state" 24 "github.com/evdatsion/aphelion-dpos-bft/types" 25 ) 26 27 // WALGenerateNBlocks generates a consensus WAL. It does this by spinning up a 28 // stripped down version of node (proxy app, event bus, consensus state) with a 29 // persistent kvstore application and special consensus wal instance 30 // (byteBufferWAL) and waits until numBlocks are created. If the node fails to produce given numBlocks, it returns an error. 31 func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) { 32 config := getConfig(t) 33 34 app := kvstore.NewPersistentKVStoreApplication(filepath.Join(config.DBDir(), "wal_generator")) 35 36 logger := log.TestingLogger().With("wal_generator", "wal_generator") 37 logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks) 38 39 ///////////////////////////////////////////////////////////////////////////// 40 // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS 41 // NOTE: we can't import node package because of circular dependency. 42 // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly. 43 privValidatorKeyFile := config.PrivValidatorKeyFile() 44 privValidatorStateFile := config.PrivValidatorStateFile() 45 privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) 46 genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) 47 if err != nil { 48 return errors.Wrap(err, "failed to read genesis file") 49 } 50 blockStoreDB := db.NewMemDB() 51 stateDB := blockStoreDB 52 state, err := sm.MakeGenesisState(genDoc) 53 if err != nil { 54 return errors.Wrap(err, "failed to make genesis state") 55 } 56 state.Version.Consensus.App = kvstore.ProtocolVersion 57 sm.SaveState(stateDB, state) 58 blockStore := bc.NewBlockStore(blockStoreDB) 59 proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app)) 60 proxyApp.SetLogger(logger.With("module", "proxy")) 61 if err := proxyApp.Start(); err != nil { 62 return errors.Wrap(err, "failed to start proxy app connections") 63 } 64 defer proxyApp.Stop() 65 66 eventBus := types.NewEventBus() 67 eventBus.SetLogger(logger.With("module", "events")) 68 if err := eventBus.Start(); err != nil { 69 return errors.Wrap(err, "failed to start event bus") 70 } 71 defer eventBus.Stop() 72 mempool := mock.Mempool{} 73 evpool := sm.MockEvidencePool{} 74 blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) 75 consensusState := NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool) 76 consensusState.SetLogger(logger) 77 consensusState.SetEventBus(eventBus) 78 if privValidator != nil { 79 consensusState.SetPrivValidator(privValidator) 80 } 81 // END OF COPY PASTE 82 ///////////////////////////////////////////////////////////////////////////// 83 84 // set consensus wal to buffered WAL, which will write all incoming msgs to buffer 85 numBlocksWritten := make(chan struct{}) 86 wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten) 87 // see wal.go#103 88 wal.Write(EndHeightMessage{0}) 89 consensusState.wal = wal 90 91 if err := consensusState.Start(); err != nil { 92 return errors.Wrap(err, "failed to start consensus state") 93 } 94 95 select { 96 case <-numBlocksWritten: 97 consensusState.Stop() 98 return nil 99 case <-time.After(1 * time.Minute): 100 consensusState.Stop() 101 return fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks) 102 } 103 } 104 105 //WALWithNBlocks returns a WAL content with numBlocks. 106 func WALWithNBlocks(t *testing.T, numBlocks int) (data []byte, err error) { 107 var b bytes.Buffer 108 wr := bufio.NewWriter(&b) 109 110 if err := WALGenerateNBlocks(t, wr, numBlocks); err != nil { 111 return []byte{}, err 112 } 113 114 wr.Flush() 115 return b.Bytes(), nil 116 } 117 118 func randPort() int { 119 // returns between base and base + spread 120 base, spread := 20000, 20000 121 return base + cmn.RandIntn(spread) 122 } 123 124 func makeAddrs() (string, string, string) { 125 start := randPort() 126 return fmt.Sprintf("tcp://0.0.0.0:%d", start), 127 fmt.Sprintf("tcp://0.0.0.0:%d", start+1), 128 fmt.Sprintf("tcp://0.0.0.0:%d", start+2) 129 } 130 131 // getConfig returns a config for test cases 132 func getConfig(t *testing.T) *cfg.Config { 133 c := cfg.ResetTestRoot(t.Name()) 134 135 // and we use random ports to run in parallel 136 tm, rpc, grpc := makeAddrs() 137 c.P2P.ListenAddress = tm 138 c.RPC.ListenAddress = rpc 139 c.RPC.GRPCListenAddress = grpc 140 return c 141 } 142 143 // byteBufferWAL is a WAL which writes all msgs to a byte buffer. Writing stops 144 // when the heightToStop is reached. Client will be notified via 145 // signalWhenStopsTo channel. 146 type byteBufferWAL struct { 147 enc *WALEncoder 148 stopped bool 149 heightToStop int64 150 signalWhenStopsTo chan<- struct{} 151 152 logger log.Logger 153 } 154 155 // needed for determinism 156 var fixedTime, _ = time.Parse(time.RFC3339, "2017-01-02T15:04:05Z") 157 158 func newByteBufferWAL(logger log.Logger, enc *WALEncoder, nBlocks int64, signalStop chan<- struct{}) *byteBufferWAL { 159 return &byteBufferWAL{ 160 enc: enc, 161 heightToStop: nBlocks, 162 signalWhenStopsTo: signalStop, 163 logger: logger, 164 } 165 } 166 167 // Save writes message to the internal buffer except when heightToStop is 168 // reached, in which case it will signal the caller via signalWhenStopsTo and 169 // skip writing. 170 func (w *byteBufferWAL) Write(m WALMessage) { 171 if w.stopped { 172 w.logger.Debug("WAL already stopped. Not writing message", "msg", m) 173 return 174 } 175 176 if endMsg, ok := m.(EndHeightMessage); ok { 177 w.logger.Debug("WAL write end height message", "height", endMsg.Height, "stopHeight", w.heightToStop) 178 if endMsg.Height == w.heightToStop { 179 w.logger.Debug("Stopping WAL at height", "height", endMsg.Height) 180 w.signalWhenStopsTo <- struct{}{} 181 w.stopped = true 182 return 183 } 184 } 185 186 w.logger.Debug("WAL Write Message", "msg", m) 187 err := w.enc.Encode(&TimedWALMessage{fixedTime, m}) 188 if err != nil { 189 panic(fmt.Sprintf("failed to encode the msg %v", m)) 190 } 191 } 192 193 func (w *byteBufferWAL) WriteSync(m WALMessage) { 194 w.Write(m) 195 } 196 197 func (w *byteBufferWAL) FlushAndSync() error { return nil } 198 199 func (w *byteBufferWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) { 200 return nil, false, nil 201 } 202 203 func (w *byteBufferWAL) Start() error { return nil } 204 func (w *byteBufferWAL) Stop() error { return nil } 205 func (w *byteBufferWAL) Wait() {}