@@ -178,6 +178,56 @@ export interface FetcherState {
178
178
heights : OrderedMap < string , number >
179
179
}
180
180
181
+ /**
182
+ * This entry collects the full contract state.
183
+ * This is obviously expensive to store, so it should be improved,
184
+ * if the MVP proves successful.
185
+ */
186
+ export interface FullState {
187
+ /**
188
+ * The number of seconds elapsed since unix epoch of when the latest contract ledger was closed.
189
+ */
190
+ timestamp : number
191
+ /**
192
+ * The ledger number that this state corresponds to.
193
+ */
194
+ height : number
195
+ /**
196
+ * The hash of the latest transaction that led to this state.
197
+ */
198
+ latestTxHash : string
199
+ /**
200
+ * The address of the contract being called.
201
+ */
202
+ contractId : string
203
+ /**
204
+ * Ordered mapping from contract address to instance/persistent/temporary storage
205
+ * of the respective contract. The contract storage for a given durability is
206
+ * an ordered mapping from field names to their native values (JS).
207
+ *
208
+ * This mapping contains values only for the fields that have been created
209
+ * or updated by a transaction in the past. It may happen that
210
+ * `storage` contains fewer fields than `oldStorage`, when the contract
211
+ * deletes some fields from the storage. Also, fields may be cleared from `storage`
212
+ * when the storage goes over TTL.
213
+ */
214
+ storage : MultiContractStorage
215
+ }
216
+
217
+ /**
218
+ * The empty full state that is to be initialized.
219
+ * @returns an empty full state
220
+ */
221
+ export function emptyFullState ( ) : FullState {
222
+ return {
223
+ timestamp : 0 ,
224
+ height : 0 ,
225
+ latestTxHash : '' ,
226
+ contractId : '' ,
227
+ storage : emptyMultiContractStorage ( ) ,
228
+ }
229
+ }
230
+
181
231
/**
182
232
* Given the solarkraft home, construct the path to store the transactions.
183
233
* @param solarkraftHome path to solarkraft home (or project directory)
@@ -191,8 +241,9 @@ export function storagePath(solarkraftHome: string): string {
191
241
* Store a contract call entry in the file storage.
192
242
* @param home the storage root directory
193
243
* @param entry a call entry
244
+ * @returns the filename, where the entry was stored
194
245
*/
195
- export function saveContractCallEntry ( home : string , entry : ContractCallEntry ) {
246
+ export function saveContractCallEntry ( home : string , entry : ContractCallEntry ) : string {
196
247
const filename = getEntryFilename ( storagePath ( home ) , entry )
197
248
const verificationStatus : VerificationStatus =
198
249
entry . verificationStatus ?? VerificationStatus . Unknown
@@ -261,6 +312,44 @@ export function loadContractCallEntry(
261
312
} )
262
313
}
263
314
315
+ /**
316
+ * Load full state of a contract from the storage.
317
+ * @param solarkraftHome the .solarkraft directory
318
+ * @param contractId the contract address
319
+ * @returns the loaded full state
320
+ */
321
+ export function loadContractFullState (
322
+ solarkraftHome : string ,
323
+ contractId : string
324
+ ) : Result < FullState > {
325
+ const filename = getFullStateFilename (
326
+ storagePath ( solarkraftHome ) ,
327
+ contractId
328
+ )
329
+ if ( ! existsSync ( filename ) ) {
330
+ return left ( `No state found for contract ${ contractId } ` )
331
+ }
332
+ const contents = readFileSync ( filename )
333
+ const loaded = JSONbig . parse ( contents )
334
+ return right ( {
335
+ ...loaded ,
336
+ storage : storageFromJS ( loaded . storage ) ,
337
+ } )
338
+ }
339
+
340
+ /**
341
+ * Store contract full state in the file storage.
342
+ * @param home the storage root directory
343
+ * @param state a state of the contract
344
+ * @returns the filename, where the state was stored
345
+ */
346
+ export function saveContractFullState ( home : string , state : FullState ) : string {
347
+ const filename = getFullStateFilename ( storagePath ( home ) , state . contractId )
348
+ const contents = JSONbig . stringify ( state )
349
+ writeFileSync ( filename , contents )
350
+ return filename
351
+ }
352
+
264
353
/**
265
354
* Generate storage entries for a given contract id in a path.
266
355
* @param contractId contract identifier (address)
@@ -275,7 +364,8 @@ export function* yieldListEntriesForContract(
275
364
if ( dirent . isDirectory ( ) && / ^ [ 0 - 9 ] + $ / . exec ( dirent . name ) ) {
276
365
// This directory may contain several transactions for the same height.
277
366
const height = Number . parseInt ( dirent . name )
278
- for ( const ledgerDirent of readdirSync ( join ( path , dirent . name ) , {
367
+ const dirPath = join ( path , dirent . name )
368
+ for ( const ledgerDirent of readdirSync ( dirPath , {
279
369
withFileTypes : true ,
280
370
} ) ) {
281
371
// match all storage entries, which may be reported in different cases
@@ -284,10 +374,7 @@ export function* yieldListEntriesForContract(
284
374
)
285
375
if ( ledgerDirent . isFile ( ) && matcher ) {
286
376
const txHash = matcher [ 1 ]
287
- const filename = join (
288
- ledgerDirent . path ,
289
- `entry-${ txHash } .json`
290
- )
377
+ const filename = join ( dirPath , `entry-${ txHash } .json` )
291
378
const contents = JSONbig . parse (
292
379
readFileSync ( filename , 'utf-8' )
293
380
)
@@ -351,10 +438,22 @@ export function saveFetcherState(home: string, state: FetcherState): string {
351
438
* @returns the filename
352
439
*/
353
440
function getEntryFilename ( root : string , entry : ContractCallEntry ) {
354
- const dir = getOrCreateDirectory ( root , entry )
441
+ const dir = getOrCreateEntryParentDir ( root , entry )
355
442
return join ( dir , `entry-${ entry . txHash } .json` )
356
443
}
357
444
445
+ /**
446
+ * Get the directory name to store contract state.
447
+ *
448
+ * @param root storage root
449
+ * @param contractId the contract id to retrieve the state for
450
+ * @returns the directory name
451
+ */
452
+ function getFullStateFilename ( root : string , contractId : string ) {
453
+ const dir = getOrCreateContractDir ( root , contractId )
454
+ return join ( dir , 'state.json' )
455
+ }
456
+
358
457
/**
359
458
* Get the filename for the fetcher state.
360
459
*
@@ -374,8 +473,23 @@ function getFetcherStateFilename(root: string) {
374
473
* @param entry call entry
375
474
* @returns the directory name
376
475
*/
377
- function getOrCreateDirectory ( root : string , entry : ContractCallEntry ) {
378
- const directory = join ( root , entry . contractId , entry . height . toString ( ) )
476
+ function getOrCreateEntryParentDir ( root : string , entry : ContractCallEntry ) {
477
+ const contractDir = getOrCreateContractDir ( root , entry . contractId )
478
+ const directory = join ( contractDir , entry . height . toString ( ) )
479
+ mkdirSync ( directory , { recursive : true } )
480
+ return directory
481
+ }
482
+
483
+ /**
484
+ * Return the contract directory.
485
+ * If this directory does not exist, create it recursively.
486
+ *
487
+ * @param root storage root
488
+ * @param contractId contract address
489
+ * @returns the directory name
490
+ */
491
+ function getOrCreateContractDir ( root : string , contractId : string ) {
492
+ const directory = join ( root , contractId )
379
493
mkdirSync ( directory , { recursive : true } )
380
494
return directory
381
495
}
0 commit comments