"Warp" Sync - a full scan method

For light wallets, the initial sync time is a big issue. Depending on how many notes a shielded wallet has, it can take several minutes to hours to completely scan the blockchain.

Today, I’d like to introduce a technique that can scan from sapling activation height in less than 15 seconds (on a desktop machine) even for big wallets.

Besides the speed benefits, warp sync creates the same outputs as normal sync (byte for byte). It allows you to mix different strategies: warp to recent block then incrementally sync the remaining ones.
Or, Warp sync for the initial sync then incrementally sync afterward.

Roughly speaking, it calculates the final state (at the current height) without applying every block one by one. Most of the processing is also done in parallel.

Example of a run:

[2021-06-19T02:22:34Z INFO  sync::scan] Download chain: 5751 ms
[2021-06-19T02:22:40Z INFO  sync::scan] Decrypt Notes: 5983 ms
[2021-06-19T02:22:40Z INFO  sync::chain] Build CMU list: 53 ms - 675844 nodes
[2021-06-19T02:22:43Z INFO  sync::chain] Tree State & Witnesses: 2234 ms
[2021-06-19T02:22:43Z INFO  sync::scan] # Witnesses 9
[2021-06-19T02:22:43Z INFO  sync::scan] Total: 14024 ms
  • Downloading the compact blocks from lightwalletd took 5751 ms. It’s from a server that runs locally. Network speed matters a lot here.
  • Looking for our notes took 5983 ms. It has to trial decrypt every output using our IVK and it is performed in parallel across all CPUs.
  • It built the list of note commitments, i.e. the leaves of the commitment tree. It took 53 ms. The tree has 675844 nodes, i.e. that’s the total number of spend outputs since activation.
  • It calculated the current tree state and the witness for each of our received notes in 2234 ms. This is where the massive gains come from. This step could have taken hours. We will describe how Warp Sync performs this step differently.

At this point, we are completely synced. There is no additional data or computation done in the background. The wallet is ready.

Here’s a chart of running time with 10 runs. It looks pretty consistent.

As mentioned earlier, the most time-consuming step is the maintenance of the tree state and the witness data. That’s required if you want to spend.

Commitment Tree

The commitment tree contains all the output note commitments (basically a hash value). There are 675844 of them and they are 32 bytes long. They form the leaves. Then you hash two of them at a time to build the 2nd layer. And repeat until there is a single value, the root.

Here’s a nice explanation:

Hash calculations in zcash are relatively expensive because it uses Pedersen Hash that involves elliptic curve computation.

Our goal is to do the minimum number of hash calculations possible.

The commitment tree is incremental and new nodes are always appended to the end. Nodes are never removed or updated. As such, the insertion of a new note requires one hash calculation.

A tree of N nodes can be built with about N hash calculations.

That’s very good and it’s hard to do better than that.

librustzcash has an object called CommitmentTree which supports appending a node in about constant time. It is used to compute the tree state at the end of each block.

Witness Data

Our notes are in the tree as well and we know where they are. When we spend a note, we have to use the Merkle path of the note to build the transaction. The path of a given note remains the same (since a note never moves) but the values of the nodes that the path goes through, can change. For example, the value of the root node will always change. The process of updating the path values is called “updating the incremental witness”. librustzcash has a class IncrementalWitness which keeps care of it.

In the end, we have a CommitmentTree and a bunch of IncrementalWitness (one per note we received). On seeing a new transaction output, i.e. a new note, we use .append on CommitmentTree and IncrementalWitness and they update.

In itself, adding a note is fast. But when we have to do ~675k hashes multiplied by the number of notes we received it becomes very slow. The more notes we receive, the worse it gets.

(You could avoid updating witnesses if you know that the note has been spent but the scalability issue remains.)

Warp Sync

From a very high level, Warp Sync uses the fact that the Incremental Witnesses are paths of the same tree. When we build the tree state and combine nodes to form each level, we can simultaneously update the Commitment Tree state and all the Witnesses without computing any hashes for the Witnesses. The algorithm reuses the work done when the Merkle Tree is built and is interleaved with its creation.

The name “Warp Sync” comes from the idea that we don’t travel through the path made by each modification to the Tree but we “warp” to the final state. It takes longer than a single incremental change but is much faster than applying them cumulatively.

Also, the number of witnesses has little impact on the run time.

Having 1 000 witnesses only increased the run time by less than 1 sec. In traditional sync, the runtime would be x1000.

Lastly, another optimization comes from the calculation of the Merkle tree. It combines two nodes independently from other nodes and therefore is a prime candidate for parallelization.

Conclusion

This was an interesting investigation and I’m glad that we could find a method that speeds up the synchronization of a large number of blocks especially when the workflow is straightforward.
Warp Sync doesn’t need advanced multitasking. I use rayon to do parallel iteration. That’s all, no locks, no spawning tasks, workers, etc. The big gain comes from the algorithm itself.

16 Likes

Good work @hanh

1 Like

Thanks, I’m also working on an improvement to this method. It will be usable for short syncs too (even 1 block).

Currently, it only works if the starting state is empty.

Once ready, wallets will be able to sync in seconds regardless of their size and last time opened.

8 Likes

I improved it and it works for incremental updates too.

Even for short sync, warp is faster than normal sync.

For example, if you have 20 received notes in your wallet and sync when you are just 4 transactions behind (assuming 2 outputs per tx), Warp takes less than 1ms, normal sync will take 4 ms. It is not a noticeable difference though.

But at a 4000 tx sync, normal sync takes ~4s while warp sync is 20 ms.

Gap Warp Normal
8 0 4
16 0 7
32 1 14
64 1 29
128 1 58
256 1 117
512 2 234
1024 4 464
2048 5 927
4096 9 1856
8192 16 3714

time in ms, gap in number of shielded outputs to process
wallet has 20 received notes.

At 1 million outputs, warp takes 3s. Note that we don’t have that many outputs yet. Normal sync clocks at ~ 8mn.

All in all, it looks like a really big performance increase.

9 Likes

This seems like a really important and under-appreciated post considering the difficulties lightwallets are currently having. Light wallets without warp sync seem to be really struggling at the moment. I want to understand better how to approach warp sync as a developer.

From my understanding, correct me if I’m wrong.

The warp sync code is here:

Ywallet builds this into the client? But it can also be run as a standalone server?

I’d really love to see some hand-holdy guides about how to build and develop on zwallet and zcash-sync. Have you considered making a grant proposal? Bringing the power of this library to newbie developers with guides and dev documentation would be worth a lot of Zcash IMO. I’ve tried to understand zwallet and zcash-sync for a few hours now (and I don’t want to give up just yet) but I’m having a hard time understanding how to use it and build on it.

4 Likes

With the improvements to the ECC library to sync time, do the relative speeds between most wallets and warp sync presented here still hold?
If they do, why in all this time has the ECC not just adopted warp sync as the standard?

2 Likes

If this question keeps popping up I think ECC’s engineering management should address it with a post, or so. I’ve read this question from many people and it’s a legitimate curiosity.

3 Likes

This question keeps popping up, I think ECC’s engineering management should address it. I’ve read this question from many people and it’s a legitimate curiosity. IMO this type of decision making and workflow should be transparent from the get-go, not an addendum when people start asking questions.

1 Like

Ive been asking this for a very long time. Where are the benchmarks and comparisions from ECC? Why have they been ‘not interested’ in warp sync? Even if ECC didnt want to adopt warp sync as the main sync algorithm, why didnt they allow Hanh to implement it as another option in librustzcash?

1 Like

Until specially stated otherwise, perhaps my initial perception applies – bar some embargo

Bottom line: clarity is needed

I’ll say this: I’ve been running ZecWallet and YWallet side-by-side on the same machine for over a year. A few months ago, I made a small payment from one address to another, and YWallet made it appear that all (or nearly all?) funds from the originating account were permanently gone. This remained the case long after the transaction was confirmed many times. ZecWallet showed the expected balance in both accounts. But it was understandably scary when YWallet first showed such a small balance.

Even a rescan in YWallet didn’t solve it. The only solution I found was to use ZecWallet to ‘send’ all the ZEC in that account to itself, effectively creating a new UTXO for all the funds in the account. YWallet was able to see (and understand) that one and ever since has shown the right balance again.
But that would be terrifying and paralyzing for someone who only ran YWallet. Was YWallet’s flaw due to warp sync? I don’t know. And since it was all in shielded funds, I may never know, unless perhaps I was willing to divulge my full viewing key to a highly skilled person who would tediously replay each block into YWallet to repro the problem and diagnose its cause.

I still use YWallet today due to its fast sync. But I keep in my mind a very small lack of trust due to my past experience.

Was your transaction made in zwl or ywallet originally?

Edit: What wallet you trust is your own personal decision. I will always strongly respect one’s right to choose. However, I’d also appreciate it if you don’t spread rumors about lost funds when there is no evidence.

It is my understanding that to have an equivalent scan between different wallets (in this case ZecWallet and Ywallet), you should scan both wallets no transaction filters heuristics whatsoever.

I’m sure the Ywallet team will help you troubleshoot if any issues are outstanding.

A bug can make notes unscannable from any other wallet but the one that created it. It has happened before.

1 Like

+1

In fact it happened to me when I sent from wallets using the mobile SDK and then recovering that same seed with other wallets that didn’t account for the internal change address.

The important thing is to note that funds are not lost and remain in custody of the user’s keys.

I’d also appreciate it if you don’t spread rumors about lost funds when there is no evidence.

I saw what I saw. And I reserve the right to share my experience without also sharing my viewing key. I wasn’t sharing this experience to be vindictive either. The question of why ECC hasn’t adopted warp sync was the context and I shared it as one possible cause: lack of confidence that it works in every case.

What evidence would you have me share? If you’re interested in investigating, I’d be willing to spend some time putting evidence together that would be helpful to you. Ideally not my viewing key. Maybe if I replayed similar transactions leading up to the issue on testnet that would be helpful to you?

Was your transaction made in zwl or ywallet originally?

I’m nearly certain I made it with YWallet, which I use virtually exclusively. I only fellback to ZecWallet to create a recovering transaction when YWallet seemed to lose funds.

@hanh, I’m delighted to say I have a repro for what I suspect may be the original issue I hit a few months ago.
The symptom may not be quite the same as I remember, but I may remember incorrectly. Or maybe this is a separate issue.

Right now I’m looking at YWallet (Android) and it has a non-zero balance. But it won’t let me send anything. All the notes are crossed out.
I had just completed a send of a subset of my ZEC (which had been split between sapling and orchard pools) from one account to another. The transaction maintained the sapling/orchard split.

After the transaction was mined, the balance adjusted as I expected, but I never regained the ability to spend the remaining balance, and no new note appeared in the Notes list with the ‘change’ from the transaction.
There were other complications, including that this was the 3rd time I had tried to send this transaction because the default fee in YWallet was too low and it just wasn’t getting mined. I don’t know if that factored into it or not.

But this feels reminiscent of my reported prior experience, where the only way I could find to move forward was to ‘spend’ all my remaining ZEC from another wallet (e.g. ZECWallet Lite), which at least in that case created a new note, which ultimately did show up in YWallet, allowing me to resume normal operations.

I hope this helps. It’s certainly an alarming occurrence, particularly the first time a user encounters it. I hope and expect this can be fixed, and may be just a UI issue rather than anything to do with warp sync.

2 Likes