-
Notifications
You must be signed in to change notification settings - Fork 242
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
Fraud Proof: Invalid domain extrinsic root #1999
Conversation
250f2f7
to
dc1609c
Compare
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.
Reviewed commits until Substrate upgrade. I don't have enough context to understand everything, so left a bunch of questions and will probably have to have a call to clarify a bunch more, especially as it relates to Substrate changes.
crates/sp-domains/src/fraud_proof.rs
Outdated
/// This is a representation of actual Randomness on Consensus chain state. | ||
/// Any change in key or value there should be changed here accordingly. | ||
pub fn block_randomness_final_key() -> Vec<u8> { | ||
frame_support::storage::storage_prefix("Subspace".as_ref(), "BlockRandomness".as_ref()).to_vec() | ||
} |
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 do not like this at all. There is no connection to pallet-subspace
, it is not even mentioned in the comment. There is actually BlockRandomness::storage_value_final_key()
method thanks to StorageValue
trait, if you need this key, I'd say you should probably expose runtime API that returns you this key instead. If there are other similar places in domains, they should be fixed similarly as well.
domain_id, | ||
bad_receipt_hash, | ||
valid_bundle_digests, | ||
randomness_proof, |
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.
Why is randomness proof necessary? Can't something on consensus chain that cares about it store proofs? Maybe even on the client (depending on how proof is verified afterwards).
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.
Block randomness changes per block and runtime needs to have access to randomness for the block in which ER is submitted. So we provide a randomness proof present at that instant.
execution_receipt.consensus_block_number, | ||
execution_receipt.hash(), | ||
ReceiptMismatchInfo::DomainExtrinsicsRoot { | ||
consensus_block_hash, | ||
}, |
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.
Why do we have both consensus block number and hash at the same time? We can get number from hash.
This whole tuple looks strange: we have consensus block number, then has of ER, but then mismatch info enum just has consensus block hash, which is ignored below. There should be a more consistent way to do this.
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.
We need consensus block number since aux storage is fork aware and indexes number to block hashes.
This whole tuple looks strange: we have consensus block number, then has of ER, but then mismatch info enum just has consensus block hash, which is ignored below. There should be a more consistent way to do this.
I agree. We can make it consistent but right now we are re-using some data types from Fraud proof V1 and there is a TODO to clean it up as we do not know how Fraud proof V2 would look like right now
.into_iter() | ||
.zip(valid_bundle_digests) | ||
{ | ||
let bundle_digest_hash = BlakeTwo256::hash_of(&bundle_digest.bundle_digest); |
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.
Bundle digest is already a hash, why does it need to be hashed again? It doesn't make a lot of sense especially reading next line where you compare hash to non-hashed bundle digest.
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.
The naming is confusing here and updated it. ValidBundles contains the hash of the bundle digest for each bundle while this fraud proof carries the pre-image and is verified as part of the fraud proof verification.
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.
Digest is still a hash, why is it necessary to hash the hash?
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.
No it not the hash. valid_bundle
has the digest hash that is part of the ER on the runtime while fraud proof contains the original digest_hash's preimage which we are hashing and comparing. The struct is found here - https://github.com/subspace/subspace/pull/1999/files#diff-0cccf0c02c5905d75d809f04db4222140f39b09cf7abf950758ba6163fd75e45R453
Hashing, | ||
>( | ||
consensus_state_root: CBlock::Hash, | ||
bad_receipt: ExecutionReceipt< |
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 seems like bad_receipt
is a sizable data structure, can it be passed by reference instead of as an owned value?
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.
we do not need this structure after the verification, so passed them as Owned rather cloning inner fields within this function
Ok(()) | ||
} | ||
|
||
fn extrinsics_shuffling_seed<Hashing>(block_randomness: Randomness) -> Hashing::Out |
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.
If you need Randomness
in the end, why is this function returning Hashing::Out
if you have to convert it 3 times after that at all site?
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.
Since this is used on the runtime, and there is no way to get H256 from the Hasing::Out
which is Block::Hash
. So I'm converting this to H256
8753c26
to
02146b2
Compare
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.
Given context of fraud proofs v1 that are mixed with v2 and other circumstances this looks reasonable, though I may not able to judge it properly due to lack of depth of corresponding part of the codebase. Would be nice to get CI green on paritytech/polkadot-sdk#1691 that this PR requires.
.into_iter() | ||
.zip(valid_bundle_digests) | ||
{ | ||
let bundle_digest_hash = BlakeTwo256::hash_of(&bundle_digest.bundle_digest); |
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.
Digest is still a hash, why is it necessary to hash the hash?
crates/pallet-domains/src/lib.rs
Outdated
@@ -1779,7 +1780,7 @@ impl<T: Config> Pallet<T> { | |||
} | |||
|
|||
pub fn extrinsics_shuffling_seed() -> T::Hash { | |||
let seed = EXTRINSICS_SHUFFLING_SEED; | |||
let seed = DOMAIN_EXTRINSICS_SHUFFLING_SEED; |
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.
The concern this name was that it is essentially "seed's seed"
02146b2
to
3c94c2e
Compare
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 have renamed the const to DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT
since that is the what the variable is called in randomness trait. I'm open for an different that makes more sense 👍🏼
Note: Rebased last two commits rather than appending since they anyway handle the review suggestions and changes are much less from the previous version of commits
.into_iter() | ||
.zip(valid_bundle_digests) | ||
{ | ||
let bundle_digest_hash = BlakeTwo256::hash_of(&bundle_digest.bundle_digest); |
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.
No it not the hash. valid_bundle
has the digest hash that is part of the ER on the runtime while fraud proof contains the original digest_hash's preimage which we are hashing and comparing. The struct is found here - https://github.com/subspace/subspace/pull/1999/files#diff-0cccf0c02c5905d75d809f04db4222140f39b09cf7abf950758ba6163fd75e45R453
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 update polkadot-sdk fork PR with latest version of paritytech/polkadot-sdk#1691, update it here, resolve conflicts and should be good to go.
Substrate upgrade should really be its own PR |
688f59f
to
d16a9dc
Compare
@nazar-pc removed the commit that updates the substrate commit and will create a PR once you back port your PR 👍🏼 |
d16a9dc
to
01b83c0
Compare
01b83c0
to
5ddbbeb
Compare
8a2b10b
to
c219eca
Compare
…verify the invalid domain extrinsincs block root. trie root derivation was taken from trie-db with necessary modifications to allow for generating root using just hash of extrinsics.
…k and use StateVersion::V1 for domains
5ddbbeb
to
b700e16
Compare
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 were too many force-pushes to understand what is going on. I'll just assume that nothing has changed except Substrate upgrade.
@nazar-pc yes, nothing has changed and rebased on top the upgrade PR. Will merge this in |
for (valid_bundle, bundle_digest) in bad_receipt | ||
.valid_bundles | ||
.into_iter() | ||
.zip(valid_bundle_digests) |
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.
We should check the length of bad_receipt.valid_bundles
and fraud_proof.valid_bundle_digests
to be equal first, otherwise the malicious operator can use a fraudulent valid_bundle_digests
to construct a valid fraud proof that target a valid ER.
ConsensusBlockInfo::<T>::insert( | ||
domain_id, | ||
parent_number, | ||
(parent_hash, T::Hash::default()), |
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.
Would prefer to have a comment explaining why it is okay to use the default value here
let domain_block = get_block_tree_node_at::<Test>(domain_id, bad_receipt_at).unwrap(); | ||
|
||
let bad_receipt_hash = domain_block.execution_receipt.hash(); | ||
let (fraud_proof, root) = generate_invalid_domain_extrinsic_root_fraud_proof::<Test>( | ||
domain_id, | ||
bad_receipt_hash, | ||
Randomness::from([1u8; 32]), | ||
); |
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.
This is not legitimate, it basically creates valid fraud proof that targets a valid ER, I fixed this in 44d3a69 though.
|
||
/// Submit parent state root to the blockchain. | ||
#[pallet::call_index(11)] | ||
#[pallet::weight((Weight::from_all(10_000), DispatchClass::Mandatory, Pays::No))] |
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.
Weight::from_all(10_000)
seems not accurate, plz leave a comment for this.
@@ -351,7 +355,7 @@ pub struct ExecutionReceipt<Number, Hash, DomainNumber, DomainHash, Balance> { | |||
/// The block hash corresponding to `domain_block_number`. | |||
pub domain_block_hash: DomainHash, | |||
/// Extrinsic root field of the header of domain block referenced by this ER. | |||
pub domain_block_extrinsic_root: DomainHash, | |||
pub domain_block_extrinsic_root: H256, |
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.
Should be ExtrinsicsRoot
@@ -998,6 +999,120 @@ async fn test_invalid_total_rewards_proof_creation() { | |||
ferdie.produce_blocks(1).await.unwrap(); | |||
} | |||
|
|||
#[tokio::test(flavor = "multi_thread")] | |||
#[ignore] |
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.
The underlying test infra for Fraud proof is still in V1 and not fully implemented.
Do you mean #1527?
This PR generates invalid extrinsic root fraud when there is a mismatch between operator local receipt and submitted receipt.
Apart from this, there are two notable changes:
Note: The polkadot-sdk commit is from the PR that is yet to be merged. Once that is merged, i'll update the commit ref here and is a non blocker for the review.
Closes: #1888
Code contributor checklist: