Skip to content

Commit fcbbeef

Browse files
fix: dryRun check + fix multiple solutions bug (#443)
* fix: `dryRun check` + fix multiple solutions bug * cli: add dry_run arg * fix grumbles; check if dry-run works on startup * Update src/monitor.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * cargo fmt Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
1 parent 35eba69 commit fcbbeef

File tree

3 files changed

+70
-7
lines changed

3 files changed

+70
-7
lines changed

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ mod tests {
227227
solver: Solver::SeqPhragmen { iterations: 10 },
228228
submission_strategy: SubmissionStrategy::IfLeading,
229229
seed_or_path: "//Alice".to_string(),
230+
dry_run: false,
230231
}),
231232
}
232233
);

src/monitor.rs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ use crate::{
2626
};
2727
use codec::{Decode, Encode};
2828
use frame_election_provider_support::NposSolution;
29+
use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError};
2930
use pallet_election_provider_multi_phase::{RawSolution, SolutionOf};
3031
use sp_runtime::Perbill;
3132
use std::sync::Arc;
32-
use subxt::{error::RpcError, rpc::Subscription, tx::TxStatus};
33+
use subxt::{error::RpcError, rpc::Subscription, tx::TxStatus, Error as SubxtError};
3334
use tokio::sync::Mutex;
3435

3536
pub async fn monitor_cmd<T>(api: SubxtClient, config: MonitorConfig) -> Result<(), Error>
@@ -49,6 +50,11 @@ where
4950

5051
log::info!(target: LOG_TARGET, "Loaded account {}, {:?}", signer, account_info);
5152

53+
if config.dry_run {
54+
// if we want to try-run, ensure the node supports it.
55+
dry_run_works(&api).await?;
56+
}
57+
5258
let mut subscription = heads_subscription(&api, config.listen).await?;
5359
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<Error>();
5460
let submit_lock = Arc::new(Mutex::new(()));
@@ -101,9 +107,6 @@ where
101107
}
102108

103109
fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender<Error>, err: Error) {
104-
use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError};
105-
use subxt::Error as SubxtError;
106-
107110
match err {
108111
Error::AlreadySubmitted |
109112
Error::BetterScoreExist |
@@ -130,11 +133,15 @@ fn kill_main_task_if_critical_err(tx: &tokio::sync::mpsc::UnboundedSender<Error>
130133
JsonRpseeError::Call(CallError::Custom(e)) => {
131134
const BAD_EXTRINSIC_FORMAT: i32 = 1001;
132135
const VERIFICATION_ERROR: i32 = 1002;
136+
use jsonrpsee::types::error::ErrorCode;
133137

134138
// Check if the transaction gets fatal errors from the `author` RPC.
135139
// It's possible to get other errors such as outdated nonce and similar
136140
// but then it should be possible to try again in the next block or round.
137-
if e.code() == BAD_EXTRINSIC_FORMAT || e.code() == VERIFICATION_ERROR {
141+
if e.code() == BAD_EXTRINSIC_FORMAT ||
142+
e.code() == VERIFICATION_ERROR || e.code() ==
143+
ErrorCode::MethodNotFound.code()
144+
{
138145
let _ = tx.send(Error::Subxt(SubxtError::Rpc(
139146
RpcError::ClientError(Box::new(CallError::Custom(e))),
140147
)));
@@ -215,8 +222,6 @@ async fn mine_and_submit_solution<T>(
215222
},
216223
};
217224

218-
let _lock = submit_lock.lock().await;
219-
220225
if let Err(e) =
221226
ensure_no_previous_solution::<T::Solution>(&api, hash, signer.account_id()).await
222227
{
@@ -231,6 +236,8 @@ async fn mine_and_submit_solution<T>(
231236
return
232237
}
233238

239+
let _lock = submit_lock.lock().await;
240+
234241
let (solution, score) =
235242
match epm::fetch_snapshot_and_mine_solution::<T>(&api, Some(hash), config.solver)
236243
.timed()
@@ -292,6 +299,20 @@ async fn mine_and_submit_solution<T>(
292299
return
293300
}
294301

302+
if let Err(e) =
303+
ensure_no_previous_solution::<T::Solution>(&api, best_head, signer.account_id()).await
304+
{
305+
log::debug!(
306+
target: LOG_TARGET,
307+
"ensure_no_previous_solution failed: {:?}; skipping block: {:?}",
308+
e,
309+
best_head,
310+
);
311+
312+
kill_main_task_if_critical_err(&tx, e);
313+
return
314+
}
315+
295316
match ensure_solution_passes_strategy(&api, best_head, score, config.submission_strategy)
296317
.timed()
297318
.await
@@ -323,6 +344,7 @@ async fn mine_and_submit_solution<T>(
323344
hash,
324345
nonce,
325346
config.listen,
347+
config.dry_run,
326348
)
327349
.timed()
328350
.await
@@ -419,13 +441,22 @@ async fn submit_and_watch_solution<T: MinerConfig + Send + Sync + 'static>(
419441
hash: Hash,
420442
nonce: u32,
421443
listen: Listen,
444+
dry_run: bool,
422445
) -> Result<(), Error> {
423446
let tx = epm::signed_solution(RawSolution { solution, score, round })?;
424447

425448
let xt = api
426449
.tx()
427450
.create_signed_with_nonce(&tx, &*signer, nonce, ExtrinsicParams::default())?;
428451

452+
if dry_run {
453+
match api.rpc().dry_run(xt.encoded(), None).await? {
454+
Ok(Ok(())) => (),
455+
Ok(Err(e)) => return Err(Error::TransactionRejected(format!("{:?}", e))),
456+
Err(e) => return Err(Error::TransactionRejected(e.to_string())),
457+
};
458+
}
459+
429460
let mut status_sub = xt.submit_and_watch().await.map_err(|e| {
430461
log::warn!(target: LOG_TARGET, "submit solution failed: {:?}", e);
431462
e
@@ -520,6 +551,29 @@ pub(crate) fn score_passes_strategy(
520551
}
521552
}
522553

554+
async fn dry_run_works(api: &SubxtClient) -> Result<(), Error> {
555+
if let Err(SubxtError::Rpc(RpcError::ClientError(e))) = api.rpc().dry_run(&[], None).await {
556+
let rpc_err = match e.downcast::<JsonRpseeError>() {
557+
Ok(e) => *e,
558+
Err(_) =>
559+
return Err(Error::Other(
560+
"Failed to downcast RPC error; this is a bug please file an issue".to_string(),
561+
)),
562+
};
563+
564+
if let JsonRpseeError::Call(CallError::Custom(e)) = rpc_err {
565+
if e.message() == "RPC call is unsafe to be called externally" {
566+
return Err(Error::Other(
567+
"dry-run requires a RPC endpoint with `--rpc-methods unsafe`; \
568+
either connect to another RPC endpoint or disable dry-run"
569+
.to_string(),
570+
))
571+
}
572+
}
573+
}
574+
Ok(())
575+
}
576+
523577
#[cfg(test)]
524578
mod tests {
525579
use super::*;

src/opt.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@ pub struct MonitorConfig {
223223
/// configured, it might re-try and lose funds through transaction fees/deposits.
224224
#[clap(long, short, env = "SEED")]
225225
pub seed_or_path: String,
226+
227+
/// Verify the submission by `dry-run` the extrinsic to check the validity.
228+
/// If the extrinsic is invalid then the submission is ignored and the next block will attempted again.
229+
///
230+
/// This requires a RPC endpoint that exposes unsafe RPC methods, if the RPC endpoint doesn't expose unsafe RPC methods
231+
/// then the miner will be terminated.
232+
#[clap(long)]
233+
pub dry_run: bool,
226234
}
227235

228236
#[derive(Debug, Clone, Parser)]

0 commit comments

Comments
 (0)