Skip to content

Commit cbc4ecc

Browse files
authored
Merge pull request #597 from openmina/feat/tx-pool-error-handling
Improve tx-pool error handling
2 parents 28c31d9 + 9f651e7 commit cbc4ecc

File tree

14 files changed

+208
-117
lines changed

14 files changed

+208
-117
lines changed

ledger/src/scan_state/transaction_logic.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4181,6 +4181,12 @@ pub mod zkapp_command {
41814181
cache
41824182
.find(account_id, &vk_hash)
41834183
.cloned()
4184+
.or_else(|| {
4185+
cmd.extract_vks()
4186+
.iter()
4187+
.find(|(id, _)| account_id == id)
4188+
.map(|(_, key)| key.clone())
4189+
})
41844190
.ok_or_else(|| "verification key not found in cache".to_string())
41854191
})?;
41864192
if !is_failed {
@@ -4733,12 +4739,17 @@ impl UserCommand {
47334739
}
47344740
}
47354741

4736-
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
4742+
#[derive(Debug, Clone, Hash, PartialEq, Eq, thiserror::Error)]
47374743
pub enum WellFormednessError {
4744+
#[error("Insufficient Fee")]
47384745
InsufficientFee,
4746+
#[error("Zero vesting period")]
47394747
ZeroVestingPeriod,
4748+
#[error("Zkapp too big: {0}")]
47404749
ZkappTooBig(String),
4750+
#[error("Transaction type disabled")]
47414751
TransactionTypeDisabled,
4752+
#[error("Incompatible version")]
47424753
IncompatibleVersion,
47434754
}
47444755

ledger/src/staged_ledger/staged_ledger.rs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,30 +1222,6 @@ impl StagedLedger {
12221222
verifier
12231223
.verify_commands(cs, skip_verification)
12241224
.into_iter()
1225-
.map(|x| {
1226-
use crate::verifier::VerifyCommandsResult::*;
1227-
match x {
1228-
Valid(x) => Ok(x),
1229-
InvalidKeys(invalid)
1230-
| InvalidSignature(invalid)
1231-
| MissingVerificationKey(invalid)
1232-
| UnexpectedVerificationKey(invalid)
1233-
| MismatchedVerificationKey(invalid)
1234-
| MismatchedAuthorizationKind(invalid) => {
1235-
Err(VerifierError::VerificationFailed(format!(
1236-
"verification failed on command: {:?}",
1237-
invalid
1238-
)))
1239-
}
1240-
InvalidProof(invalid) => Err(VerifierError::VerificationFailed(format!(
1241-
"verification failed on command: {:?}",
1242-
invalid
1243-
))),
1244-
ValidAssuming(_) => Err(VerifierError::VerificationFailed(
1245-
"batch verification failed".to_string(),
1246-
)),
1247-
}
1248-
})
12491225
.collect()
12501226
}
12511227

ledger/src/transaction_pool.rs

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,37 @@ use crate::{
2121
MaybeWithStatus, WithHash,
2222
},
2323
TransactionStatus::Applied,
24-
UserCommand, WithStatus,
24+
UserCommand, WellFormednessError, WithStatus,
2525
},
2626
},
27-
verifier::Verifier,
27+
verifier::{Verifier, VerifierError},
2828
Account, AccountId, BaseLedger, Mask, TokenId, VerificationKey,
2929
};
3030

31+
#[derive(Debug, thiserror::Error)]
32+
pub enum TransactionPoolErrors {
33+
/// Invalid transactions, rejeceted diffs, etc...
34+
#[error("Transaction pool errors: {0:?}")]
35+
BatchedErrors(Vec<TransactionError>),
36+
/// Errors that should panic the node (bugs in implementation)
37+
#[error("Unexpected error: {0}")]
38+
Unexpected(String),
39+
}
40+
41+
#[derive(Debug, thiserror::Error)]
42+
pub enum TransactionError {
43+
#[error(transparent)]
44+
Verifier(#[from] VerifierError),
45+
#[error(transparent)]
46+
WellFormedness(#[from] WellFormednessError),
47+
}
48+
49+
impl From<String> for TransactionPoolErrors {
50+
fn from(value: String) -> Self {
51+
Self::Unexpected(value)
52+
}
53+
}
54+
3155
mod consensus {
3256
use crate::scan_state::currency::{BlockTimeSpan, Epoch, Length};
3357

@@ -475,6 +499,7 @@ enum Batch {
475499
Of(usize),
476500
}
477501

502+
#[derive(Debug)]
478503
pub enum CommandError {
479504
InvalidNonce {
480505
account_nonce: Nonce,
@@ -729,6 +754,7 @@ impl IndexedPool {
729754
if unchecked.expected_target_nonce()
730755
!= first_queued.data.forget_check().applicable_at_nonce()
731756
{
757+
// Ocaml panics here as well
732758
panic!("indexed pool nonces inconsistent when adding from backtrack.")
733759
}
734760

@@ -833,8 +859,8 @@ impl IndexedPool {
833859
})
834860
.unwrap();
835861

836-
let keep_queue = sender_queue.split_off(cmd_index);
837-
let drop_queue = sender_queue.clone();
862+
let drop_queue = sender_queue.split_off(cmd_index);
863+
let keep_queue = sender_queue;
838864
assert!(!drop_queue.is_empty());
839865

840866
let currency_to_remove = drop_queue.iter().fold(Amount::zero(), |acc, cmd| {
@@ -963,7 +989,9 @@ impl IndexedPool {
963989
self.check_expiry(global_slot_since_genesis, &unchecked)?;
964990
let consumed = currency_consumed(&unchecked).ok_or(CommandError::Overflow)?;
965991
if !unchecked.fee_token().is_default() {
966-
return Err(CommandError::BadToken);
992+
return Err(CommandError::UnwantedFeeToken {
993+
token_id: unchecked.fee_token(),
994+
});
967995
}
968996
consumed
969997
};
@@ -1042,8 +1070,7 @@ impl IndexedPool {
10421070
})
10431071
.unwrap();
10441072

1045-
let _ = queued_cmds.split_off(replacement_index);
1046-
let drop_queue = queued_cmds.clone();
1073+
let drop_queue = queued_cmds.split_off(replacement_index);
10471074

10481075
let to_drop = drop_queue.front().unwrap().data.forget_check();
10491076
assert!(cmd_applicable_at_nonce <= to_drop.applicable_at_nonce());
@@ -2072,7 +2099,7 @@ impl TransactionPool {
20722099
&self,
20732100
diff: diff::Diff,
20742101
accounts: &BTreeMap<AccountId, Account>,
2075-
) -> Result<Vec<valid::UserCommand>, String> {
2102+
) -> Result<Vec<valid::UserCommand>, TransactionPoolErrors> {
20762103
let well_formedness_errors: HashSet<_> = diff
20772104
.list
20782105
.iter()
@@ -2083,9 +2110,11 @@ impl TransactionPool {
20832110
.collect();
20842111

20852112
if !well_formedness_errors.is_empty() {
2086-
return Err(format!(
2087-
"Some commands have one or more well-formedness errors: {:?}",
2113+
return Err(TransactionPoolErrors::BatchedErrors(
20882114
well_formedness_errors
2115+
.into_iter()
2116+
.map(TransactionError::WellFormedness)
2117+
.collect_vec(),
20892118
));
20902119
}
20912120

@@ -2132,7 +2161,7 @@ impl TransactionPool {
21322161

21332162
from_unapplied_sequence::Cache::new(merged)
21342163
})
2135-
.map_err(|e| format!("Invalid {:?}", e))?;
2164+
.map_err(TransactionPoolErrors::Unexpected)?;
21362165

21372166
let diff = diff
21382167
.into_iter()
@@ -2142,17 +2171,25 @@ impl TransactionPool {
21422171
})
21432172
.collect::<Vec<_>>();
21442173

2145-
Verifier
2174+
let (verified, invalid): (Vec<_>, Vec<_>) = Verifier
21462175
.verify_commands(diff, None)
21472176
.into_iter()
2148-
.map(|cmd| {
2149-
// TODO: Handle invalids
2150-
match cmd {
2151-
crate::verifier::VerifyCommandsResult::Valid(cmd) => Ok(cmd),
2152-
e => Err(format!("invalid tx: {:?}", e)),
2153-
}
2154-
})
2155-
.collect()
2177+
.partition(Result::is_ok);
2178+
2179+
let verified: Vec<_> = verified.into_iter().map(Result::unwrap).collect();
2180+
let invalid: Vec<_> = invalid.into_iter().map(Result::unwrap_err).collect();
2181+
2182+
if !invalid.is_empty() {
2183+
let transaction_pool_errors = invalid
2184+
.into_iter()
2185+
.map(TransactionError::Verifier)
2186+
.collect();
2187+
Err(TransactionPoolErrors::BatchedErrors(
2188+
transaction_pool_errors,
2189+
))
2190+
} else {
2191+
Ok(verified)
2192+
}
21562193
}
21572194

21582195
fn get_rebroadcastable<F>(&mut self, has_timed_out: F) -> Vec<Vec<UserCommand>>

ledger/src/verifier/mod.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,32 @@ fn verify_digest_only(ts: Vec<(LedgerProof, SokMessage)>) -> Result<(), String>
103103
}
104104

105105
/// https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/verifier/verifier_intf.ml#L10C1-L36C29
106-
#[derive(Debug)]
107-
pub enum VerifyCommandsResult {
108-
Valid(valid::UserCommand),
106+
pub type VerifyCommandsResult = Result<valid::UserCommand, VerifierError>;
107+
108+
#[derive(Debug, thiserror::Error)]
109+
pub enum VerifierError {
110+
// TODO(adonagy): print something here as well?
111+
#[error("Batch verification failed")]
109112
ValidAssuming(
110113
Vec<(
111114
VerificationKey,
112115
ZkappStatement,
113116
Arc<PicklesProofProofsVerifiedMaxStableV2>,
114117
)>,
115118
),
119+
#[error("Invalid keys: {0:?}")]
116120
InvalidKeys(Vec<CompressedPubKey>),
121+
#[error("Invalid signature: {0:?}")]
117122
InvalidSignature(Vec<CompressedPubKey>),
123+
#[error("Invalid proof: {0}")]
118124
InvalidProof(String),
125+
#[error("Missing verification key: {0:?}")]
119126
MissingVerificationKey(Vec<CompressedPubKey>),
127+
#[error("Unexpected verification key: {0:?}")]
120128
UnexpectedVerificationKey(Vec<CompressedPubKey>),
129+
#[error("Mismatched verification key: {0:?}")]
121130
MismatchedVerificationKey(Vec<CompressedPubKey>),
131+
#[error("Authorization kind does not match the authorization - Keys {0:?}")]
122132
MismatchedAuthorizationKind(Vec<CompressedPubKey>),
123133
}
124134

@@ -176,36 +186,36 @@ impl Verifier {
176186

177187
cs.into_iter()
178188
.map(|c| match c {
179-
CheckResult::Valid(c) => VerifyCommandsResult::Valid(c),
189+
CheckResult::Valid(c) => Ok(c),
180190
CheckResult::ValidAssuming((c, xs)) => {
181191
if all_verified {
182-
VerifyCommandsResult::Valid(c)
192+
Ok(c)
183193
} else {
184-
VerifyCommandsResult::ValidAssuming(xs)
194+
Err(VerifierError::ValidAssuming(xs))
185195
}
186196
}
187-
CheckResult::InvalidKeys(keys) => VerifyCommandsResult::InvalidKeys(keys),
188-
CheckResult::InvalidSignature(keys) => VerifyCommandsResult::InvalidSignature(keys),
189-
CheckResult::InvalidProof(s) => VerifyCommandsResult::InvalidProof(s),
197+
CheckResult::InvalidKeys(keys) => Err(VerifierError::InvalidKeys(keys)),
198+
CheckResult::InvalidSignature(keys) => Err(VerifierError::InvalidSignature(keys)),
199+
CheckResult::InvalidProof(s) => Err(VerifierError::InvalidProof(s)),
190200
CheckResult::MissingVerificationKey(keys) => {
191-
VerifyCommandsResult::MissingVerificationKey(keys)
201+
Err(VerifierError::MissingVerificationKey(keys))
192202
}
193203
CheckResult::UnexpectedVerificationKey(keys) => {
194-
VerifyCommandsResult::UnexpectedVerificationKey(keys)
204+
Err(VerifierError::UnexpectedVerificationKey(keys))
195205
}
196206
CheckResult::MismatchedAuthorizationKind(keys) => {
197-
VerifyCommandsResult::MismatchedAuthorizationKind(keys)
207+
Err(VerifierError::MismatchedAuthorizationKind(keys))
198208
}
199209
})
200210
.collect()
201211
}
202212
}
203213

204-
#[derive(Debug, derive_more::From)]
205-
pub enum VerifierError {
206-
CheckError(CheckResult),
207-
VerificationFailed(String),
208-
}
214+
// #[derive(Debug, derive_more::From)]
215+
// pub enum VerifierError {
216+
// CheckError(CheckResult),
217+
// VerificationFailed(String),
218+
// }
209219

210220
pub mod common {
211221
use std::sync::Arc;

node/common/src/service/rpc/mod.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use node::rpc::{
77
RpcBlockProducerStatsGetResponse, RpcDiscoveryBoostrapStatsResponse,
88
RpcDiscoveryRoutingTableResponse, RpcHealthCheckResponse, RpcLedgerAccountsResponse,
99
RpcMessageProgressResponse, RpcPeersGetResponse, RpcReadinessCheckResponse, RpcRequest,
10-
RpcStateGetError, RpcStatusGetResponse, RpcTransactionInjectFailure,
10+
RpcStateGetError, RpcStatusGetResponse, RpcTransactionInjectResponse,
1111
RpcTransactionPoolResponse, RpcTransitionFrontierUserCommandsResponse,
1212
};
1313
use serde::{Deserialize, Serialize};
@@ -19,7 +19,7 @@ pub use node::rpc::{
1919
ActionStatsResponse, RespondError, RpcActionStatsGetResponse, RpcId, RpcIdType,
2020
RpcP2pConnectionOutgoingResponse, RpcScanStateSummaryGetResponse, RpcSnarkPoolGetResponse,
2121
RpcSnarkerJobCommitResponse, RpcSnarkerJobSpecResponse, RpcStateGetResponse,
22-
RpcSyncStatsGetResponse, RpcTransactionInjectResponse,
22+
RpcSyncStatsGetResponse, RpcTransactionInjectSuccess,
2323
};
2424
use node::State;
2525
use node::{event_source::Event, rpc::RpcSnarkPoolJobGetResponse};
@@ -96,9 +96,9 @@ macro_rules! rpc_service_impl {
9696
let chan = entry.ok_or(RespondError::UnknownRpcId)?;
9797
let chan = chan
9898
.downcast::<oneshot::Sender<$ty>>()
99-
.or(Err(RespondError::UnexpectedResponseType))?;
99+
.map_err(|_| RespondError::UnexpectedResponseType)?;
100100
chan.send(response)
101-
.or(Err(RespondError::RespondingFailed))?;
101+
.map_err(|_| RespondError::RespondingFailed)?;
102102
Ok(())
103103
}
104104
};
@@ -287,10 +287,6 @@ impl node::rpc::RpcService for NodeService {
287287
rpc_service_impl!(respond_transaction_pool, RpcTransactionPoolResponse);
288288
rpc_service_impl!(respond_ledger_accounts, RpcLedgerAccountsResponse);
289289
rpc_service_impl!(respond_transaction_inject, RpcTransactionInjectResponse);
290-
rpc_service_impl!(
291-
respond_transaction_inject_failed,
292-
RpcTransactionInjectFailure
293-
);
294290
rpc_service_impl!(
295291
respond_transition_frontier_commands,
296292
RpcTransitionFrontierUserCommandsResponse

node/src/action_kind.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ pub enum ActionKind {
395395
RpcTransactionInjectFailure,
396396
RpcTransactionInjectInit,
397397
RpcTransactionInjectPending,
398+
RpcTransactionInjectRejected,
398399
RpcTransactionInjectSuccess,
399400
RpcTransactionPool,
400401
RpcTransitionFrontierUserCommandsGet,
@@ -447,6 +448,7 @@ pub enum ActionKind {
447448
TransactionPoolRebroadcast,
448449
TransactionPoolStartVerify,
449450
TransactionPoolStartVerifyWithAccounts,
451+
TransactionPoolVerifyError,
450452
TransactionPoolEffectfulFetchAccounts,
451453
TransitionFrontierGenesisInject,
452454
TransitionFrontierSynced,
@@ -536,7 +538,7 @@ pub enum ActionKind {
536538
}
537539

538540
impl ActionKind {
539-
pub const COUNT: u16 = 444;
541+
pub const COUNT: u16 = 446;
540542
}
541543

542544
impl std::fmt::Display for ActionKind {
@@ -685,6 +687,7 @@ impl ActionKindGet for TransactionPoolAction {
685687
Self::StartVerifyWithAccounts { .. } => {
686688
ActionKind::TransactionPoolStartVerifyWithAccounts
687689
}
690+
Self::VerifyError { .. } => ActionKind::TransactionPoolVerifyError,
688691
Self::BestTipChanged { .. } => ActionKind::TransactionPoolBestTipChanged,
689692
Self::BestTipChangedWithAccounts { .. } => {
690693
ActionKind::TransactionPoolBestTipChangedWithAccounts
@@ -817,6 +820,7 @@ impl ActionKindGet for RpcAction {
817820
Self::TransactionInjectInit { .. } => ActionKind::RpcTransactionInjectInit,
818821
Self::TransactionInjectPending { .. } => ActionKind::RpcTransactionInjectPending,
819822
Self::TransactionInjectSuccess { .. } => ActionKind::RpcTransactionInjectSuccess,
823+
Self::TransactionInjectRejected { .. } => ActionKind::RpcTransactionInjectRejected,
820824
Self::TransactionInjectFailure { .. } => ActionKind::RpcTransactionInjectFailure,
821825
Self::TransitionFrontierUserCommandsGet { .. } => {
822826
ActionKind::RpcTransitionFrontierUserCommandsGet

0 commit comments

Comments
 (0)