github.com/Finschia/finschia-sdk@v0.49.1/store/streaming/file/service.go (about) 1 package file 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path" 8 "path/filepath" 9 "sync" 10 11 ocabci "github.com/Finschia/ostracon/abci/types" 12 abci "github.com/tendermint/tendermint/abci/types" 13 14 "github.com/Finschia/finschia-sdk/baseapp" 15 "github.com/Finschia/finschia-sdk/codec" 16 "github.com/Finschia/finschia-sdk/store/types" 17 sdk "github.com/Finschia/finschia-sdk/types" 18 ) 19 20 var _ baseapp.StreamingService = &StreamingService{} 21 22 // StreamingService is a concrete implementation of StreamingService that writes state changes out to files 23 type StreamingService struct { 24 listeners map[types.StoreKey][]types.WriteListener // the listeners that will be initialized with BaseApp 25 srcChan <-chan []byte // the channel that all the WriteListeners write their data out to 26 filePrefix string // optional prefix for each of the generated files 27 writeDir string // directory to write files into 28 codec codec.BinaryCodec // marshaller used for re-marshaling the ABCI messages to write them out to the destination files 29 stateCache [][]byte // cache the protobuf binary encoded StoreKVPairs in the order they are received 30 stateCacheLock *sync.Mutex // mutex for the state cache 31 currentBlockNumber int64 // the current block number 32 currentTxIndex int64 // the index of the current tx 33 quitChan chan struct{} // channel to synchronize closure 34 } 35 36 // IntermediateWriter is used so that we do not need to update the underlying io.Writer 37 // inside the StoreKVPairWriteListener everytime we begin writing to a new file 38 type IntermediateWriter struct { 39 outChan chan<- []byte 40 } 41 42 // NewIntermediateWriter create an instance of an intermediateWriter that sends to the provided channel 43 func NewIntermediateWriter(outChan chan<- []byte) *IntermediateWriter { 44 return &IntermediateWriter{ 45 outChan: outChan, 46 } 47 } 48 49 // Write satisfies io.Writer 50 func (iw *IntermediateWriter) Write(b []byte) (int, error) { 51 iw.outChan <- b 52 return len(b), nil 53 } 54 55 // NewStreamingService creates a new StreamingService for the provided writeDir, (optional) filePrefix, and storeKeys 56 func NewStreamingService(writeDir, filePrefix string, storeKeys []types.StoreKey, c codec.BinaryCodec) (*StreamingService, error) { 57 listenChan := make(chan []byte) 58 iw := NewIntermediateWriter(listenChan) 59 listener := types.NewStoreKVPairWriteListener(iw, c) 60 listeners := make(map[types.StoreKey][]types.WriteListener, len(storeKeys)) 61 // in this case, we are using the same listener for each Store 62 for _, key := range storeKeys { 63 listeners[key] = append(listeners[key], listener) 64 } 65 // check that the writeDir exists and is writeable so that we can catch the error here at initialization if it is not 66 // we don't open a dstFile until we receive our first ABCI message 67 if err := isDirWriteable(writeDir); err != nil { 68 return nil, err 69 } 70 return &StreamingService{ 71 listeners: listeners, 72 srcChan: listenChan, 73 filePrefix: filePrefix, 74 writeDir: writeDir, 75 codec: c, 76 stateCache: make([][]byte, 0), 77 stateCacheLock: new(sync.Mutex), 78 }, nil 79 } 80 81 // Listeners satisfies the baseapp.StreamingService interface 82 // It returns the StreamingService's underlying WriteListeners 83 // Use for registering the underlying WriteListeners with the BaseApp 84 func (fss *StreamingService) Listeners() map[types.StoreKey][]types.WriteListener { 85 return fss.listeners 86 } 87 88 // ListenBeginBlock satisfies the baseapp.ABCIListener interface 89 // It writes the received BeginBlock request and response and the resulting state changes 90 // out to a file as described in the above the naming schema 91 func (fss *StreamingService) ListenBeginBlock(ctx sdk.Context, req ocabci.RequestBeginBlock, res abci.ResponseBeginBlock) error { 92 // generate the new file 93 dstFile, err := fss.openBeginBlockFile(req) 94 if err != nil { 95 return err 96 } 97 // write req to file 98 lengthPrefixedReqBytes, err := fss.codec.MarshalLengthPrefixed(&req) 99 if err != nil { 100 return err 101 } 102 if _, err = dstFile.Write(lengthPrefixedReqBytes); err != nil { 103 return err 104 } 105 // write all state changes cached for this stage to file 106 fss.stateCacheLock.Lock() 107 for _, stateChange := range fss.stateCache { 108 if _, err = dstFile.Write(stateChange); err != nil { 109 fss.stateCache = nil 110 fss.stateCacheLock.Unlock() 111 return err 112 } 113 } 114 // reset cache 115 fss.stateCache = nil 116 fss.stateCacheLock.Unlock() 117 // write res to file 118 lengthPrefixedResBytes, err := fss.codec.MarshalLengthPrefixed(&res) 119 if err != nil { 120 return err 121 } 122 if _, err = dstFile.Write(lengthPrefixedResBytes); err != nil { 123 return err 124 } 125 // close file 126 return dstFile.Close() 127 } 128 129 func (fss *StreamingService) openBeginBlockFile(req ocabci.RequestBeginBlock) (*os.File, error) { 130 fss.currentBlockNumber = req.GetHeader().Height 131 fss.currentTxIndex = 0 132 fileName := fmt.Sprintf("block-%d-begin", fss.currentBlockNumber) 133 if fss.filePrefix != "" { 134 fileName = fmt.Sprintf("%s-%s", fss.filePrefix, fileName) 135 } 136 return os.OpenFile(filepath.Join(fss.writeDir, fileName), os.O_CREATE|os.O_WRONLY, 0o600) 137 } 138 139 // ListenDeliverTx satisfies the baseapp.ABCIListener interface 140 // It writes the received DeliverTx request and response and the resulting state changes 141 // out to a file as described in the above the naming schema 142 func (fss *StreamingService) ListenDeliverTx(ctx sdk.Context, req abci.RequestDeliverTx, res abci.ResponseDeliverTx) error { 143 // generate the new file 144 dstFile, err := fss.openDeliverTxFile() 145 if err != nil { 146 return err 147 } 148 // write req to file 149 lengthPrefixedReqBytes, err := fss.codec.MarshalLengthPrefixed(&req) 150 if err != nil { 151 return err 152 } 153 if _, err = dstFile.Write(lengthPrefixedReqBytes); err != nil { 154 return err 155 } 156 // write all state changes cached for this stage to file 157 fss.stateCacheLock.Lock() 158 for _, stateChange := range fss.stateCache { 159 if _, err = dstFile.Write(stateChange); err != nil { 160 fss.stateCache = nil 161 fss.stateCacheLock.Unlock() 162 return err 163 } 164 } 165 // reset cache 166 fss.stateCache = nil 167 fss.stateCacheLock.Unlock() 168 // write res to file 169 lengthPrefixedResBytes, err := fss.codec.MarshalLengthPrefixed(&res) 170 if err != nil { 171 return err 172 } 173 if _, err = dstFile.Write(lengthPrefixedResBytes); err != nil { 174 return err 175 } 176 // close file 177 return dstFile.Close() 178 } 179 180 func (fss *StreamingService) openDeliverTxFile() (*os.File, error) { 181 fileName := fmt.Sprintf("block-%d-tx-%d", fss.currentBlockNumber, fss.currentTxIndex) 182 if fss.filePrefix != "" { 183 fileName = fmt.Sprintf("%s-%s", fss.filePrefix, fileName) 184 } 185 fss.currentTxIndex++ 186 return os.OpenFile(filepath.Join(fss.writeDir, fileName), os.O_CREATE|os.O_WRONLY, 0o600) 187 } 188 189 // ListenEndBlock satisfies the baseapp.ABCIListener interface 190 // It writes the received EndBlock request and response and the resulting state changes 191 // out to a file as described in the above the naming schema 192 func (fss *StreamingService) ListenEndBlock(ctx sdk.Context, req abci.RequestEndBlock, res abci.ResponseEndBlock) error { 193 // generate the new file 194 dstFile, err := fss.openEndBlockFile() 195 if err != nil { 196 return err 197 } 198 // write req to file 199 lengthPrefixedReqBytes, err := fss.codec.MarshalLengthPrefixed(&req) 200 if err != nil { 201 return err 202 } 203 if _, err = dstFile.Write(lengthPrefixedReqBytes); err != nil { 204 return err 205 } 206 // write all state changes cached for this stage to file 207 fss.stateCacheLock.Lock() 208 for _, stateChange := range fss.stateCache { 209 if _, err = dstFile.Write(stateChange); err != nil { 210 fss.stateCache = nil 211 fss.stateCacheLock.Unlock() 212 return err 213 } 214 } 215 // reset cache 216 fss.stateCache = nil 217 fss.stateCacheLock.Unlock() 218 // write res to file 219 lengthPrefixedResBytes, err := fss.codec.MarshalLengthPrefixed(&res) 220 if err != nil { 221 return err 222 } 223 if _, err = dstFile.Write(lengthPrefixedResBytes); err != nil { 224 return err 225 } 226 // close file 227 return dstFile.Close() 228 } 229 230 func (fss *StreamingService) openEndBlockFile() (*os.File, error) { 231 fileName := fmt.Sprintf("block-%d-end", fss.currentBlockNumber) 232 if fss.filePrefix != "" { 233 fileName = fmt.Sprintf("%s-%s", fss.filePrefix, fileName) 234 } 235 return os.OpenFile(filepath.Join(fss.writeDir, fileName), os.O_CREATE|os.O_WRONLY, 0o600) 236 } 237 238 // Stream satisfies the baseapp.StreamingService interface 239 // It spins up a goroutine select loop which awaits length-prefixed binary encoded KV pairs 240 // and caches them in the order they were received 241 // returns an error if it is called twice 242 func (fss *StreamingService) Stream(wg *sync.WaitGroup) error { 243 if fss.quitChan != nil { 244 return errors.New("`Stream` has already been called. The stream needs to be closed before it can be started again") 245 } 246 fss.quitChan = make(chan struct{}) 247 wg.Add(1) 248 go func() { 249 defer wg.Done() 250 for { 251 select { 252 case <-fss.quitChan: 253 fss.quitChan = nil 254 return 255 case by := <-fss.srcChan: 256 fss.stateCacheLock.Lock() 257 fss.stateCache = append(fss.stateCache, by) 258 fss.stateCacheLock.Unlock() 259 } 260 } 261 }() 262 return nil 263 } 264 265 // Close satisfies the io.Closer interface, which satisfies the baseapp.StreamingService interface 266 func (fss *StreamingService) Close() error { 267 close(fss.quitChan) 268 return nil 269 } 270 271 // isDirWriteable checks if dir is writable by writing and removing a file 272 // to dir. It returns nil if dir is writable. 273 func isDirWriteable(dir string) error { 274 f := path.Join(dir, ".touch") 275 if err := os.WriteFile(f, []byte(""), 0o600); err != nil { 276 return err 277 } 278 return os.Remove(f) 279 }