-
Notifications
You must be signed in to change notification settings - Fork 46
Extract a new extrinsics factory and nonce cache crate #498
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
| "app-libs/stf", | ||
| "cli", | ||
| "core/light-client", | ||
| "core/direct-rpc-server", | ||
| "core/tls-websocket-server", | ||
| "core/light-client", | ||
| "core/rest-client", | ||
| "core/rpc-client", | ||
| "core/rpc-server", | ||
| "core/tls-websocket-server", | ||
| "core-primitives/api-client-extensions", | ||
| "core-primitives/enclave-api", | ||
| "core-primitives/enclave-api/ffi", | ||
| "core-primitives/types", | ||
| "core-primitives/extrinsics-factory", | ||
| "core-primitives/nonce-cache", | ||
| "core-primitives/ocall-api", | ||
| "core-primitives/storage-verified", | ||
| "core-primitives/teerex-storage", | ||
| "core-primitives/settings", | ||
| "core-primitives/sgx/crypto", | ||
| "core-primitives/sgx/io", | ||
| "core-primitives/stf-executor", | ||
| "core-primitives/stf-state-handler", | ||
| "core-primitives/storage", | ||
| "core-primitives/storage-verified", | ||
| "core-primitives/teerex-storage", | ||
| "core-primitives/test", | ||
| "core-primitives/types", | ||
| "sidechain/sidechain-crate", | ||
| "sidechain/validateer-fetch", | ||
| "sidechain/primitives", | ||
| "service", | ||
| "sidechain/consensus/aura", | ||
| "sidechain/consensus/common", | ||
| "sidechain/consensus/slots", | ||
| "sidechain/primitives", | ||
| "sidechain/rpc-handler", | ||
| "sidechain/sidechain-crate", | ||
| "sidechain/state", | ||
| "sidechain/top-pool", | ||
| "sidechain/top-pool-rpc-author", | ||
| "app-libs/stf", | ||
| "service", | ||
| "core/rpc-client", | ||
| "core/rpc-server", | ||
| # "enclave", | ||
| "sidechain/validateer-fetch", |
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.
Re-ordered the members alphabetically
| /// Create extrinsics from opaque calls | ||
| /// | ||
| /// Also increases the nonce counter for each extrinsic that is created. | ||
| pub trait CreateExtrinsics { | ||
| fn create_extrinsics(&self, calls: &[OpaqueCall]) -> Result<Vec<OpaqueExtrinsic>>; | ||
| } |
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 the new trait to create extrinsics, transforming OpaqueCall into OpaqueExtrinsic
| let mut nonce_lock = self.nonce_cache.load_for_mutation()?; | ||
| let mut nonce_value = nonce_lock.0; | ||
|
|
||
| let extrinsics_buffer: Vec<OpaqueExtrinsic> = calls | ||
| .iter() | ||
| .map(|call| { | ||
| let xt = compose_extrinsic_offline!( | ||
| self.signer.clone(), | ||
| call, | ||
| nonce_value, | ||
| Era::Immortal, | ||
| self.genesis_hash, | ||
| self.genesis_hash, | ||
| RUNTIME_SPEC_VERSION, | ||
| RUNTIME_TRANSACTION_VERSION | ||
| ) | ||
| .encode(); | ||
| nonce_value += 1; | ||
| xt | ||
| }) | ||
| .map(|xt| { | ||
| OpaqueExtrinsic::from_bytes(&xt) | ||
| .expect("A previously encoded extrinsic has valid codec; qed.") | ||
| }) | ||
| .collect(); | ||
|
|
||
| *nonce_lock = Nonce(nonce_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.
Retrieves the nonce write lock, creates all the extrinsics and increments the nonce value for each of them. Then at the end, write the nonce value back into the cache.
The reason we have a strict nonce lock here, is that we want to prevent any other concurrent process to get or write the nonce while we're incrementing it here.
| // #[test] | ||
| // pub fn xts_have_increasing_nonce() { | ||
| // let nonce_cache = Arc::new(NonceCache::default()); | ||
| // nonce_cache.set_nonce(Nonce(34)).unwrap(); | ||
| // let extrinsics_factory = | ||
| // ExtrinsicsFactory::new(test_genesis_hash(), test_account(), nonce_cache); | ||
| // | ||
| // let opaque_calls = | ||
| // [OpaqueCall(vec![3u8; 42]), OpaqueCall(vec![12u8, 78]), OpaqueCall(vec![15u8, 12])]; | ||
| // let xts: Vec<UncheckedExtrinsicV4<OpaqueCall>> = extrinsics_factory | ||
| // .create_extrinsics(&opaque_calls) | ||
| // .unwrap() | ||
| // .iter() | ||
| // .map(|mut x| UncheckedExtrinsicV4::<OpaqueCall>::decode(&mut x)) | ||
| // .collect(); | ||
| // | ||
| // assert_eq!(xts.len(), opaque_calls.len()); | ||
| // assert_eq!(xts[0].signature.unwrap().2 .2, 34u128); | ||
| // } |
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 wanted to write a test where I create extrinsics and then check if the nonce in the resulting extrinsics is correct. Unfortunately, the nonce in the extrinsic is in a private member that I cannot access here 😞
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 want to be hardcore, you could check the relevant bits in the encoded extrinsics. :D.
Anyhow, we can make the nonce public in the api-client. I don't see anything against that.
| lazy_static! { | ||
| /// Global instance of a nonce cache | ||
| /// | ||
| /// Concurrent access is managed internally, using RW locks | ||
| pub static ref GLOBAL_NONCE_CACHE: Arc<NonceCache> = Default::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.
The global nonce cache instance that we can use in the enclave. Lazy initialized.
| pub mod error; | ||
| pub mod nonce_cache; | ||
|
|
||
| pub type NonceValue = u32; |
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.
NonceValue is an u32. I think there is an open issue where we want to connect this to another type definition Index?
Also I first tried to use u64 or even u128 to be more safe from overflows - but unfortunately deeper down in the extrinsics code, we use u32, so I'm forced to use u32 here too.
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.
Yes, I created the issue yesterday because I noticed the hardcoded u32 in PR #487: #495.
I also think we shouldnt define the value here but rather in the settings or wherever we have our node primitves defined. But lets leave this task to the issue itself. This is atleast one step towards that goal, thanks.
| /// Local nonce cache | ||
| /// | ||
| /// stores the nonce internally, protected by a RW lock for concurrent access | ||
| #[derive(Default)] | ||
| pub struct NonceCache { | ||
| nonce_lock: RwLock<Nonce>, | ||
| } | ||
|
|
||
| impl NonceCache { | ||
| pub fn new(nonce_lock: RwLock<Nonce>) -> Self { | ||
| NonceCache { nonce_lock } | ||
| } | ||
| } | ||
|
|
||
| impl MutateNonce for NonceCache { | ||
| fn load_for_mutation(&self) -> Result<RwLockWriteGuard<'_, Nonce>> { | ||
| self.nonce_lock.write().map_err(|_| Error::LockPoisoning) | ||
| } | ||
| } | ||
|
|
||
| impl GetNonce for NonceCache { | ||
| fn get_nonce(&self) -> Result<Nonce> { | ||
| let nonce_lock = self.nonce_lock.read().map_err(|_| Error::LockPoisoning)?; | ||
| Ok(*nonce_lock) | ||
| } | ||
| } |
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 nonce cache is basically a wrapper around a RwLock, with a bit of convenience added to it.
| fn send_extrinsics<OCallApi: EnclaveOnChainOCallApi>( | ||
| &mut self, | ||
| ocall_api: &OCallApi, | ||
| extrinsics: Vec<OpaqueExtrinsic>, | ||
| ) -> Result<(), Error>; |
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 light client is extended to have the send_extrinsics method. The OCallApi is passed as parameter, rather than having it as a member of the light client. The reason being, that the light client has to implement Encode and Decode, which will be more tricky with an Arc<OCallApi>.
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.
Could you add some description here? Like that it sends the encoded extrinsics to the parentchain?
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.
yes, done 👍
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 this is a good design. 👍
| lazy_static! { | ||
| /// the enclave's parentchain nonce | ||
| /// | ||
| /// This should be abstracted away better in the future. Currently, this only exists | ||
| /// because we produce sidechain blocks faster that parentchain chain blocks. So for now this | ||
| /// design suffices. Later, we also need to sync across parallel ecalls that might both result | ||
| /// in parentchain xt's. Then we should probably think about how we want to abstract the global | ||
| /// nonce. | ||
| static ref NONCE: SgxRwLock<u32> = SgxRwLock::new(Default::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.
Remove the global nonce value here, in favor of the nonce cache.
|
|
||
| *nonce += 1; | ||
| *nonce_lock = Nonce(nonce_value + 1); | ||
| std::mem::drop(nonce_lock); |
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.
just a small optimization: we drop the nonce write lock early, to minimize the time we're holding the lock.
| let genesis_hash = validator.genesis_hash(validator.num_relays())?; | ||
| let extrinsics_factory = | ||
| ExtrinsicsFactory::new(genesis_hash, signer, GLOBAL_NONCE_CACHE.clone()); |
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 now get the genesis hash from the light client when we crate an extrinsics factory
| //! move most parts here to the sidechain crate. | ||
| use crate::{execute_top_pool_trusted_calls, prepare_and_send_xts, Result as EnclaveResult}; | ||
| use crate::{execute_top_pool_trusted_calls, Result as EnclaveResult}; |
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 main reason we're doing this refactoring: refactor and remove the prepare_and_send_xts function from the enclave-runtime, so we can move this sidechain_impl code back into the sidechain crate.
(We still have the execute_top_pool_trusted_calls function in enclave-runtime - that will be refactored in the next PR)
11a7a2e to
40d8f29
Compare
|
|
||
| /// Create extrinsics from opaque calls | ||
| /// | ||
| /// Also increases the nonce counter for each extrinsic that is created. |
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.
Ohh, thanks alot for that one ❤️
| [features] | ||
| default = ["std"] | ||
| std = [ | ||
| "thiserror", |
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.
| "thiserror", | |
| "thiserror", | |
| "log/std" |
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.
done 👍
| pub mod error; | ||
| pub mod nonce_cache; | ||
|
|
||
| pub type NonceValue = u32; |
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.
Yes, I created the issue yesterday because I noticed the hardcoded u32 in PR #487: #495.
I also think we shouldnt define the value here but rather in the settings or wherever we have our node primitves defined. But lets leave this task to the issue itself. This is atleast one step towards that goal, thanks.
| fn send_extrinsics<OCallApi: EnclaveOnChainOCallApi>( | ||
| &mut self, | ||
| ocall_api: &OCallApi, | ||
| extrinsics: Vec<OpaqueExtrinsic>, | ||
| ) -> Result<(), Error>; |
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.
Could you add some description here? Like that it sends the encoded extrinsics to the parentchain?
Also introduce a nonce cache in the process
40d8f29 to
709bb97
Compare
clangenb
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 very good, only some minor questions.
| // #[test] | ||
| // pub fn xts_have_increasing_nonce() { | ||
| // let nonce_cache = Arc::new(NonceCache::default()); | ||
| // nonce_cache.set_nonce(Nonce(34)).unwrap(); | ||
| // let extrinsics_factory = | ||
| // ExtrinsicsFactory::new(test_genesis_hash(), test_account(), nonce_cache); | ||
| // | ||
| // let opaque_calls = | ||
| // [OpaqueCall(vec![3u8; 42]), OpaqueCall(vec![12u8, 78]), OpaqueCall(vec![15u8, 12])]; | ||
| // let xts: Vec<UncheckedExtrinsicV4<OpaqueCall>> = extrinsics_factory | ||
| // .create_extrinsics(&opaque_calls) | ||
| // .unwrap() | ||
| // .iter() | ||
| // .map(|mut x| UncheckedExtrinsicV4::<OpaqueCall>::decode(&mut x)) | ||
| // .collect(); | ||
| // | ||
| // assert_eq!(xts.len(), opaque_calls.len()); | ||
| // assert_eq!(xts[0].signature.unwrap().2 .2, 34u128); | ||
| // } |
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 want to be hardcore, you could check the relevant bits in the encoded extrinsics. :D.
Anyhow, we can make the nonce public in the api-client. I don't see anything against that.
| fn send_extrinsics<OCallApi: EnclaveOnChainOCallApi>( | ||
| &mut self, | ||
| ocall_api: &OCallApi, | ||
| extrinsics: Vec<OpaqueExtrinsic>, | ||
| ) -> Result<(), Error>; |
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 this is a good design. 👍
| let xts = extrinsics_factory.create_extrinsics(calls.as_slice())?; | ||
|
|
||
| ocall_api | ||
| .send_to_parentchain(extrinsics) | ||
| .map_err(|e| Error::Other(format!("Failed to send extrinsics: {}", e).into()))?; | ||
| validator.send_extrinsics(on_chain_ocall_api, xts)?; | ||
|
|
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 very nice now. :)
|
|
||
| pub type NonceValue = u32; | ||
|
|
||
| /// Nonce type (newtype wrapper for u64) |
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 can't understand the purpose here. Can you please explain it to me? 😄
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.
you mean the purpose of a newtype for the Nonce? My intention was to have a strong type for the nonce. So in cases where we have other u32 values/parameters, it prevents mixing them by accident. Imagine a function that has 2 parameters:
fn do_work(nonce: u32, my_counter: u32);
When I call this function, it can happen that I mix up the two parameters, because their types are interchangeable. If I have a newtype for the nonce, that cannot happen.
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.
But I see now that the comment is not correct anymore, since we don't have a u64 😛 An example where comments can get out of sync with the code very easily.
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 see. I am much in favor of using strong typing here :). However, the comment was indeed misleading. 😄
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.
Ah so you're initial question was regarding the comment? Sorry I misunderstood then 😄 The comment is now fixed in the latest commit.
clangenb
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 to me! We are making some serious progress here. :)
author echevrier <[email protected]> 1633529369 +0200 committer echevrier <[email protected]> 1636648987 +0100 Exchange Rate Oracle Showcase Add exchange-oracle crate and implement get_exchange_rate for coinGecko Clean according review Call update market data (Exchange rate) from enclave Rename pallet market to teeracle cargo fmt Set MARKET_DATA_UPDATE_INTERVAL to 24h [GA] bump node artifact Changes from review Set substrate-fixed version to 0.5.6 (before update to new substrate) Changes from review Rebase Changes due to review Rebase to get PR #498 to rework code according review
…ko (#442) author echevrier <[email protected]> 1633529369 +0200 committer echevrier <[email protected]> 1636648987 +0100 Exchange Rate Oracle Showcase Add exchange-oracle crate and implement get_exchange_rate for coinGecko Clean according review Call update market data (Exchange rate) from enclave Rename pallet market to teeracle cargo fmt Set MARKET_DATA_UPDATE_INTERVAL to 24h [GA] bump node artifact Changes from review Set substrate-fixed version to 0.5.6 (before update to new substrate) Changes from review Rebase Changes due to review Rebase to get PR #498 to rework code according review * Rebase * cargo clippy and catch error in execute_update_market to prevent that the thread is stopped * settings for demo * update CI because this branch will not be merged in master, but in exchange_rate_oracle branch * changes from review * changes from review * Remove reference to light client for oracle * Set update interval for exchange range to 24h * Changes review * Changes review * Cargo clippy * Changes from review: improve variable names Co-authored-by: echevrier <[email protected]>
…ko (#442) author echevrier <[email protected]> 1633529369 +0200 committer echevrier <[email protected]> 1636648987 +0100 Exchange Rate Oracle Showcase Add exchange-oracle crate and implement get_exchange_rate for coinGecko Clean according review Call update market data (Exchange rate) from enclave Rename pallet market to teeracle cargo fmt Set MARKET_DATA_UPDATE_INTERVAL to 24h [GA] bump node artifact Changes from review Set substrate-fixed version to 0.5.6 (before update to new substrate) Changes from review Rebase Changes due to review Rebase to get PR #498 to rework code according review * Rebase * cargo clippy and catch error in execute_update_market to prevent that the thread is stopped * settings for demo * update CI because this branch will not be merged in master, but in exchange_rate_oracle branch * changes from review * changes from review * Remove reference to light client for oracle * Set update interval for exchange range to 24h * Changes review * Changes review * Cargo clippy * Changes from review: improve variable names Co-authored-by: echevrier <[email protected]>
Add exchange-oracle crate and implement get_exchange_rate for coinGecko Clean according review Call update market data (Exchange rate) from enclave Rename pallet market to teeracle cargo fmt Set MARKET_DATA_UPDATE_INTERVAL to 24h [GA] bump node artifact Changes from review Set substrate-fixed version to 0.5.6 (before update to new substrate) Changes from review Rebase Changes due to review Rebase to get PR #498 to rework code according review * Rebase * cargo clippy and catch error in execute_update_market to prevent that the thread is stopped * settings for demo * update CI because this branch will not be merged in master, but in exchange_rate_oracle branch * changes from review * changes from review * Remove reference to light client for oracle * Set update interval for exchange range to 24h * Changes review * Changes review * Cargo clippy * Changes from review: improve variable names Co-authored-by: echevrier <[email protected]> Cargo test fix
Add exchange-oracle crate and implement get_exchange_rate for coinGecko Clean according review Call update market data (Exchange rate) from enclave Rename pallet market to teeracle cargo fmt Set MARKET_DATA_UPDATE_INTERVAL to 24h [GA] bump node artifact Changes from review Set substrate-fixed version to 0.5.6 (before update to new substrate) Changes from review Rebase Changes due to review Rebase to get PR #498 to rework code according review * Rebase * cargo clippy and catch error in execute_update_market to prevent that the thread is stopped * settings for demo * update CI because this branch will not be merged in master, but in exchange_rate_oracle branch * changes from review * changes from review * Remove reference to light client for oracle * Set update interval for exchange range to 24h * Changes review * Changes review * Cargo clippy * Changes from review: improve variable names Co-authored-by: echevrier <[email protected]> Cargo test fix
Add exchange-oracle crate and implement get_exchange_rate for coinGecko Clean according review Call update market data (Exchange rate) from enclave Rename pallet market to teeracle cargo fmt Set MARKET_DATA_UPDATE_INTERVAL to 24h [GA] bump node artifact Changes from review Set substrate-fixed version to 0.5.6 (before update to new substrate) Changes from review Rebase Changes due to review Rebase to get PR #498 to rework code according review * Rebase * cargo clippy and catch error in execute_update_market to prevent that the thread is stopped * settings for demo * update CI because this branch will not be merged in master, but in exchange_rate_oracle branch * changes from review * changes from review * Remove reference to light client for oracle * Set update interval for exchange range to 24h * Changes review * Changes review * Cargo clippy * Changes from review: improve variable names Co-authored-by: echevrier <[email protected]> Cargo test fix
This is the next refactoring step for #301:
extrinsics-factorycratenonce-cachecrate to handle the enclave internal nonceThis is done because in the sidechain we need to compose and send extrinsics to the parentchain and so we need those parts of the code out of the
enclave-runtime.Closes #429