Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Ignore global cache for patched accounts #9752

Merged
merged 3 commits into from
Oct 31, 2018
Merged

Conversation

grbIzl
Copy link
Collaborator

@grbIzl grbIzl commented Oct 15, 2018

For private transactions we patch the state in order to apply encrypted data upon it. Such state is not commited and used only in order to calculate the results of the private transaction. In terms of this PR patched state was introduced explicitly for the state and the account in order to work with cache properly. For such patched accounts only local cache should be used (as it was changed during "patching").

Closes #9494

@grbIzl grbIzl added A0-pleasereview 🤓 Pull request needs code review. M4-core ⛓ Core client code / Rust. labels Oct 15, 2018
@5chdn 5chdn added this to the 2.2 milestone Oct 15, 2018
@5chdn
Copy link
Contributor

5chdn commented Oct 26, 2018

Please review @andresilva @cheme :)

Copy link
Contributor

@cheme cheme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got some question for this review (see comment).
My principal interrogation is that eip1283 uses orginal value lru cache and I am not sure how things will run here.

Ok(self.require(a, false)?.reset_code_and_storage(code, storage))
let mut account = self.require(a, false)?;
account.switch_to_patched();
Ok(account.reset_code_and_storage(code, storage))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a question regarding 'reset_code_and_storage' : currently it does not touch account fields 'original_storage_cache', shouldn't we (I am not 100% sure but it seems safer) ?

In fact the PR seems to try to ignore lru storage cache since storage is ignored in private_tx, but to get eip-1283 running we probably want to keep trace of original_storage_value, I am starting to wonder how it will behave.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding of eip-1283 is gas optimization of SSTORE calls. For this case original cache may be required. In terms of this PR I fixed caching logic for SLOAD, which doesn't use original cache.
But again it's only my understanding. I don't know, how original cache can be further evolved. So it would be great, if @sorpaas can comment on this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine plugging the encrypted state as a replacement for the trieDB storage could make it run ok, but there may be simpler way to do it.
Unrelated question, about those private contract, what are the limitation, it seems logic that they cannot change the state of other contracts (kinda break the confidentiality), but are they allowed to call external contract?

@@ -263,6 +281,10 @@ impl Account {
if let Some(value) = self.storage_changes.get(key) {
return Some(value.clone())
}
//Use only in memory state for the patched account
if self.patched_account {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is probably true for others function ('storage_at' will query the triedb for instance), do we need this kind of guard for them too?

I wonder if it wouldn't be ok to use the storage cache and original cache value (init them in 'reset_code_and_storage') and just prevent querying the triedb at 'get_and_cache_storage' ?

Copy link
Collaborator Author

@grbIzl grbIzl Oct 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it is the guard for storage_at, because it directly calls cached_storage_at:

pub fn storage_at(&self, db: &HashDB<KeccakHasher, DBValue>, key: &H256) -> TrieResult<H256> {
		if let Some(value) = self.cached_storage_at(key) {
			return Ok(value);
		}
		Self::get_and_cache_storage(
			&self.storage_root,
			&mut self.storage_cache.borrow_mut(),
			db,
			key)
	}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 'Self::get_and_cache_storage' will be call due to this function returning 'None' value on missing key, then it will query the triedb (from the 'storage_at' code).

@@ -206,6 +214,16 @@ impl Account {
self.storage_changes = storage;
}

/// Swtich account to the patched mode
pub fn switch_to_patched(&mut self) {
self.patched_account = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reseting some account values here could make sense. Maybe this function could be combine with 'patch_account' somehow to avoid strange account state if future code forgot to call 'patch_account'.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking heavily on how to achieve this, because I also don't like the this kind of dangling state for account. Didn't find a good solution. This information (if account is patched) is required both in state and account, but the external code works with the state only. So in such constraints this was the only suitable solution (IMO).
Meanwhile 'forget' to patch account is pretty difficult, the more dangerous situation may appear, if someone starts to use patched accounts as regular ones. Here I tried to prevent this with asserts in commit methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I am missing some code/context, I only find 'switch_to_patched' function call in 'patch_account' account function, so I was simply thinking of removing this fn and make the bool switch in account fn 'reset_code_and_storage' (which seems to be also called at a single place just after switch_to_patched).

@5chdn 5chdn modified the milestones: 2.2, 2.3 Oct 29, 2018
@andresilva
Copy link
Contributor

Asked review from @sorpaas since I don't know the implications this has related to EIP-1283 (as @cheme mentioned).

Copy link
Collaborator

@sorpaas sorpaas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grbIzl Before we proceed on this, I wonder whether a simpler change would work:

  • In state::account::Account::reset_code_and_storage, in addition to what we have right now, add self.storage_root = KECCAK_NULL_RLP.

This should make it so that the patched account does not have any additional storage inherit from non-patched address. And it doesn't introduce additional complications!

My understanding of private transaction is limited, so please let me know if I get this wrong. I also have one question:

  • When we "patch" an account for private transaction, do we need to care about its nonce and balance? I didn't find current code that deals with this.

@@ -263,6 +281,10 @@ impl Account {
if let Some(value) = self.storage_changes.get(key) {
return Some(value.clone())
}
//Use only in memory state for the patched account
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing space

@@ -474,6 +496,7 @@ impl Account {

/// Commit the `storage_changes` to the backing DB and update `storage_root`.
pub fn commit_storage(&mut self, trie_factory: &TrieFactory, db: &mut HashDB<KeccakHasher, DBValue>) -> TrieResult<()> {
assert!(!self.patched_account, "Patched account should not be committed");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we sure here that patched account won't be committed?

@grbIzl
Copy link
Collaborator Author

grbIzl commented Oct 30, 2018

@sorpaas Thanks for help! Yes, resetting storage root does the trick. I reverted my changes in account logic.

As for your question: we have a regular smart contract - "wrapper" for the private contract, that stores private contract's state and code (encrypted). We call this wrapper "public contract". For the execution, we temporarily patch public contract's state and code with decrypted state and code from private contract and run transaction on such patched state. Nonce and balance during execution taken from the public contract, they are in a way "shared" between private and public contracts. So we don't need to care about them for this code. Do you see any issues with such approach?

@sorpaas
Copy link
Collaborator

sorpaas commented Oct 30, 2018

@grbIzl Thanks for the explanation. Sounds good! I don't see any issues at least for now. :)

Copy link
Collaborator

@sorpaas sorpaas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Collaborator

@sorpaas sorpaas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed this, we also need to add (also in state::account::Account::reset_code_and_storage):

if self.storage_root != KECCAK_NULL_RLP {
	self.original_storage_cache = Some((self.storage_root, Self::empty_storage_cache()));
}

This avoids patched account breaking its own checkpointing storage fetch. This particular situation would probably never really matter, but nonetheless I think it would be good to get them correct, for completeness.

Copy link
Contributor

@cheme cheme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (original_storage_at will always call cached_storage_at so it seems fine regarding my previous interrogations). Just a litle concern about the fact that we rely on this particular state of the code that may break with future code change, but I cannot think of any good test case to prevent it :)

@andresilva andresilva merged commit a3bd355 into master Oct 31, 2018
@5chdn 5chdn deleted the PatchedAccountHandling branch November 1, 2018 10:38
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-pleasereview 🤓 Pull request needs code review. M4-core ⛓ Core client code / Rust.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants