@agoric/cosmos 0.34.2-dev-5513dea.0 → 0.34.2-dev-3679b4c.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/app.go +16 -11
- package/git-revision.txt +1 -1
- package/package.json +2 -2
- package/proto/agoric/swingset/swingset.proto +5 -2
- package/x/swingset/alias.go +7 -6
- package/x/swingset/keeper/extension_snapshotter.go +321 -0
- package/x/swingset/keeper/extension_snapshotter_test.go +106 -0
- package/x/swingset/keeper/swing_store_exports_handler.go +818 -0
- package/x/swingset/keeper/swing_store_exports_handler_test.go +247 -0
- package/x/swingset/types/swingset.pb.go +82 -80
- package/x/swingset/keeper/snapshotter.go +0 -528
- package/x/swingset/keeper/snapshotter_test.go +0 -195
|
@@ -1,528 +0,0 @@
|
|
|
1
|
-
package keeper
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"errors"
|
|
6
|
-
"fmt"
|
|
7
|
-
"io"
|
|
8
|
-
"os"
|
|
9
|
-
"path/filepath"
|
|
10
|
-
"regexp"
|
|
11
|
-
|
|
12
|
-
"github.com/Agoric/agoric-sdk/golang/cosmos/vm"
|
|
13
|
-
"github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/types"
|
|
14
|
-
vstoragetypes "github.com/Agoric/agoric-sdk/golang/cosmos/x/vstorage/types"
|
|
15
|
-
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
16
|
-
snapshots "github.com/cosmos/cosmos-sdk/snapshots/types"
|
|
17
|
-
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
18
|
-
"github.com/tendermint/tendermint/libs/log"
|
|
19
|
-
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
var _ snapshots.ExtensionSnapshotter = &SwingsetSnapshotter{}
|
|
23
|
-
|
|
24
|
-
// SnapshotFormat 1 is a proto message containing an artifact name, and the binary artifact data
|
|
25
|
-
const SnapshotFormat = 1
|
|
26
|
-
|
|
27
|
-
// The manifest filename must be synchronized with the JS export/import tooling
|
|
28
|
-
const ExportManifestFilename = "export-manifest.json"
|
|
29
|
-
const ExportDataFilename = "export-data.jsonl"
|
|
30
|
-
const UntrustedExportDataArtifactName = "UNTRUSTED-EXPORT-DATA"
|
|
31
|
-
const UntrustedExportDataFilename = "untrusted-export-data.jsonl"
|
|
32
|
-
const ExportedFilesMode = 0644
|
|
33
|
-
|
|
34
|
-
var disallowedArtifactNameChar = regexp.MustCompile(`[^-_.a-zA-Z0-9]`)
|
|
35
|
-
|
|
36
|
-
// sanitizeArtifactName searches a string for all characters
|
|
37
|
-
// other than ASCII alphanumerics, hyphens, underscores, and dots,
|
|
38
|
-
// and replaces each of them with a hyphen.
|
|
39
|
-
func sanitizeArtifactName(name string) string {
|
|
40
|
-
return disallowedArtifactNameChar.ReplaceAllString(name, "-")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
type activeSnapshot struct {
|
|
44
|
-
// Whether the operation in progress is a restore
|
|
45
|
-
isRestore bool
|
|
46
|
-
// The block height of the snapshot in progress
|
|
47
|
-
height int64
|
|
48
|
-
// The logger for this snapshot
|
|
49
|
-
logger log.Logger
|
|
50
|
-
// Use to synchronize the commit boundary
|
|
51
|
-
startedResult chan error
|
|
52
|
-
// Internal flag indicating whether the cosmos driven snapshot process completed
|
|
53
|
-
// Only read or written by the snapshot worker goroutine.
|
|
54
|
-
retrieved bool
|
|
55
|
-
// Closed when this snapshot is complete
|
|
56
|
-
done chan struct{}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
type exportManifest struct {
|
|
60
|
-
BlockHeight uint64 `json:"blockHeight,omitempty"`
|
|
61
|
-
// The filename of the export data
|
|
62
|
-
Data string `json:"data,omitempty"`
|
|
63
|
-
// The list of artifact names and their corresponding filenames
|
|
64
|
-
Artifacts [][2]string `json:"artifacts"`
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
type SwingsetSnapshotter struct {
|
|
68
|
-
isConfigured func() bool
|
|
69
|
-
takeSnapshot func(height int64)
|
|
70
|
-
newRestoreContext func(height int64) sdk.Context
|
|
71
|
-
logger log.Logger
|
|
72
|
-
getSwingStoreExportData func(ctx sdk.Context) []*vstoragetypes.DataEntry
|
|
73
|
-
blockingSend func(action vm.Jsonable, mustNotBeInited bool) (string, error)
|
|
74
|
-
// Only modified by the main goroutine.
|
|
75
|
-
activeSnapshot *activeSnapshot
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
type snapshotAction struct {
|
|
79
|
-
Type string `json:"type"` // COSMOS_SNAPSHOT
|
|
80
|
-
BlockHeight int64 `json:"blockHeight"`
|
|
81
|
-
Request string `json:"request"` // "initiate", "discard", "retrieve", or "restore"
|
|
82
|
-
Args []json.RawMessage `json:"args,omitempty"`
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// NewSwingsetSnapshotter creates a SwingsetSnapshotter which exclusively
|
|
86
|
-
// manages communication with the JS side for Swingset snapshots, ensuring
|
|
87
|
-
// insensitivity to sub-block timing, and enforcing concurrency requirements.
|
|
88
|
-
// The caller of this submodule must arrange block level commit synchronization,
|
|
89
|
-
// to ensure the results are deterministic.
|
|
90
|
-
//
|
|
91
|
-
// Some `blockingSend` calls performed by this submodule are non-deterministic.
|
|
92
|
-
// This submodule will send messages to JS from goroutines at unpredictable
|
|
93
|
-
// times, but this is safe because when handling the messages, the JS side
|
|
94
|
-
// does not perform operations affecting consensus and ignores state changes
|
|
95
|
-
// since committing the previous block.
|
|
96
|
-
// Some other `blockingSend` calls however do change the JS swing-store and
|
|
97
|
-
// must happen before the Swingset controller on the JS side was inited.
|
|
98
|
-
func NewSwingsetSnapshotter(
|
|
99
|
-
app *baseapp.BaseApp,
|
|
100
|
-
getSwingStoreExportData func(ctx sdk.Context) []*vstoragetypes.DataEntry,
|
|
101
|
-
blockingSend func(action vm.Jsonable, mustNotBeInited bool) (string, error),
|
|
102
|
-
) SwingsetSnapshotter {
|
|
103
|
-
return SwingsetSnapshotter{
|
|
104
|
-
isConfigured: func() bool { return app.SnapshotManager() != nil },
|
|
105
|
-
takeSnapshot: app.Snapshot,
|
|
106
|
-
newRestoreContext: func(height int64) sdk.Context {
|
|
107
|
-
return app.NewUncachedContext(false, tmproto.Header{Height: height})
|
|
108
|
-
},
|
|
109
|
-
logger: app.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName), "submodule", "snapshotter"),
|
|
110
|
-
getSwingStoreExportData: getSwingStoreExportData,
|
|
111
|
-
blockingSend: blockingSend,
|
|
112
|
-
activeSnapshot: nil,
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// checkNotActive returns an error if there is an active snapshot.
|
|
117
|
-
func (snapshotter *SwingsetSnapshotter) checkNotActive() error {
|
|
118
|
-
active := snapshotter.activeSnapshot
|
|
119
|
-
if active != nil {
|
|
120
|
-
select {
|
|
121
|
-
case <-active.done:
|
|
122
|
-
snapshotter.activeSnapshot = nil
|
|
123
|
-
default:
|
|
124
|
-
if active.isRestore {
|
|
125
|
-
return fmt.Errorf("snapshot restore already in progress for height %d", active.height)
|
|
126
|
-
} else {
|
|
127
|
-
return fmt.Errorf("snapshot already in progress for height %d", active.height)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return nil
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// InitiateSnapshot synchronously initiates a snapshot for the given height.
|
|
135
|
-
// If a snapshot is already in progress, or if no snapshot manager is configured,
|
|
136
|
-
// this will fail.
|
|
137
|
-
// The snapshot operation is performed in a goroutine, and synchronized with the
|
|
138
|
-
// main thread through the `WaitUntilSnapshotStarted` method.
|
|
139
|
-
func (snapshotter *SwingsetSnapshotter) InitiateSnapshot(height int64) error {
|
|
140
|
-
err := snapshotter.checkNotActive()
|
|
141
|
-
if err != nil {
|
|
142
|
-
return err
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if !snapshotter.isConfigured() {
|
|
146
|
-
return fmt.Errorf("snapshot manager not configured")
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
logger := snapshotter.logger.With("height", height)
|
|
150
|
-
|
|
151
|
-
// Indicate that a snapshot has been initiated by setting `activeSnapshot`.
|
|
152
|
-
// This structure is used to synchronize with the goroutine spawned below.
|
|
153
|
-
// It's nilled-out before exiting (and is the only code that does so).
|
|
154
|
-
active := &activeSnapshot{
|
|
155
|
-
height: height,
|
|
156
|
-
logger: logger,
|
|
157
|
-
startedResult: make(chan error, 1),
|
|
158
|
-
retrieved: false,
|
|
159
|
-
done: make(chan struct{}),
|
|
160
|
-
}
|
|
161
|
-
snapshotter.activeSnapshot = active
|
|
162
|
-
|
|
163
|
-
go func() {
|
|
164
|
-
defer close(active.done)
|
|
165
|
-
|
|
166
|
-
action := &snapshotAction{
|
|
167
|
-
Type: "COSMOS_SNAPSHOT",
|
|
168
|
-
BlockHeight: height,
|
|
169
|
-
Request: "initiate",
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// blockingSend for COSMOS_SNAPSHOT action is safe to call from a goroutine
|
|
173
|
-
_, err := snapshotter.blockingSend(action, false)
|
|
174
|
-
|
|
175
|
-
if err != nil {
|
|
176
|
-
// First indicate a snapshot is no longer in progress if the call to
|
|
177
|
-
// `WaitUntilSnapshotStarted` has't happened yet.
|
|
178
|
-
// Then signal the current snapshot operation if a call to
|
|
179
|
-
// `WaitUntilSnapshotStarted` was already waiting.
|
|
180
|
-
active.startedResult <- err
|
|
181
|
-
close(active.startedResult)
|
|
182
|
-
logger.Error("failed to initiate swingset snapshot", "err", err)
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Signal that the snapshot operation has started in the goroutine. Calls to
|
|
187
|
-
// `WaitUntilSnapshotStarted` will no longer block.
|
|
188
|
-
close(active.startedResult)
|
|
189
|
-
|
|
190
|
-
// In production this should indirectly call SnapshotExtension().
|
|
191
|
-
snapshotter.takeSnapshot(height)
|
|
192
|
-
|
|
193
|
-
// Check whether the cosmos Snapshot() method successfully handled our extension
|
|
194
|
-
if active.retrieved {
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
logger.Error("failed to make swingset snapshot")
|
|
199
|
-
action = &snapshotAction{
|
|
200
|
-
Type: "COSMOS_SNAPSHOT",
|
|
201
|
-
BlockHeight: height,
|
|
202
|
-
Request: "discard",
|
|
203
|
-
}
|
|
204
|
-
_, err = snapshotter.blockingSend(action, false)
|
|
205
|
-
|
|
206
|
-
if err != nil {
|
|
207
|
-
logger.Error("failed to discard swingset snapshot", "err", err)
|
|
208
|
-
}
|
|
209
|
-
}()
|
|
210
|
-
|
|
211
|
-
return nil
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// WaitUntilSnapshotStarted synchronizes with a snapshot in progress, if any.
|
|
215
|
-
// The JS SwingStore export must have started before a new block is committed.
|
|
216
|
-
// The app must call this method before sending a commit action to SwingSet.
|
|
217
|
-
//
|
|
218
|
-
// Waits for a just initiated snapshot to have started in its goroutine.
|
|
219
|
-
// If no snapshot is in progress (`InitiateSnapshot` hasn't been called or
|
|
220
|
-
// already completed), or if we previously checked if the snapshot had started,
|
|
221
|
-
// returns immediately.
|
|
222
|
-
func (snapshotter *SwingsetSnapshotter) WaitUntilSnapshotStarted() error {
|
|
223
|
-
activeSnapshot := snapshotter.activeSnapshot
|
|
224
|
-
if activeSnapshot == nil {
|
|
225
|
-
return nil
|
|
226
|
-
}
|
|
227
|
-
// Block until the active snapshot has started, saving the result.
|
|
228
|
-
// The snapshot goroutine only produces a value in case of an error,
|
|
229
|
-
// and closes the channel once the snapshot has started or failed.
|
|
230
|
-
// Only the first call after a snapshot was initiated will report an error.
|
|
231
|
-
startErr := <-activeSnapshot.startedResult
|
|
232
|
-
|
|
233
|
-
// Check if the active snapshot is done, and if so, nil it out so future
|
|
234
|
-
// calls are faster.
|
|
235
|
-
select {
|
|
236
|
-
case <-activeSnapshot.done:
|
|
237
|
-
snapshotter.activeSnapshot = nil
|
|
238
|
-
default:
|
|
239
|
-
// don't wait for it to finish
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return startErr
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// SnapshotName returns the name of snapshotter, it should be unique in the manager.
|
|
246
|
-
// Implements ExtensionSnapshotter
|
|
247
|
-
func (snapshotter *SwingsetSnapshotter) SnapshotName() string {
|
|
248
|
-
return types.ModuleName
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// SnapshotFormat returns the default format the extension snapshotter uses to encode the
|
|
252
|
-
// payloads when taking a snapshot.
|
|
253
|
-
// It's defined within the extension, different from the global format for the whole state-sync snapshot.
|
|
254
|
-
// Implements ExtensionSnapshotter
|
|
255
|
-
func (snapshotter *SwingsetSnapshotter) SnapshotFormat() uint32 {
|
|
256
|
-
return SnapshotFormat
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// SupportedFormats returns a list of formats it can restore from.
|
|
260
|
-
// Implements ExtensionSnapshotter
|
|
261
|
-
func (snapshotter *SwingsetSnapshotter) SupportedFormats() []uint32 {
|
|
262
|
-
return []uint32{SnapshotFormat}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// SnapshotExtension writes extension payloads into the underlying protobuf stream.
|
|
266
|
-
// This operation is invoked by the snapshot manager in the goroutine started by
|
|
267
|
-
// `InitiateSnapshot`.
|
|
268
|
-
// Implements ExtensionSnapshotter
|
|
269
|
-
func (snapshotter *SwingsetSnapshotter) SnapshotExtension(height uint64, payloadWriter snapshots.ExtensionPayloadWriter) (err error) {
|
|
270
|
-
defer func() {
|
|
271
|
-
// Since the cosmos layers do a poor job of reporting errors, do our own reporting
|
|
272
|
-
// `err` will be set correctly regardless if it was explicitly assigned or
|
|
273
|
-
// a value was provided to a `return` statement.
|
|
274
|
-
// See https://go.dev/blog/defer-panic-and-recover for details
|
|
275
|
-
if err != nil {
|
|
276
|
-
var logger log.Logger
|
|
277
|
-
if snapshotter.activeSnapshot != nil {
|
|
278
|
-
logger = snapshotter.activeSnapshot.logger
|
|
279
|
-
} else {
|
|
280
|
-
logger = snapshotter.logger
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
logger.Error("swingset snapshot extension failed", "err", err)
|
|
284
|
-
}
|
|
285
|
-
}()
|
|
286
|
-
|
|
287
|
-
activeSnapshot := snapshotter.activeSnapshot
|
|
288
|
-
if activeSnapshot == nil {
|
|
289
|
-
// shouldn't happen, but return an error if it does
|
|
290
|
-
return errors.New("no active swingset snapshot")
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if activeSnapshot.height != int64(height) {
|
|
294
|
-
return fmt.Errorf("swingset snapshot requested for unexpected height %d (expected %d)", height, activeSnapshot.height)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
action := &snapshotAction{
|
|
298
|
-
Type: "COSMOS_SNAPSHOT",
|
|
299
|
-
BlockHeight: activeSnapshot.height,
|
|
300
|
-
Request: "retrieve",
|
|
301
|
-
}
|
|
302
|
-
out, err := snapshotter.blockingSend(action, false)
|
|
303
|
-
|
|
304
|
-
if err != nil {
|
|
305
|
-
return err
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
var exportDir string
|
|
309
|
-
err = json.Unmarshal([]byte(out), &exportDir)
|
|
310
|
-
if err != nil {
|
|
311
|
-
return err
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
defer os.RemoveAll(exportDir)
|
|
315
|
-
|
|
316
|
-
rawManifest, err := os.ReadFile(filepath.Join(exportDir, ExportManifestFilename))
|
|
317
|
-
if err != nil {
|
|
318
|
-
return err
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
var manifest exportManifest
|
|
322
|
-
err = json.Unmarshal(rawManifest, &manifest)
|
|
323
|
-
if err != nil {
|
|
324
|
-
return err
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if manifest.BlockHeight != height {
|
|
328
|
-
return fmt.Errorf("snapshot manifest blockHeight (%d) doesn't match (%d)", manifest.BlockHeight, height)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
writeFileToPayload := func(fileName string, artifactName string) error {
|
|
332
|
-
payload := types.ExtensionSnapshotterArtifactPayload{Name: artifactName}
|
|
333
|
-
|
|
334
|
-
payload.Data, err = os.ReadFile(filepath.Join(exportDir, fileName))
|
|
335
|
-
if err != nil {
|
|
336
|
-
return err
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
payloadBytes, err := payload.Marshal()
|
|
340
|
-
if err != nil {
|
|
341
|
-
return err
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
err = payloadWriter(payloadBytes)
|
|
345
|
-
if err != nil {
|
|
346
|
-
return err
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return nil
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if manifest.Data != "" {
|
|
353
|
-
err = writeFileToPayload(manifest.Data, UntrustedExportDataArtifactName)
|
|
354
|
-
if err != nil {
|
|
355
|
-
return err
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
for _, artifactInfo := range manifest.Artifacts {
|
|
360
|
-
artifactName := artifactInfo[0]
|
|
361
|
-
fileName := artifactInfo[1]
|
|
362
|
-
if artifactName == UntrustedExportDataArtifactName {
|
|
363
|
-
return fmt.Errorf("unexpected artifact name %s", artifactName)
|
|
364
|
-
}
|
|
365
|
-
err = writeFileToPayload(fileName, artifactName)
|
|
366
|
-
if err != nil {
|
|
367
|
-
return err
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
activeSnapshot.retrieved = true
|
|
372
|
-
activeSnapshot.logger.Info("retrieved snapshot", "exportDir", exportDir)
|
|
373
|
-
|
|
374
|
-
return nil
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// RestoreExtension restores an extension state snapshot,
|
|
378
|
-
// the payload reader returns `io.EOF` when it reaches the extension boundaries.
|
|
379
|
-
// Implements ExtensionSnapshotter
|
|
380
|
-
func (snapshotter *SwingsetSnapshotter) RestoreExtension(height uint64, format uint32, payloadReader snapshots.ExtensionPayloadReader) error {
|
|
381
|
-
if format != SnapshotFormat {
|
|
382
|
-
return snapshots.ErrUnknownFormat
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
err := snapshotter.checkNotActive()
|
|
386
|
-
if err != nil {
|
|
387
|
-
return err
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// We technically don't need to create an active snapshot here since both
|
|
391
|
-
// `InitiateSnapshot` and `RestoreExtension` should only be called from the
|
|
392
|
-
// main thread, but it doesn't cost much to add in case things go wrong.
|
|
393
|
-
active := &activeSnapshot{
|
|
394
|
-
isRestore: true,
|
|
395
|
-
height: int64(height),
|
|
396
|
-
logger: snapshotter.logger,
|
|
397
|
-
// goroutine synchronization is unnecessary since anything checking should
|
|
398
|
-
// be called from the same thread.
|
|
399
|
-
// Effectively `WaitUntilSnapshotStarted` would block infinitely and
|
|
400
|
-
// and `InitiateSnapshot` will error when calling `checkNotActive`.
|
|
401
|
-
startedResult: nil,
|
|
402
|
-
done: nil,
|
|
403
|
-
}
|
|
404
|
-
snapshotter.activeSnapshot = active
|
|
405
|
-
defer func() {
|
|
406
|
-
snapshotter.activeSnapshot = nil
|
|
407
|
-
}()
|
|
408
|
-
|
|
409
|
-
ctx := snapshotter.newRestoreContext(int64(height))
|
|
410
|
-
|
|
411
|
-
exportDir, err := os.MkdirTemp("", fmt.Sprintf("agd-state-sync-restore-%d-*", height))
|
|
412
|
-
if err != nil {
|
|
413
|
-
return err
|
|
414
|
-
}
|
|
415
|
-
defer os.RemoveAll(exportDir)
|
|
416
|
-
|
|
417
|
-
manifest := exportManifest{
|
|
418
|
-
BlockHeight: height,
|
|
419
|
-
Data: ExportDataFilename,
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
exportDataFile, err := os.OpenFile(filepath.Join(exportDir, ExportDataFilename), os.O_CREATE|os.O_WRONLY, ExportedFilesMode)
|
|
423
|
-
if err != nil {
|
|
424
|
-
return err
|
|
425
|
-
}
|
|
426
|
-
defer exportDataFile.Close()
|
|
427
|
-
|
|
428
|
-
// Retrieve the SwingStore "ExportData" from the verified vstorage data.
|
|
429
|
-
// At this point the content of the cosmos DB has been verified against the
|
|
430
|
-
// AppHash, which means the SwingStore data it contains can be used as the
|
|
431
|
-
// trusted root against which to validate the artifacts.
|
|
432
|
-
swingStoreEntries := snapshotter.getSwingStoreExportData(ctx)
|
|
433
|
-
|
|
434
|
-
if len(swingStoreEntries) > 0 {
|
|
435
|
-
encoder := json.NewEncoder(exportDataFile)
|
|
436
|
-
encoder.SetEscapeHTML(false)
|
|
437
|
-
for _, dataEntry := range swingStoreEntries {
|
|
438
|
-
entry := []string{dataEntry.Path, dataEntry.Value}
|
|
439
|
-
err := encoder.Encode(entry)
|
|
440
|
-
if err != nil {
|
|
441
|
-
return err
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
writeExportFile := func(filename string, data []byte) error {
|
|
447
|
-
return os.WriteFile(filepath.Join(exportDir, filename), data, ExportedFilesMode)
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
for {
|
|
451
|
-
payloadBytes, err := payloadReader()
|
|
452
|
-
if err == io.EOF {
|
|
453
|
-
break
|
|
454
|
-
} else if err != nil {
|
|
455
|
-
return err
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
payload := types.ExtensionSnapshotterArtifactPayload{}
|
|
459
|
-
if err = payload.Unmarshal(payloadBytes); err != nil {
|
|
460
|
-
return err
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
switch {
|
|
464
|
-
case payload.Name != UntrustedExportDataArtifactName:
|
|
465
|
-
// Artifact verifiable on import from the export data
|
|
466
|
-
// Since we cannot trust the state-sync payload at this point, we generate
|
|
467
|
-
// a safe and unique filename from the artifact name we received, by
|
|
468
|
-
// substituting any non letters-digits-hyphen-underscore-dot by a hyphen,
|
|
469
|
-
// and prefixing with an incremented id.
|
|
470
|
-
// The filename is not used for any purpose in the snapshotting logic.
|
|
471
|
-
filename := sanitizeArtifactName(payload.Name)
|
|
472
|
-
filename = fmt.Sprintf("%d-%s", len(manifest.Artifacts), filename)
|
|
473
|
-
manifest.Artifacts = append(manifest.Artifacts, [2]string{payload.Name, filename})
|
|
474
|
-
err = writeExportFile(filename, payload.Data)
|
|
475
|
-
|
|
476
|
-
case len(swingStoreEntries) > 0:
|
|
477
|
-
// Pseudo artifact containing untrusted export data which may have been
|
|
478
|
-
// saved separately for debugging purposes (not referenced from the manifest)
|
|
479
|
-
err = writeExportFile(UntrustedExportDataFilename, payload.Data)
|
|
480
|
-
|
|
481
|
-
default:
|
|
482
|
-
// There is no trusted export data
|
|
483
|
-
err = errors.New("cannot restore from untrusted export data")
|
|
484
|
-
// snapshotter.logger.Info("using untrusted export data for swingstore restore")
|
|
485
|
-
// _, err = exportDataFile.Write(payload.Data)
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if err != nil {
|
|
489
|
-
return err
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
err = exportDataFile.Sync()
|
|
494
|
-
if err != nil {
|
|
495
|
-
return err
|
|
496
|
-
}
|
|
497
|
-
exportDataFile.Close()
|
|
498
|
-
|
|
499
|
-
manifestBytes, err := json.MarshalIndent(manifest, "", " ")
|
|
500
|
-
if err != nil {
|
|
501
|
-
return err
|
|
502
|
-
}
|
|
503
|
-
err = writeExportFile(ExportManifestFilename, manifestBytes)
|
|
504
|
-
if err != nil {
|
|
505
|
-
return err
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
encodedExportDir, err := json.Marshal(exportDir)
|
|
509
|
-
if err != nil {
|
|
510
|
-
return err
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
action := &snapshotAction{
|
|
514
|
-
Type: "COSMOS_SNAPSHOT",
|
|
515
|
-
BlockHeight: int64(height),
|
|
516
|
-
Request: "restore",
|
|
517
|
-
Args: []json.RawMessage{encodedExportDir},
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
_, err = snapshotter.blockingSend(action, true)
|
|
521
|
-
if err != nil {
|
|
522
|
-
return err
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
snapshotter.logger.Info("restored snapshot", "exportDir", exportDir, "height", height)
|
|
526
|
-
|
|
527
|
-
return nil
|
|
528
|
-
}
|