github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-049-state-sync-hooks.md (about)

     1  # ADR 049: State Sync Hooks
     2  
     3  ## Changelog
     4  
     5  * Jan 19, 2022: Initial Draft
     6  * Apr 29, 2022: Safer extension snapshotter interface
     7  
     8  ## Status
     9  
    10  Implemented
    11  
    12  ## Abstract
    13  
    14  This ADR outlines a hooks-based mechanism for application modules to provide additional state (outside of the IAVL tree) to be used 
    15  during state sync.
    16  
    17  ## Context
    18  
    19  New clients use state-sync to download snapshots of module state from peers. Currently, the snapshot consists of a
    20  stream of `SnapshotStoreItem` and `SnapshotIAVLItem`, which means that application modules that define their state outside of the IAVL 
    21  tree cannot include their state as part of the state-sync process.
    22  
    23  Note, Even though the module state data is outside of the tree, for determinism we require that the hash of the external data should 
    24  be posted in the IAVL tree.
    25  
    26  ## Decision
    27  
    28  A simple proposal based on our existing implementation is that, we can add two new message types: `SnapshotExtensionMeta` 
    29  and `SnapshotExtensionPayload`, and they are appended to the existing multi-store stream with `SnapshotExtensionMeta` 
    30  acting as a delimiter between extensions. As the chunk hashes should be able to ensure data integrity, we don't need 
    31  a delimiter to mark the end of the snapshot stream.
    32  
    33  Besides, we provide `Snapshotter` and `ExtensionSnapshotter` interface for modules to implement snapshotters, which will handle both taking 
    34  snapshot and the restoration. Each module could have mutiple snapshotters, and for modules with additional state, they should
    35  implement `ExtensionSnapshotter` as extension snapshotters. When setting up the application, the snapshot `Manager` should call 
    36  `RegisterExtensions([]ExtensionSnapshotter…)` to register all the extension snapshotters.
    37  
    38  ```protobuf
    39  // SnapshotItem is an item contained in a rootmulti.Store snapshot.
    40  // On top of the exsiting SnapshotStoreItem and SnapshotIAVLItem, we add two new options for the item.
    41  message SnapshotItem {
    42    // item is the specific type of snapshot item.
    43    oneof item {
    44      SnapshotStoreItem        store             = 1;
    45      SnapshotIAVLItem         iavl              = 2 [(gogoproto.customname) = "IAVL"];
    46      SnapshotExtensionMeta    extension         = 3;
    47      SnapshotExtensionPayload extension_payload = 4;
    48    }
    49  }
    50  
    51  // SnapshotExtensionMeta contains metadata about an external snapshotter.
    52  // One module may need multiple snapshotters, so each module may have multiple SnapshotExtensionMeta.
    53  message SnapshotExtensionMeta {
    54    // the name of the ExtensionSnapshotter, and it is registered to snapshotter manager when setting up the application
    55    // name should be unique for each ExtensionSnapshotter as we need to alphabetically order their snapshots to get
    56    // deterministic snapshot stream.
    57    string name   = 1;
    58    // this is used by each ExtensionSnapshotter to decide the format of payloads included in SnapshotExtensionPayload message
    59    // it is used within the snapshotter/namespace, not global one for all modules
    60    uint32 format = 2;
    61  }
    62  
    63  // SnapshotExtensionPayload contains payloads of an external snapshotter.
    64  message SnapshotExtensionPayload {
    65    bytes payload = 1;
    66  }
    67  ```
    68  
    69  When we create a snapshot stream, the `multistore` snapshot is always placed at the beginning of the binary stream, and other extension snapshots are alphabetically ordered by the name of the corresponding `ExtensionSnapshotter`. 
    70  
    71  The snapshot stream would look like as follows:
    72  
    73  ```go
    74  // multi-store snapshot
    75  {SnapshotStoreItem | SnapshotIAVLItem, ...}
    76  // extension1 snapshot
    77  SnapshotExtensionMeta
    78  {SnapshotExtensionPayload, ...}
    79  // extension2 snapshot
    80  SnapshotExtensionMeta
    81  {SnapshotExtensionPayload, ...}
    82  ```
    83  
    84  We add an `extensions` field to snapshot `Manager` for extension snapshotters. The `multistore` snapshotter is a special one and it doesn't need a name because it is always placed at the beginning of the binary stream.
    85  
    86  ```go
    87  type Manager struct {
    88  	store      *Store
    89  	multistore types.Snapshotter
    90  	extensions map[string]types.ExtensionSnapshotter
    91  	mtx                sync.Mutex
    92  	operation          operation
    93  	chRestore          chan<- io.ReadCloser
    94  	chRestoreDone      <-chan restoreDone
    95  	restoreChunkHashes [][]byte
    96  	restoreChunkIndex  uint32
    97  }
    98  ```
    99  
   100  For extension snapshotters that implement the `ExtensionSnapshotter` interface, their names should be registered to the snapshot `Manager` by 
   101  calling `RegisterExtensions` when setting up the application. The snapshotters will handle both taking snapshot and restoration.
   102  
   103  ```go
   104  // RegisterExtensions register extension snapshotters to manager
   105  func (m *Manager) RegisterExtensions(extensions ...types.ExtensionSnapshotter) error 
   106  ```
   107  
   108  On top of the existing `Snapshotter` interface for the `multistore`, we add `ExtensionSnapshotter` interface for the extension snapshotters. Three more function signatures: `SnapshotFormat()`, `SupportedFormats()` and `SnapshotName()` are added to `ExtensionSnapshotter`.
   109  
   110  ```go
   111  // ExtensionPayloadReader read extension payloads,
   112  // it returns io.EOF when reached either end of stream or the extension boundaries.
   113  type ExtensionPayloadReader = func() ([]byte, error)
   114  
   115  // ExtensionPayloadWriter is a helper to write extension payloads to underlying stream.
   116  type ExtensionPayloadWriter = func([]byte) error
   117  
   118  // ExtensionSnapshotter is an extension Snapshotter that is appended to the snapshot stream.
   119  // ExtensionSnapshotter has an unique name and manages it's own internal formats.
   120  type ExtensionSnapshotter interface {
   121  	// SnapshotName returns the name of snapshotter, it should be unique in the manager.
   122  	SnapshotName() string
   123  
   124  	// SnapshotFormat returns the default format used to take a snapshot.
   125  	SnapshotFormat() uint32
   126  
   127  	// SupportedFormats returns a list of formats it can restore from.
   128  	SupportedFormats() []uint32
   129  
   130  	// SnapshotExtension writes extension payloads into the underlying protobuf stream.
   131  	SnapshotExtension(height uint64, payloadWriter ExtensionPayloadWriter) error
   132  
   133  	// RestoreExtension restores an extension state snapshot,
   134  	// the payload reader returns `io.EOF` when reached the extension boundaries.
   135  	RestoreExtension(height uint64, format uint32, payloadReader ExtensionPayloadReader) error
   136  
   137  }
   138  ```
   139  
   140  ## Consequences
   141  
   142  As a result of this implementation, we are able to create snapshots of binary chunk stream for the state that we maintain outside of the IAVL Tree, CosmWasm blobs for example. And new clients are able to fetch sanpshots of state for all modules that have implemented the corresponding interface from peer nodes. 
   143  
   144  
   145  ### Backwards Compatibility
   146  
   147  This ADR introduces new proto message types, add an `extensions` field in snapshot `Manager`, and add new `ExtensionSnapshotter` interface, so this is not backwards compatible if we have extensions.
   148  
   149  But for applications that does not have the state data outside of the IAVL tree for any module, the snapshot stream is backwards-compatible.
   150  
   151  ### Positive
   152  
   153  * State maintained outside of IAVL tree like CosmWasm blobs can create snapshots by implementing extension snapshotters, and being fetched by new clients via state-sync.
   154  
   155  ### Negative
   156  
   157  ### Neutral
   158  
   159  * All modules that maintain state outside of IAVL tree need to implement `ExtensionSnapshotter` and the snapshot `Manager` need to call `RegisterExtensions` when setting up the application.
   160  
   161  ## Further Discussions
   162  
   163  While an ADR is in the DRAFT or PROPOSED stage, this section should contain a summary of issues to be solved in future iterations (usually referencing comments from a pull-request discussion).
   164  Later, this section can optionally list ideas or improvements the author or reviewers found during the analysis of this ADR.
   165  
   166  ## Test Cases [optional]
   167  
   168  Test cases for an implementation are mandatory for ADRs that are affecting consensus changes. Other ADRs can choose to include links to test cases if applicable.
   169  
   170  ## References
   171  
   172  * https://github.com/cosmos/cosmos-sdk/pull/10961
   173  * https://github.com/cosmos/cosmos-sdk/issues/7340
   174  * https://hackmd.io/gJoyev6DSmqqkO667WQlGw