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  }