github.com/Finschia/finschia-sdk@v0.48.1/snapshots/manager.go (about)

     1  package snapshots
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"fmt"
     7  	"io"
     8  	"math"
     9  	"sort"
    10  	"sync"
    11  
    12  	"github.com/Finschia/finschia-sdk/snapshots/types"
    13  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
    14  )
    15  
    16  const (
    17  	opNone     operation = ""
    18  	opSnapshot operation = "snapshot"
    19  	opPrune    operation = "prune"
    20  	opRestore  operation = "restore"
    21  
    22  	chunkBufferSize = 4
    23  
    24  	snapshotMaxItemSize = int(64e6) // SDK has no key/value size limit, so we set an arbitrary limit
    25  )
    26  
    27  // operation represents a Manager operation. Only one operation can be in progress at a time.
    28  type operation string
    29  
    30  // restoreDone represents the result of a restore operation.
    31  type restoreDone struct {
    32  	complete bool  // if true, restore completed successfully (not prematurely)
    33  	err      error // if non-nil, restore errored
    34  }
    35  
    36  // Manager manages snapshot and restore operations for an app, making sure only a single
    37  // long-running operation is in progress at any given time, and provides convenience methods
    38  // mirroring the ABCI interface.
    39  //
    40  // Although the ABCI interface (and this manager) passes chunks as byte slices, the internal
    41  // snapshot/restore APIs use IO streams (i.e. chan io.ReadCloser), for two reasons:
    42  //
    43  //  1. In the future, ABCI should support streaming. Consider e.g. InitChain during chain
    44  //     upgrades, which currently passes the entire chain state as an in-memory byte slice.
    45  //     https://github.com/tendermint/tendermint/issues/5184
    46  //
    47  //  2. io.ReadCloser streams automatically propagate IO errors, and can pass arbitrary
    48  //     errors via io.Pipe.CloseWithError().
    49  type Manager struct {
    50  	store      *Store
    51  	multistore types.Snapshotter
    52  	extensions map[string]types.ExtensionSnapshotter
    53  
    54  	mtx                sync.Mutex
    55  	operation          operation
    56  	chRestore          chan<- io.ReadCloser
    57  	chRestoreDone      <-chan restoreDone
    58  	restoreChunkHashes [][]byte
    59  	restoreChunkIndex  uint32
    60  }
    61  
    62  // NewManager creates a new manager.
    63  func NewManager(store *Store, multistore types.Snapshotter) *Manager {
    64  	return &Manager{
    65  		store:      store,
    66  		multistore: multistore,
    67  		extensions: make(map[string]types.ExtensionSnapshotter),
    68  	}
    69  }
    70  
    71  // NewManagerWithExtensions creates a new manager.
    72  func NewManagerWithExtensions(store *Store, multistore types.Snapshotter, extensions map[string]types.ExtensionSnapshotter) *Manager {
    73  	return &Manager{
    74  		store:      store,
    75  		multistore: multistore,
    76  		extensions: extensions,
    77  	}
    78  }
    79  
    80  // RegisterExtensions register extension snapshotters to manager
    81  func (m *Manager) RegisterExtensions(extensions ...types.ExtensionSnapshotter) error {
    82  	for _, extension := range extensions {
    83  		name := extension.SnapshotName()
    84  		if _, ok := m.extensions[name]; ok {
    85  			return fmt.Errorf("duplicated snapshotter name: %s", name)
    86  		}
    87  		if !IsFormatSupported(extension, extension.SnapshotFormat()) {
    88  			return fmt.Errorf("snapshotter don't support it's own snapshot format: %s %d", name, extension.SnapshotFormat())
    89  		}
    90  		m.extensions[name] = extension
    91  	}
    92  	return nil
    93  }
    94  
    95  // begin starts an operation, or errors if one is in progress. It manages the mutex itself.
    96  func (m *Manager) begin(op operation) error {
    97  	m.mtx.Lock()
    98  	defer m.mtx.Unlock()
    99  	return m.beginLocked(op)
   100  }
   101  
   102  // beginLocked begins an operation while already holding the mutex.
   103  func (m *Manager) beginLocked(op operation) error {
   104  	if op == opNone {
   105  		return sdkerrors.Wrap(sdkerrors.ErrLogic, "can't begin a none operation")
   106  	}
   107  	if m.operation != opNone {
   108  		return sdkerrors.Wrapf(sdkerrors.ErrConflict, "a %v operation is in progress", m.operation)
   109  	}
   110  	m.operation = op
   111  	return nil
   112  }
   113  
   114  // end ends the current operation.
   115  func (m *Manager) end() {
   116  	m.mtx.Lock()
   117  	defer m.mtx.Unlock()
   118  	m.endLocked()
   119  }
   120  
   121  // endLocked ends the current operation while already holding the mutex.
   122  func (m *Manager) endLocked() {
   123  	m.operation = opNone
   124  	if m.chRestore != nil {
   125  		close(m.chRestore)
   126  		m.chRestore = nil
   127  	}
   128  	m.chRestoreDone = nil
   129  	m.restoreChunkHashes = nil
   130  	m.restoreChunkIndex = 0
   131  }
   132  
   133  // sortedExtensionNames sort extension names for deterministic iteration.
   134  func (m *Manager) sortedExtensionNames() []string {
   135  	names := make([]string, 0, len(m.extensions))
   136  	for name := range m.extensions {
   137  		names = append(names, name)
   138  	}
   139  
   140  	sort.Strings(names)
   141  	return names
   142  }
   143  
   144  // Create creates a snapshot and returns its metadata.
   145  func (m *Manager) Create(height uint64) (*types.Snapshot, error) {
   146  	if m == nil {
   147  		return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "no snapshot store configured")
   148  	}
   149  	err := m.begin(opSnapshot)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	defer m.end()
   154  
   155  	latest, err := m.store.GetLatest()
   156  	if err != nil {
   157  		return nil, sdkerrors.Wrap(err, "failed to examine latest snapshot")
   158  	}
   159  	if latest != nil && latest.Height >= height {
   160  		return nil, sdkerrors.Wrapf(sdkerrors.ErrConflict,
   161  			"a more recent snapshot already exists at height %v", latest.Height)
   162  	}
   163  
   164  	// Spawn goroutine to generate snapshot chunks and pass their io.ReadClosers through a channel
   165  	ch := make(chan io.ReadCloser)
   166  	go m.createSnapshot(height, ch)
   167  
   168  	return m.store.Save(height, types.CurrentFormat, ch)
   169  }
   170  
   171  // createSnapshot do the heavy work of snapshotting after the validations of request are done
   172  // the produced chunks are written to the channel.
   173  func (m *Manager) createSnapshot(height uint64, ch chan<- io.ReadCloser) {
   174  	streamWriter := NewStreamWriter(ch)
   175  	if streamWriter == nil {
   176  		return
   177  	}
   178  	defer streamWriter.Close()
   179  	if err := m.multistore.Snapshot(height, streamWriter); err != nil {
   180  		streamWriter.CloseWithError(err)
   181  		return
   182  	}
   183  	for _, name := range m.sortedExtensionNames() {
   184  		extension := m.extensions[name]
   185  		// write extension metadata
   186  		err := streamWriter.WriteMsg(&types.SnapshotItem{
   187  			Item: &types.SnapshotItem_Extension{
   188  				Extension: &types.SnapshotExtensionMeta{
   189  					Name:   name,
   190  					Format: extension.SnapshotFormat(),
   191  				},
   192  			},
   193  		})
   194  		if err != nil {
   195  			streamWriter.CloseWithError(err)
   196  			return
   197  		}
   198  		if err := extension.Snapshot(height, streamWriter); err != nil {
   199  			streamWriter.CloseWithError(err)
   200  			return
   201  		}
   202  	}
   203  }
   204  
   205  // List lists snapshots, mirroring ABCI ListSnapshots. It can be concurrent with other operations.
   206  func (m *Manager) List() ([]*types.Snapshot, error) {
   207  	return m.store.List()
   208  }
   209  
   210  // LoadChunk loads a chunk into a byte slice, mirroring ABCI LoadChunk. It can be called
   211  // concurrently with other operations. If the chunk does not exist, nil is returned.
   212  func (m *Manager) LoadChunk(height uint64, format uint32, chunk uint32) ([]byte, error) {
   213  	reader, err := m.store.LoadChunk(height, format, chunk)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	if reader == nil {
   218  		return nil, nil
   219  	}
   220  	defer reader.Close()
   221  
   222  	return io.ReadAll(reader)
   223  }
   224  
   225  // Prune prunes snapshots, if no other operations are in progress.
   226  func (m *Manager) Prune(retain uint32) (uint64, error) {
   227  	err := m.begin(opPrune)
   228  	if err != nil {
   229  		return 0, err
   230  	}
   231  	defer m.end()
   232  	return m.store.Prune(retain)
   233  }
   234  
   235  // Restore begins an async snapshot restoration, mirroring ABCI OfferSnapshot. Chunks must be fed
   236  // via RestoreChunk() until the restore is complete or a chunk fails.
   237  func (m *Manager) Restore(snapshot types.Snapshot) error {
   238  	if snapshot.Chunks == 0 {
   239  		return sdkerrors.Wrap(types.ErrInvalidMetadata, "no chunks")
   240  	}
   241  	if uint32(len(snapshot.Metadata.ChunkHashes)) != snapshot.Chunks {
   242  		return sdkerrors.Wrapf(types.ErrInvalidMetadata, "snapshot has %v chunk hashes, but %v chunks",
   243  			uint32(len(snapshot.Metadata.ChunkHashes)),
   244  			snapshot.Chunks)
   245  	}
   246  	m.mtx.Lock()
   247  	defer m.mtx.Unlock()
   248  
   249  	// check multistore supported format preemptive
   250  	if snapshot.Format != types.CurrentFormat {
   251  		return sdkerrors.Wrapf(types.ErrUnknownFormat, "snapshot format %v", snapshot.Format)
   252  	}
   253  	if snapshot.Height == 0 {
   254  		return sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot restore snapshot at height 0")
   255  	}
   256  	if snapshot.Height > uint64(math.MaxInt64) {
   257  		return sdkerrors.Wrapf(types.ErrInvalidMetadata,
   258  			"snapshot height %v cannot exceed %v", snapshot.Height, int64(math.MaxInt64))
   259  	}
   260  
   261  	err := m.beginLocked(opRestore)
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	// Start an asynchronous snapshot restoration, passing chunks and completion status via channels.
   267  	chChunks := make(chan io.ReadCloser, chunkBufferSize)
   268  	chDone := make(chan restoreDone, 1)
   269  
   270  	go func() {
   271  		err := m.restoreSnapshot(snapshot, chChunks)
   272  		chDone <- restoreDone{
   273  			complete: err == nil,
   274  			err:      err,
   275  		}
   276  		close(chDone)
   277  	}()
   278  
   279  	m.chRestore = chChunks
   280  	m.chRestoreDone = chDone
   281  	m.restoreChunkHashes = snapshot.Metadata.ChunkHashes
   282  	m.restoreChunkIndex = 0
   283  	return nil
   284  }
   285  
   286  // restoreSnapshot do the heavy work of snapshot restoration after preliminary checks on request have passed.
   287  func (m *Manager) restoreSnapshot(snapshot types.Snapshot, chChunks <-chan io.ReadCloser) error {
   288  	streamReader, err := NewStreamReader(chChunks)
   289  	if err != nil {
   290  		return err
   291  	}
   292  	defer streamReader.Close()
   293  
   294  	next, err := m.multistore.Restore(snapshot.Height, snapshot.Format, streamReader)
   295  	if err != nil {
   296  		return sdkerrors.Wrap(err, "multistore restore")
   297  	}
   298  	for {
   299  		if next.Item == nil {
   300  			// end of stream
   301  			break
   302  		}
   303  		metadata := next.GetExtension()
   304  		if metadata == nil {
   305  			return sdkerrors.Wrapf(sdkerrors.ErrLogic, "unknown snapshot item %T", next.Item)
   306  		}
   307  		extension, ok := m.extensions[metadata.Name]
   308  		if !ok {
   309  			return sdkerrors.Wrapf(sdkerrors.ErrLogic, "unknown extension snapshotter %s", metadata.Name)
   310  		}
   311  		if !IsFormatSupported(extension, metadata.Format) {
   312  			return sdkerrors.Wrapf(types.ErrUnknownFormat, "format %v for extension %s", metadata.Format, metadata.Name)
   313  		}
   314  		next, err = extension.Restore(snapshot.Height, metadata.Format, streamReader)
   315  		if err != nil {
   316  			return sdkerrors.Wrapf(err, "extension %s restore", metadata.Name)
   317  		}
   318  	}
   319  	return nil
   320  }
   321  
   322  // RestoreChunk adds a chunk to an active snapshot restoration, mirroring ABCI ApplySnapshotChunk.
   323  // Chunks must be given until the restore is complete, returning true, or a chunk errors.
   324  func (m *Manager) RestoreChunk(chunk []byte) (bool, error) {
   325  	m.mtx.Lock()
   326  	defer m.mtx.Unlock()
   327  	if m.operation != opRestore {
   328  		return false, sdkerrors.Wrap(sdkerrors.ErrLogic, "no restore operation in progress")
   329  	}
   330  
   331  	if int(m.restoreChunkIndex) >= len(m.restoreChunkHashes) {
   332  		return false, sdkerrors.Wrap(sdkerrors.ErrLogic, "received unexpected chunk")
   333  	}
   334  
   335  	// Check if any errors have occurred yet.
   336  	select {
   337  	case done := <-m.chRestoreDone:
   338  		m.endLocked()
   339  		if done.err != nil {
   340  			return false, done.err
   341  		}
   342  		return false, sdkerrors.Wrap(sdkerrors.ErrLogic, "restore ended unexpectedly")
   343  	default:
   344  	}
   345  
   346  	// Verify the chunk hash.
   347  	hash := sha256.Sum256(chunk)
   348  	expected := m.restoreChunkHashes[m.restoreChunkIndex]
   349  	if !bytes.Equal(hash[:], expected) {
   350  		return false, sdkerrors.Wrapf(types.ErrChunkHashMismatch,
   351  			"expected %x, got %x", hash, expected)
   352  	}
   353  
   354  	// Pass the chunk to the restore, and wait for completion if it was the final one.
   355  	m.chRestore <- io.NopCloser(bytes.NewReader(chunk))
   356  	m.restoreChunkIndex++
   357  
   358  	if int(m.restoreChunkIndex) >= len(m.restoreChunkHashes) {
   359  		close(m.chRestore)
   360  		m.chRestore = nil
   361  		done := <-m.chRestoreDone
   362  		m.endLocked()
   363  		if done.err != nil {
   364  			return false, done.err
   365  		}
   366  		if !done.complete {
   367  			return false, sdkerrors.Wrap(sdkerrors.ErrLogic, "restore ended prematurely")
   368  		}
   369  		return true, nil
   370  	}
   371  	return false, nil
   372  }
   373  
   374  // IsFormatSupported returns if the snapshotter supports restoration from given format.
   375  func IsFormatSupported(snapshotter types.ExtensionSnapshotter, format uint32) bool {
   376  	for _, i := range snapshotter.SupportedFormats() {
   377  		if i == format {
   378  			return true
   379  		}
   380  	}
   381  	return false
   382  }