-
Notifications
You must be signed in to change notification settings - Fork 87
feat: Add block validation logic against validated transactions (in-memory) #1460
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
Conversation
| // Remove the validated transactions from the cache. | ||
| let mut validated_txs = validated_transactions.write().await; | ||
| for tx_header in body.transactions().as_slice() { | ||
| validated_txs.remove(&tx_header.id()); | ||
| } | ||
| // Release the validated transactions write lock. | ||
| drop(validated_txs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest not manually managing the locks as it becomes easy to refactor your way into lock issues. How about replacing the lock type alias with a proper struct ValidatedTransactions instead?
#[derive(Default, Clone)]
struct ValidatedTransactions(RwLock<HashMap<TransactionId, TransactionHeader>>);
impl ValidatedTransactions {
pub async fn check(&self, txs: impl Iterator<TransactionId>) -> Result<(), BlockValidationError>{
let inner = self.0.read().await;
...
}
pub async fn remove(&self, txs: ...) {
let inner = self.0.read().await;
for tx in txs {
inner.remove(tx);
}
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although thinking on this some more, I don't think we can actually remove these transactions at this stage.
We should only remove transactions once we know that the block has successfully landed in the store? Right now we're assuming that if this function is called that the caller successfully completes its end i.e. we're being optimistic.
Do we not need two methods, one for signing and one for the store to submit a BlockCommitted(TransactionIDs)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep the transactions around for a long time. As I mentioned in my other comment, until we have persistence implemented, we could probably use something like an LRU cache (with fairly generous capacity) to keep transactions around. This way, we should be able to avoid the need for manual dropping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added Lru impl
bobbinth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you! I left some comments inline.
crates/validator/src/server/mod.rs
Outdated
| /// A type alias for a read-write lock that stores validated transactions. | ||
| pub type ValidatedTransactions = RwLock<HashMap<TransactionId, TransactionHeader>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we won't have the time to implement proper persistence for this before the next release. So, maybe this could be something like an LRU cache? We could set the size to be pretty big (e.g., thousands of transactions), but at least we'll be able to keep it bounded and we won't need to manually manage transaction eviction.
In the longer run, we probably want to store transactions for a long time for audibility purposes - but that's something to implement when we add persistence mechanism.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we won't have the time to implement proper persistence for this before the next release
If we release the in-memory version won't we risk causing a halt if we restart the node? Which is to say that we can't release this until persistence is in place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo just adding a validator.sqlite with a basic
CREATE TABLE transactions (
id BLOB PRIMARY KEY,
header BLOB NOT NULL,
body BLOB NOT NULL,
timestamp INTEGER NOT NULL,
);is probably quicker to do than the time we spend thinking and discussing this :)
The "hardest" part will be the cli configuration and briefly thinking about how/if concurrent writes are a concern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was also the issue of code factoring because the validator db wouldn't go through the store. I had a PR a while ago that implemented a basic approach for it. I assume we are still of the mind that it doesn't make sense for the validator db to be tied to the store, but I haven't thought about it for a while now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be its own separate database
| // Remove the validated transactions from the cache. | ||
| let mut validated_txs = validated_transactions.write().await; | ||
| for tx_header in body.transactions().as_slice() { | ||
| validated_txs.remove(&tx_header.id()); | ||
| } | ||
| // Release the validated transactions write lock. | ||
| drop(validated_txs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep the transactions around for a long time. As I mentioned in my other comment, until we have persistence implemented, we could probably use something like an LRU cache (with fairly generous capacity) to keep transactions around. This way, we should be able to avoid the need for manual dropping.
bobbinth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you! I left one small comment inline.
| /// Number of transactions to keep in the validated transactions cache. | ||
| const NUM_VALIDATED_TRANSACTIONS: NonZeroUsize = NonZeroUsize::new(7000).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's bump this up to 10K transactions.
Context
We need block validation to involve a check that all the transactions in a given block have previously been validated by the Validator.
We will begin by doing this in memory. Followup PR will add persistence.
Relates to #1316.
Changes