-
Notifications
You must be signed in to change notification settings - Fork 52
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
feat: Cacheing the nonce in case of transact_async #367
Conversation
@jaswinder6991 Thank you for your contribution! Your pull request is now a part of the Race of Sloths! Current status: staleThis pull request was removed from the race, but you can include it again with What is the Race of SlothsRace of Sloths is a friendly competition where you can participate in challenges and compete with other open-source contributors within your normal workflow For contributors:
For maintainers:
Feel free to check our website for additional details! Bot commands
|
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 is already implementation for that inside fetch_tx_nonce
:
near-workspaces-rs/workspaces/src/rpc/client.rs
Lines 494 to 503 in 445e86e
let nonces = client.access_key_nonces.read().await; | |
if let Some(nonce) = nonces.get(cache_key) { | |
let nonce = nonce.fetch_add(1, Ordering::SeqCst); | |
drop(nonces); | |
// Fetch latest block_hash since the previous one is now invalid for new transactions: | |
let block = client.view_block(Some(Finality::Final.into())).await?; | |
let block_hash = block.header.hash; | |
Ok((block_hash, nonce + 1)) | |
} else { |
I believe the problem is that it does not work for the first transactions since if there is no nonce cached, it will end up in the else branch too early and thus several concurrent jobs will end up querying the latest nonce and use that.
Nice. My B, I should have checked it. One reason, I did not want to touch fetch_nonce was because I thought it might break other functions. |
@frol so I tried to simplify my solution by checking if another thread has already update the cache (L506): near-workspaces-rs/workspaces/src/rpc/client.rs Lines 497 to 519 in b28148b
It worked fine for me locally, seems like it failed in CI tests. However it has achieved some level of concurrency. Do you recommend I dig deeper or this should be fine? |
Thinking about this problem more, I believe that the issue is not due to the nonce, the implementation in master doesn’t have a race near-workspaces-rs/workspaces/src/rpc/client.rs Lines 510 to 519 in 445e86e
This is the right way to handle it. There are two other problems:
|
Hi @frol thanks for your helpful insight. I understand it now too. Seems like the nonce never had the issue. I went on the wrong track and ofcourse the test is incorrect. use anyhow::Result;
use serde_json::json;
use std::time::Duration;
const STATUS_MSG_CONTRACT: &[u8] = include_bytes!("../../examples/res/status_message.wasm");
#[tokio::test]
async fn test_nonce_caching_sequential_async_no_wait() -> Result<()> {
let worker = near_workspaces::sandbox().await?;
let contract = worker.dev_deploy(STATUS_MSG_CONTRACT).await?;
let account = worker.dev_create_account().await?;
// Get the initial nonce
let initial_nonce = worker
.view_access_key(account.id(), &account.secret_key().public_key())
.await?
.nonce;
const NUM_TRANSACTIONS: usize = 10;
let mut transaction_statuses = Vec::with_capacity(NUM_TRANSACTIONS);
// Send transactions sequentially without waiting for completion
for i in 0..NUM_TRANSACTIONS {
let msg = format!("msg{}", i);
let result = account
.call(&contract.id(), "set_status")
.args_json(json!({ "message": msg }))
.transact_async()
.await?;
transaction_statuses.push(result);
// Small delay to simulate some processing time between transactions
//tokio::time::sleep(Duration::from_millis(10)).await;
}
// Now wait for all transactions to complete
for (i, status) in transaction_statuses.into_iter().enumerate() {
loop {
match status.status().await? {
std::task::Poll::Ready(outcome) => {
if outcome.is_success() {
println!("Transaction {} completed successfully", i);
break;
} else {
return Err(anyhow::anyhow!("Transaction {} failed: {:?}", i, outcome));
}
}
std::task::Poll::Pending => {
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
}
}
// Get the final nonce
let final_nonce = worker
.view_access_key(account.id(), &account.secret_key().public_key())
.await?
.nonce;
// Verify that the nonce increased by exactly the number of transactions
assert_eq!(
final_nonce - initial_nonce,
NUM_TRANSACTIONS as u64,
"Nonce did not increment correctly"
);
Ok(())
} Going back to the original query on Telegram:
I think it is more of an account creation issue in async context. I will close this PR now. |
Note: The PR might be a little rough around the edges in terms of best practices and thread safety. Hence, your feedback is welcome.