github.com/MagHErmit/tendermint@v0.282.1/test/e2e/app/snapshots.go (about)

     1  // nolint: gosec
     2  package app
     3  
     4  import (
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"math"
    10  	"os"
    11  	"path/filepath"
    12  	"sync"
    13  
    14  	abci "github.com/MagHErmit/tendermint/abci/types"
    15  )
    16  
    17  const (
    18  	snapshotChunkSize = 1e6
    19  )
    20  
    21  // SnapshotStore stores state sync snapshots. Snapshots are stored simply as
    22  // JSON files, and chunks are generated on-the-fly by splitting the JSON data
    23  // into fixed-size chunks.
    24  type SnapshotStore struct {
    25  	sync.RWMutex
    26  	dir      string
    27  	metadata []abci.Snapshot
    28  }
    29  
    30  // NewSnapshotStore creates a new snapshot store.
    31  func NewSnapshotStore(dir string) (*SnapshotStore, error) {
    32  	store := &SnapshotStore{dir: dir}
    33  	if err := os.MkdirAll(dir, 0755); err != nil {
    34  		return nil, err
    35  	}
    36  	if err := store.loadMetadata(); err != nil {
    37  		return nil, err
    38  	}
    39  	return store, nil
    40  }
    41  
    42  // loadMetadata loads snapshot metadata. Does not take out locks, since it's
    43  // called internally on construction.
    44  func (s *SnapshotStore) loadMetadata() error {
    45  	file := filepath.Join(s.dir, "metadata.json")
    46  	metadata := []abci.Snapshot{}
    47  
    48  	bz, err := ioutil.ReadFile(file)
    49  	switch {
    50  	case errors.Is(err, os.ErrNotExist):
    51  	case err != nil:
    52  		return fmt.Errorf("failed to load snapshot metadata from %q: %w", file, err)
    53  	}
    54  	if len(bz) != 0 {
    55  		err = json.Unmarshal(bz, &metadata)
    56  		if err != nil {
    57  			return fmt.Errorf("invalid snapshot data in %q: %w", file, err)
    58  		}
    59  	}
    60  	s.metadata = metadata
    61  	return nil
    62  }
    63  
    64  // saveMetadata saves snapshot metadata. Does not take out locks, since it's
    65  // called internally from e.g. Create().
    66  func (s *SnapshotStore) saveMetadata() error {
    67  	bz, err := json.Marshal(s.metadata)
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	// save the file to a new file and move it to make saving atomic.
    73  	newFile := filepath.Join(s.dir, "metadata.json.new")
    74  	file := filepath.Join(s.dir, "metadata.json")
    75  	err = ioutil.WriteFile(newFile, bz, 0644) //nolint: gosec
    76  	if err != nil {
    77  		return err
    78  	}
    79  	return os.Rename(newFile, file)
    80  }
    81  
    82  // Create creates a snapshot of the given application state's key/value pairs.
    83  func (s *SnapshotStore) Create(state *State) (abci.Snapshot, error) {
    84  	s.Lock()
    85  	defer s.Unlock()
    86  	bz, err := state.Export()
    87  	if err != nil {
    88  		return abci.Snapshot{}, err
    89  	}
    90  	snapshot := abci.Snapshot{
    91  		Height: state.Height,
    92  		Format: 1,
    93  		Hash:   hashItems(state.Values),
    94  		Chunks: byteChunks(bz),
    95  	}
    96  	err = ioutil.WriteFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", state.Height)), bz, 0644)
    97  	if err != nil {
    98  		return abci.Snapshot{}, err
    99  	}
   100  	s.metadata = append(s.metadata, snapshot)
   101  	err = s.saveMetadata()
   102  	if err != nil {
   103  		return abci.Snapshot{}, err
   104  	}
   105  	return snapshot, nil
   106  }
   107  
   108  // List lists available snapshots.
   109  func (s *SnapshotStore) List() ([]*abci.Snapshot, error) {
   110  	s.RLock()
   111  	defer s.RUnlock()
   112  	snapshots := make([]*abci.Snapshot, len(s.metadata))
   113  	for idx := range s.metadata {
   114  		snapshots[idx] = &s.metadata[idx]
   115  	}
   116  	return snapshots, nil
   117  }
   118  
   119  // LoadChunk loads a snapshot chunk.
   120  func (s *SnapshotStore) LoadChunk(height uint64, format uint32, chunk uint32) ([]byte, error) {
   121  	s.RLock()
   122  	defer s.RUnlock()
   123  	for _, snapshot := range s.metadata {
   124  		if snapshot.Height == height && snapshot.Format == format {
   125  			bz, err := ioutil.ReadFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", height)))
   126  			if err != nil {
   127  				return nil, err
   128  			}
   129  			return byteChunk(bz, chunk), nil
   130  		}
   131  	}
   132  	return nil, nil
   133  }
   134  
   135  // byteChunk returns the chunk at a given index from the full byte slice.
   136  func byteChunk(bz []byte, index uint32) []byte {
   137  	start := int(index * snapshotChunkSize)
   138  	end := int((index + 1) * snapshotChunkSize)
   139  	switch {
   140  	case start >= len(bz):
   141  		return nil
   142  	case end >= len(bz):
   143  		return bz[start:]
   144  	default:
   145  		return bz[start:end]
   146  	}
   147  }
   148  
   149  // byteChunks calculates the number of chunks in the byte slice.
   150  func byteChunks(bz []byte) uint32 {
   151  	return uint32(math.Ceil(float64(len(bz)) / snapshotChunkSize))
   152  }