penumbra
penumbra copied to clipboard
view service should gracefully handle duplicate note commitment during sync
Describe the bug If an attacker sends a duplicate note commitment to a target address, when the target tries to sync the view service will hang (PING/REJECT vector)
To Reproduce Steps to reproduce the behavior:
You need to send two notes with the same (address, value, rseed) to the target.
- Set the Rseed in
OutputPlan::new
to e.g.Rseed([0u8; 32])
. Relevant line here. - Send the same value to the target address twice, e.g. make two transactions sending 1upenumbra to the address
- As the target, sync
Expected behavior
View service syncs without hanging
Actual behavior
View service hangs due to unique constraint violation:
2024-03-04T23:34:11.859746Z ERROR penumbra_view::worker: view worker error e=UNIQUE constraint failed: spendable_notes.note_commitment
Caused by:
Error code 1555: A PRIMARY KEY constraint failed
Scanning blocks from last sync height 96893 to latest height 97379
[0s] ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/486 0/s ETA: 0s
^C
Flagging that #1507 is very similar, might be the same underlying issue. Not sure how easy it is to write a regression test to cover this case
We concluded in discord that the TCT crate is doing the right thing, and that the fix is to use an upsert to avoid the constraint violation. It would be helpful to summarize that discussion in the issue.
blocked by planner refactor but should be prioritized once unblocked
Suggestion from Discord discussion:
- check all new note commitments against storage
- if note commitment already known, write a zero byte into the note payload ciphertext
- this triggers decryption failure
Looking at it I think there's an even simpler solution here, which is to modify the record_block
behavior to ignore/do nothing when it receives a duplicate note (or swap) commitment - this seems to be basically a primary key uniqueness violation on account of having overly simple INSERT
behavior - I'll throw up a PR shortly and try to reproduce to ensure this works
we might need to upsert rather than discard, if the TCT behavior is to retain the auth path for the newer duplicate leaf, since in that case the TCT would have an auth path for the newer note rather than the older one
Oh yeah, I'll ensure that's the case and if the auth path is being updated out of sync with view storage, just update the columns with the data attending the duplicate commitment