1use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
22use sp_runtime::transaction_validity::InvalidTransaction;
23
24#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
25#[serde(rename_all = "camelCase")]
26struct ModuleInvalidityDetails {
27 pallet_index: u8,
28 error: u16,
29}
30
31pub type Result<T> = std::result::Result<T, Error>;
33
34#[derive(Debug, thiserror::Error)]
36pub enum Error {
37 #[error("Client error: {}", .0)]
39 Client(Box<dyn std::error::Error + Send + Sync>),
40 #[error("Transaction pool error: {}", .0)]
42 Pool(#[from] sc_transaction_pool_api::error::Error),
43 #[error("Extrinsic verification error: {}", .0)]
45 Verification(Box<dyn std::error::Error + Send + Sync>),
46 #[error("Invalid extrinsic format: {}", .0)]
48 BadFormat(#[from] codec::Error),
49 #[error("Invalid key type ID format (should be of length four)")]
51 BadKeyType,
52 #[error("The key store is unavailable")]
54 KeystoreUnavailable,
55 #[error("Session keys are not encoded correctly")]
57 InvalidSessionKeys,
58 #[error("`SessionKeys` runtime api not present in the runtime")]
60 MissingSessionKeysApi,
61 #[error(transparent)]
63 UnsafeRpcCalled(#[from] crate::policy::UnsafeRpcError),
64}
65
66const BASE_ERROR: i32 = crate::error::base::AUTHOR;
68const BAD_FORMAT: i32 = BASE_ERROR + 1;
70const VERIFICATION_ERROR: i32 = BASE_ERROR + 2;
72
73const POOL_INVALID_TX: i32 = BASE_ERROR + 10;
75const POOL_UNKNOWN_VALIDITY: i32 = POOL_INVALID_TX + 1;
77const POOL_TEMPORARILY_BANNED: i32 = POOL_INVALID_TX + 2;
79const POOL_ALREADY_IMPORTED: i32 = POOL_INVALID_TX + 3;
81const POOL_TOO_LOW_PRIORITY: i32 = POOL_INVALID_TX + 4;
83const POOL_CYCLE_DETECTED: i32 = POOL_INVALID_TX + 5;
85const POOL_IMMEDIATELY_DROPPED: i32 = POOL_INVALID_TX + 6;
87const POOL_UNACTIONABLE: i32 = POOL_INVALID_TX + 8;
90const POOL_NO_TAGS: i32 = POOL_INVALID_TX + 9;
92const POOL_INVALID_BLOCK_ID: i32 = POOL_INVALID_TX + 10;
94const POOL_FUTURE_TX: i32 = POOL_INVALID_TX + 11;
96const OTHER_ERR: i32 = BASE_ERROR + 40;
98
99impl From<Error> for ErrorObjectOwned {
100 fn from(e: Error) -> ErrorObjectOwned {
101 use sc_transaction_pool_api::error::Error as PoolError;
102
103 match e {
104 Error::BadFormat(e) => ErrorObject::owned(
105 BAD_FORMAT,
106 format!("Extrinsic has invalid format: {}", e),
107 None::<()>,
108 ),
109 Error::Verification(e) => ErrorObject::owned(
110 VERIFICATION_ERROR,
111 format!("Verification Error: {}", e),
112 Some(format!("{:?}", e)),
113 ),
114 Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => {
115 ErrorObject::owned(
116 POOL_INVALID_TX,
117 "Invalid Transaction",
118 Some(format!("Custom error: {}", e)),
119 )
120 },
121 Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Module(e))) => {
122 ErrorObject::owned(
123 POOL_INVALID_TX,
124 "Invalid Transaction",
125 Some(ModuleInvalidityDetails { pallet_index: e.index, error: e.error }),
126 )
127 },
128 Error::Pool(PoolError::InvalidTransaction(e)) => {
129 let msg: &str = e.into();
130 ErrorObject::owned(
131 POOL_INVALID_TX,
132 "Invalid Transaction",
133 Some(msg),
134 )
135 },
136 Error::Pool(PoolError::UnknownTransaction(e)) => {
137 ErrorObject::owned(
138 POOL_UNKNOWN_VALIDITY,
139 "Unknown Transaction Validity",
140 Some(format!("{:?}", e)),
141 )
142 },
143 Error::Pool(PoolError::TemporarilyBanned) =>
144 ErrorObject::owned(
145 POOL_TEMPORARILY_BANNED,
146 "Transaction is temporarily banned",
147 None::<()>,
148 ),
149 Error::Pool(PoolError::AlreadyImported(hash)) =>
150 ErrorObject::owned(
151 POOL_ALREADY_IMPORTED,
152 "Transaction Already Imported",
153 Some(format!("{:?}", hash)),
154 ),
155 Error::Pool(PoolError::TooLowPriority { old, new }) => ErrorObject::owned(
156 POOL_TOO_LOW_PRIORITY,
157 format!("Priority is too low: ({} vs {})", old, new),
158 Some("The transaction has too low priority to replace another transaction already in the pool.")
159 ),
160 Error::Pool(PoolError::CycleDetected) =>
161 ErrorObject::owned(
162 POOL_CYCLE_DETECTED,
163 "Cycle Detected",
164 None::<()>
165 ),
166 Error::Pool(PoolError::ImmediatelyDropped) => ErrorObject::owned(
167 POOL_IMMEDIATELY_DROPPED,
168 "Immediately Dropped",
169 Some("The transaction couldn't enter the pool because of the limit"),
170 ),
171 Error::Pool(PoolError::Unactionable) => ErrorObject::owned(
172 POOL_UNACTIONABLE,
173 "Unactionable",
174 Some("The transaction is unactionable since it is not propagable and \
175 the local node does not author blocks")
176 ),
177 Error::Pool(PoolError::NoTagsProvided) => ErrorObject::owned(
178 POOL_NO_TAGS,
179 "No tags provided",
180 Some("Transaction does not provide any tags, so the pool can't identify it")
181 ),
182 Error::Pool(PoolError::InvalidBlockId(_)) =>
183 ErrorObject::owned(
184 POOL_INVALID_BLOCK_ID,
185 "The provided block ID is not valid",
186 None::<()>
187 ),
188 Error::Pool(PoolError::RejectedFutureTransaction) => {
189 ErrorObject::owned(
190 POOL_FUTURE_TX,
191 "The pool is not accepting future transactions",
192 None::<()>,
193 )
194 },
195 Error::UnsafeRpcCalled(e) => e.into(),
196 other => ErrorObject::owned(
197 OTHER_ERR,
198 other.to_string(),
199 None::<()>,
200 )
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use jsonrpsee::types::ErrorObjectOwned;
209 use sc_transaction_pool_api::error::Error as PoolError;
210 use sp_runtime::ModuleInvalidity;
211
212 #[test]
214 fn author_submit_extrinsic_module_invalidity_rpc_data() {
215 let on_node = ModuleInvalidity { index: 42, error: 1 };
216 let err = Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Module(on_node)));
217 let rpc_error: ErrorObjectOwned = err.into();
218
219 let raw_data = rpc_error.data().unwrap().get();
220 assert_eq!(raw_data, r#"{"palletIndex":42,"error":1}"#);
224
225 let details: ModuleInvalidityDetails = serde_json::from_str(raw_data).unwrap();
226 assert_eq!(details.pallet_index, 42);
227 assert_eq!(details.error, 1);
228 }
229}