Combine owners and nonce tables#391
Conversation
|
Thanks for the PR!
Yeah, I also noticed that the tests for the database can not be run on their own due to this. There is some other PR where I add the "sync" feature.
Makes sense. But I think we might need to adjust this: to also accept NULL values by reading anOption<String>.
I think defaulting the nonce to I will add two test cases to illustrate these issues :) |
|
I changed the in-memory storage a bit. I changed nonce storage to
Also modified all the related nonce code to use I'm happy changing it either way you see fit. A simpler solution is to leave it like it was, and handle situations with NULL nonces like this: |
| INSERT INTO nonce (owner, nonce) VALUES (?1, 0) | ||
| ON CONFLICT (owner) DO UPDATE SET nonce = nonce + 1 | ||
| INSERT INTO owners (owner, nonce) VALUES (?1, 0) | ||
| ON CONFLICT (owner) DO UPDATE SET nonce = COALESCE(nonce, -1) + 1 |
There was a problem hiding this comment.
Oooh, good idea, I like it :)
I think COALESCE(nonce + 1, 0) might be easier to understand and should also work because NULL + 1 computes to NULL.
|
Some required checks have failed. Could you please take a look @petarjuki7? 🙏 |
There was a problem hiding this comment.
Pull Request Overview
Combines the previous owners and nonce tables into a single owners table, making both fee_recipient and nonce nullable and updating all related queries and code paths accordingly.
- Merged schema: replaced separate
ownersandnoncetables with oneownerstable containing both fields - Refactored SQL operations and Rust code to handle nullable
fee_recipientandnonce - Added tests for NULL handling of
fee_recipientand nonce consistency across operations
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| anchor/database/src/validator_operations.rs | Adapt fee_recipient_for_owner to return Option<Address> |
| anchor/database/src/tests/cluster_tests.rs | Add tests for NULL fee_recipient and nonce progression logic |
| anchor/database/src/table_schema.sql | Drop old tables and define unified owners schema |
| anchor/database/src/state.rs | Filter out rows with NULL nonce when collecting nonces |
| anchor/database/src/sql_operations.rs | Update SQL queries for merged owners table |
| // Get the nonce from column 1 | ||
| let nonce = row.get(1)?; | ||
| Ok((owner, nonce)) | ||
| })? |
There was a problem hiding this comment.
[nitpick] The filter_map call silently drops owners with a NULL nonce. If that’s intended, add a comment clarifying this behavior; otherwise consider handling the None case explicitly.
| })? | |
| })? | |
| // Owners with a `NULL` nonce are intentionally dropped here. |
Can we say an unused nonce is 0? |
Since nonces in Ethereum start with 0 (the first transactions nonce is 0), then maybe putting it at 0 could read as "already had a first transaction as nonce 0" and not that it hasn't been set yet |
Are you sure? The yellow paper defines nonce as the number of transactions sent from the address. |
It also says "An account is empty when it has no code, zero nonce and zero balance" |
|
We are not talking about Ethereum nonces here, but nonces as used in the SSV network. In the database, we store the previous, not the next nonce. Therefore there is a difference between 0 (which means the last |
|
To clear up the confusion why I put " 👍 " to
While we do not work with Ethereum nonces, but with nonces as used in SSV's I will test this change on some nodes to ensure it's working fine, then we can merge this :) Thanks @petarjuki7! |
|
I found another small issue: we needed to account for the null value when We will merge this soon after deciding what will go into the next version 👍 |
Great, thanks! :) Thought of something else that might be worth changing: anchor/anchor/database/src/cluster_operations.rs Lines 164 to 180 in 94d9d4a It could be changed to: let mut stmt = tx.prepare_cached(sql_operations::GET_NONCE)?;
let nonce: u16 = stmt.query_row(params![owner.to_string()], |row| row.get(0))?;
// Update in memory with the truth from the database
self.modify_state(|state| {
state.single_state.nonces.insert(*owner, nonce);
});
Ok(nonce)So we are sure it is the same in the database as in memory. This removes any risk of the in-memory logic diverging from the SQL logic. This passes all the tests also, let me know what do you think, I can include it in a commit. |
|
Thanks for your suggestion! Our caching layer operates under the assumption that the cache update is implemented consistently with the actual database upgrade. Also, no other processes modify the database - which will be enforced by #358. So I think to keep the approach to DB updates consistent, we should keep the current state. @ThreeHrSleep maybe this is a good target for fuzz testing to ensure the update functions are correct in all cases:
|
Addresses Issue sigp#385 There is now one table `owners` which has both the `fee_recipient` and `nonce` fields.
Addresses Issue sigp#385 There is now one table `owners` which has both the `fee_recipient` and `nonce` fields.
Issue Addressed
Addresses Issue #385
Proposed Changes
There is now one table
ownerswhich has both thefee_recipientandnoncefields.Additional Info
Removed
NOT NULLonfee_recipient, had to do it because on theBUMP_NONCEcommand we are only inserting onownerandnonce.Tested with
cargo testin thedatabasecrate and all the tests pass.Side comment:
As far as I can tell
cargo testdoesn't work on thestablebranch, because in the top levelCargo.tomlthere is asyncfeature missing in thetokiodependency import.