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  }