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