Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Local snapshot file format #25

Merged
merged 21 commits into from
Dec 15, 2021
Merged
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
+ Feature name: `local_snapshot_file_format`
+ Start date: 2020-08-25
+ RFC PR: [iotaledger/protocol-rfcs#0000](https://github.com/iotaledger/protocol-rfcs/pull/0000)

# Summary

This RFC defines a file format for local snapshots which is compatible with Chrysalis Phase 2.

# Motivation

Nodes create local snapshots to produce ledger representations at a point in time of a given milestone in order to be able to:
* start up from a recent milestone instead of having to synchronize from the genesis transaction.
* delete old transaction data below a given milestone.

Current node implementations use a [local snapshot file format](https://github.com/iotaledger/iri-ls-sa-merger/tree/351020d3b5e342b6e9a41f2868575ab7ff8c251c#generating-an-export-file-from-a-localsnapshots-db) which only works with account based ledgers. For Chrysalis Phase 2 this file format has to be assimilated to support a UTXO based ledger.
luca-moser marked this conversation as resolved.
Show resolved Hide resolved

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add something about:
"Standardizing one single format across different node implementations"

I guess otherwise nodes could just share their dbs instead of files

# Detailed design

Since a UTXO based ledger is much larger in size, this RFC proposes two formats for snapshot files:
* a `full` format which represents a complete ledger state.
* a `delta` format which only contains diffs (consumed and spent outputs) of milestones from a given milestone index onwards.

This separation allows nodes to swiftly create new delta snapshot files, which then can be distributed with a companion full snapshot file to reconstruct a recent state.

Unlike the current format, this new formats do not longer include spent addresses since this information is no longer held by nodes.
luca-moser marked this conversation as resolved.
Show resolved Hide resolved

### Formats

> All types are serialized in little-endian
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is non-numerical data encoded?


#### Full Ledger State

A full ledger snapshot file contains the UTXOs (`outputs` section) of a node's latest solid milestone (`ledger_milestone_index`). The `diffs` contain the diffs to rollback the `outputs` state in order to regain the ledger state of the snapshot milestone at (`seps_milestone_index`).

![](https://i.imgur.com/e6WuufK.png)

While the node producing such full ledger state snapshot could theoretically pre-compute the actual snapshot milestone state, this is deferred to the consumer of the data in order to speed up local snapshot creation.

A full ledger state local snapshot is denoted by the type byte `0`:
luca-moser marked this conversation as resolved.
Show resolved Hide resolved

```
version<byte>
type<byte> = 0
timestamp<uint64>
network_id<uint64>
seps_milestone_index<uint64>
ledger_milestone_index<uint64>
seps_count<uint64>
outputs_count<uint64>
diffs_count<uint64>
seps<array[seps_count]>:
sep<array[32]>
outputs<array[outputs_count]>:
transaction_hash<array[32]>
output_index<uint16>
address:
address_type<byte>
wots_address<array[49]>
||
address_type<byte>
ed25519_address<array[32]>
value<uint64>
diffs<array[diffs_count]>:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no specific ordering of diffs you enforce here. You can have a diff for milestone X followed by a diff for milestone X+100. For integrity and validation sake it would be nicer to have an array[ledger_milestone_index - seps_milestone_index] of an array of diffs between milestone X and X+1, sorted by X, starting from seps_milestone_index and ending on ledger_milestone_index.

diff_milestone_index<array[32]>
created_outputs_count<uint64>
created_outputs<array>:
transaction_hash<array[32]>
output_index<uint16>
address:
address_type<byte> = 0
wots_address<array[49]>
||
address_type<byte> = 1
ed25519_address<array[32]>
value<uint64>
consumed_outputs_count<uint64>
consumed_outputs<array>:
transaction_hash<array[32]>
output_index<uint16>
address:
address_type<byte>
wots_address<array[49]>
||
address_type<byte>
ed25519_address<array[32]>
value<uint64>
```

#### Delta Ledger State

A delta ledger state local snapshot only contains the `diffs` of milestones starting from a given `ledger_milestone_index`. A node consuming such data must know the state of the ledger at `ledger_milestone_index`.

![](https://i.imgur.com/bt5BUpe.png)

A delta ledger state local snapshot is denoted by the type byte `1`:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though I agree a node should keep track of the diff at every milestone, should the snapshot file keep track of it?
I would just expect a diff from the base

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to include information at every milestone actually: especially given the fact that it will be a good thing to introduce MS data as well (proof of inclusion specifically).

```
version<byte>
type<byte> = 1
timestamp<uint64>
network_id<uint64>
seps_milestone_index<uint64>
ledger_milestone_index<uint64>
seps_count<uint64>
diffs_count<uint64>
seps<array[seps_count]>:
sep<array[32]>
diffs<array[diffs_count]>:
diff_milestone_index<array[32]>
created_outputs_count<uint64>
created_outputs<array>:
transaction_hash<array[32]>
output_index<uint16>
address:
address_type<byte> = 0
wots_address<array[49]>
||
address_type<byte> = 1
ed25519_address<array[32]>
value<uint64>
consumed_outputs_count<uint64>
consumed_outputs<array>:
transaction_hash<array[32]>
output_index<uint16>
address:
address_type<byte> = 0
wots_address<array[49]>
||
address_type<byte> = 1
ed25519_address<array[32]>
value<uint64>
```

# Drawbacks

Nodes need to support this new format.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you had nothing to fill in :-) ?

Maybe:

  1. We have 2 different snapshot files which may be confusing
  2. The size of the file will grow as the number of outputs increase


# Rationale and alternatives

* In conjunction with a companion full snapshot, a tool or node can "truncate" the data from a delta snapshot back to a single full snapshot. In that case, the `ledger_milestone_index` and `seps_milestone_index` would be the same. In the example above, given the full and delta snapshots, one could produce a new full snapshot for milestone 1350.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you mean that, after the truncation, the seps_milestone_index of the Delta will be == to the ledger_milestone_index of the Full. Correct?

* Since snapshots may include millions of UTXOs, code generating such files needs to stream data directly onto disk instead of keeping the entire representation in memory. In order to facilitate this, the count denotations for SEPs, UTXOs and diffs are at the beginning of the file. This allows code generating snapshot files to only have to seek back once after the actual count of elements is known.

# Unresolved questions

* Is all the information to startup a node from the local snapshot available with the described format?
* Can we get rid of the spent addresses or do we still need to keep them?
* Do we need to account for different types of outputs already? (we currently only have them deposit to addresses)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid so... #32 already introduces a new output type that is crucial for validation.