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 }