1use crate::{
25 pool::HopDataPool,
26 runtime_api,
27 types::{
28 submit_signing_payload, HopError, HopHash, PoolStatus, Recipient, RecipientVec,
29 SubmitResult, MAX_RECIPIENTS,
30 },
31};
32use codec::Decode;
33use jsonrpsee::{
34 core::{async_trait, RpcResult},
35 proc_macros::rpc,
36};
37use sp_api::CallApiAt;
38use sp_blockchain::HeaderBackend;
39use sp_core::{Bytes, H256};
40use sp_crypto_hashing::blake2_256;
41use sp_runtime::{
42 traits::{Block as BlockT, IdentifyAccount, Verify},
43 AccountId32, MultiSignature, MultiSigner,
44};
45use std::{marker::PhantomData, sync::Arc};
46
47#[rpc(client, server)]
49pub trait HopApi<BlockHash> {
50 #[method(name = "hop_submit", blocking)]
68 fn submit(
69 &self,
70 data: Bytes,
71 recipients: Vec<Bytes>,
72 signature: Bytes,
73 signer: Bytes,
74 submit_timestamp: u64,
75 ) -> RpcResult<SubmitResult>;
76
77 #[method(name = "hop_claim", blocking)]
96 fn claim(&self, raw_hash: Bytes, signature: Bytes) -> RpcResult<Bytes>;
97
98 #[method(name = "hop_ack", blocking)]
110 fn ack(&self, raw_hash: Bytes, signature: Bytes) -> RpcResult<()>;
111
112 #[method(name = "hop_poolStatus")]
117 fn pool_status(&self) -> RpcResult<PoolStatus>;
118}
119
120pub struct HopRpcServer<C, Block> {
122 pool: Arc<HopDataPool>,
123 client: Arc<C>,
124 _phantom: PhantomData<Block>,
125}
126
127impl<C, Block> HopRpcServer<C, Block> {
128 pub fn new(pool: Arc<HopDataPool>, client: Arc<C>) -> Self {
130 Self { pool, client, _phantom: Default::default() }
131 }
132
133 fn decode_hash(bytes: Bytes) -> RpcResult<HopHash> {
135 let hash_bytes: [u8; 32] = bytes
136 .0
137 .as_slice()
138 .try_into()
139 .map_err(|_| HopError::InvalidHashLength(bytes.0.len()))?;
140 Ok(HopHash::from(hash_bytes))
141 }
142}
143
144#[async_trait]
145impl<C, Block> HopApiServer<<Block as BlockT>::Hash> for HopRpcServer<C, Block>
146where
147 Block: BlockT,
148 C: HeaderBackend<Block> + CallApiAt<Block> + Send + Sync + 'static,
149{
150 fn submit(
151 &self,
152 data: Bytes,
153 recipients: Vec<Bytes>,
154 signature: Bytes,
155 signer: Bytes,
156 submit_timestamp: u64,
157 ) -> RpcResult<SubmitResult> {
158 let recipient_keys: RecipientVec = recipients
159 .into_iter()
160 .map(|r| {
161 MultiSigner::decode(&mut &r.0[..])
162 .map(|signer| Recipient { signer, claimed: false })
163 .map_err(|_| HopError::InvalidRecipientKey)
164 })
165 .collect::<Result<Vec<_>, _>>()?
166 .try_into()
167 .map_err(|v: Vec<Recipient>| HopError::TooManyRecipients {
168 provided: v.len(),
169 limit: MAX_RECIPIENTS as usize,
170 })?;
171
172 let signer =
173 MultiSigner::decode(&mut &signer.0[..]).map_err(|_| HopError::InvalidSigner)?;
174 let multi_sig = MultiSignature::decode(&mut &signature.0[..])
175 .map_err(|_| HopError::InvalidSignature)?;
176
177 let chain_info = self.client.info();
178 let best_hash = chain_info.best_hash;
179
180 let data_len = data.0.len();
181
182 let runtime_max = runtime_api::max_promotion_size::<Block, _>(&*self.client, best_hash)
186 .map_err(HopError::from)?;
187 if data_len > runtime_max as usize {
188 return Err(HopError::DataTooLarge(data_len, runtime_max).into());
189 }
190
191 let account_id: AccountId32 = signer.clone().into_account();
196 let authorized = runtime_api::can_account_promote::<Block, _>(
197 &*self.client,
198 best_hash,
199 account_id.clone(),
200 data_len as u32,
201 )
202 .map_err(HopError::from)?;
203 if !authorized {
204 return Err(HopError::NotAuthorized.into());
205 }
206
207 let hash = H256(blake2_256(&data.0));
211 let submit_payload = submit_signing_payload(&hash, submit_timestamp);
212 if !multi_sig.verify(&submit_payload[..], &account_id) {
213 return Err(HopError::InvalidSignature.into());
214 }
215
216 let sender_id: [u8; 32] = account_id.into();
217 self.pool
218 .insert(data.0, recipient_keys, sender_id, signer, multi_sig, submit_timestamp)?;
219 Ok(SubmitResult { pool_status: self.pool.status() })
220 }
221
222 fn claim(&self, raw_hash: Bytes, signature: Bytes) -> RpcResult<Bytes> {
223 let hash = Self::decode_hash(raw_hash)?;
224 let data = self.pool.claim(&hash, &signature.0)?;
225 Ok(Bytes(data))
226 }
227
228 fn ack(&self, raw_hash: Bytes, signature: Bytes) -> RpcResult<()> {
229 let hash = Self::decode_hash(raw_hash)?;
230 self.pool.ack(&hash, &signature.0)?;
231 Ok(())
232 }
233
234 fn pool_status(&self) -> RpcResult<PoolStatus> {
235 Ok(self.pool.status())
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use crate::pool::HopDataPool;
243 use codec::Encode;
244 use sp_api::{ApiError, CallApiAtParams};
245 use sp_blockchain::{self, Info};
246 use sp_core::{crypto::Pair, ed25519};
247 use sp_runtime::{
248 traits::{HashingFor, NumberFor},
249 MultiSigner,
250 };
251 use sp_state_machine::InMemoryBackend;
252 use sp_test_primitives::Block;
253 use std::sync::atomic::{AtomicBool, Ordering};
254 use tempfile::TempDir;
255
256 struct MockClient {
257 authorized: AtomicBool,
258 }
259
260 impl MockClient {
261 fn new(authorized: bool) -> Self {
262 Self { authorized: AtomicBool::new(authorized) }
263 }
264 }
265
266 impl HeaderBackend<Block> for MockClient {
267 fn header(
268 &self,
269 _hash: <Block as BlockT>::Hash,
270 ) -> sp_blockchain::Result<Option<<Block as BlockT>::Header>> {
271 Ok(None)
272 }
273
274 fn info(&self) -> Info<Block> {
275 Info {
276 best_hash: Default::default(),
277 best_number: 0u64,
278 genesis_hash: Default::default(),
279 finalized_hash: Default::default(),
280 finalized_number: 0u64,
281 finalized_state: None,
282 number_leaves: 0,
283 block_gap: None,
284 }
285 }
286
287 fn status(
288 &self,
289 _hash: <Block as BlockT>::Hash,
290 ) -> sp_blockchain::Result<sp_blockchain::BlockStatus> {
291 Ok(sp_blockchain::BlockStatus::Unknown)
292 }
293
294 fn number(
295 &self,
296 _hash: <Block as BlockT>::Hash,
297 ) -> sp_blockchain::Result<Option<NumberFor<Block>>> {
298 Ok(None)
299 }
300
301 fn hash(
302 &self,
303 _number: NumberFor<Block>,
304 ) -> sp_blockchain::Result<Option<<Block as BlockT>::Hash>> {
305 Ok(None)
306 }
307 }
308
309 impl CallApiAt<Block> for MockClient {
310 type StateBackend = InMemoryBackend<HashingFor<Block>>;
311
312 fn call_api_at(&self, params: CallApiAtParams<Block>) -> Result<Vec<u8>, ApiError> {
313 match params.function {
314 "HopRuntimeApi_max_promotion_size" => Ok((2u32 * 1024 * 1024).encode()),
315 "HopRuntimeApi_can_account_promote" => {
316 Ok(self.authorized.load(Ordering::Relaxed).encode())
317 },
318 "HopRuntimeApi_is_promoted_on_chain" => Ok(false.encode()),
319 other => Err(ApiError::Application(
320 format!("MockClient: unimplemented runtime API call {}", other).into(),
321 )),
322 }
323 }
324
325 fn runtime_version_at(
326 &self,
327 _at_hash: <Block as BlockT>::Hash,
328 _call_context: sp_api::CallContext,
329 ) -> Result<sp_version::RuntimeVersion, ApiError> {
330 unimplemented!("MockClient::runtime_version_at not used by tests")
331 }
332
333 fn state_at(&self, _at: <Block as BlockT>::Hash) -> Result<Self::StateBackend, ApiError> {
334 unimplemented!("MockClient::state_at not used by tests")
335 }
336
337 fn initialize_extensions(
338 &self,
339 _at: <Block as BlockT>::Hash,
340 _extensions: &mut sp_externalities::Extensions,
341 ) -> Result<(), ApiError> {
342 Ok(())
343 }
344 }
345
346 fn setup(authorized: bool) -> (HopRpcServer<MockClient, Block>, Arc<HopDataPool>, TempDir) {
347 let dir = TempDir::new().unwrap();
348 let pool = Arc::new(
349 HopDataPool::new(
350 1024 * 1024,
351 1024 * 1024,
352 100,
353 dir.path().to_path_buf(),
354 crate::rate_limit::RateLimitConfig::disabled(),
355 )
356 .unwrap(),
357 );
358 let client = Arc::new(MockClient::new(authorized));
359 let rpc = HopRpcServer::new(pool.clone(), client);
360 (rpc, pool, dir)
361 }
362
363 fn make_keypair() -> (ed25519::Pair, MultiSigner) {
364 let pair = ed25519::Pair::from_seed(&[1u8; 32]);
365 let signer = MultiSigner::Ed25519(pair.public());
366 (pair, signer)
367 }
368
369 const TEST_SUBMIT_TS: u64 = 1_700_000_000_000;
371
372 fn submit_sig(pair: &ed25519::Pair, data: &[u8], submit_timestamp: u64) -> Bytes {
374 let hash = H256(blake2_256(data));
375 let payload = submit_signing_payload(&hash, submit_timestamp);
376 let multi_sig = MultiSignature::Ed25519(pair.sign(&payload));
377 Bytes(multi_sig.encode())
378 }
379
380 fn claim_sig(pair: &ed25519::Pair, hash: &H256) -> Bytes {
381 use crate::types::{signing_payload, HOP_CLAIM_CONTEXT};
382 let payload = signing_payload(HOP_CLAIM_CONTEXT, hash);
383 Bytes(MultiSignature::Ed25519(pair.sign(&payload)).encode())
384 }
385
386 fn ack_sig(pair: &ed25519::Pair, hash: &H256) -> Bytes {
387 use crate::types::{signing_payload, HOP_ACK_CONTEXT};
388 let payload = signing_payload(HOP_ACK_CONTEXT, hash);
389 Bytes(MultiSignature::Ed25519(pair.sign(&payload)).encode())
390 }
391
392 #[test]
393 fn submit_invalid_scale_signer_returns_error() {
394 let (rpc, _, _dir) = setup(true);
395 let (_, valid_signer) = make_keypair();
398 let result = rpc.submit(
399 Bytes(vec![1, 2, 3]),
400 vec![Bytes(valid_signer.encode())],
401 Bytes(vec![0u8; 3]),
402 Bytes(vec![0u8; 3]),
403 TEST_SUBMIT_TS,
404 );
405 assert!(result.is_err());
406 let err = result.unwrap_err();
407 assert!(err.message().contains("SCALE-decode MultiSigner"), "got: {}", err.message());
408 }
409
410 #[test]
411 fn submit_invalid_scale_signature_returns_error() {
412 let (rpc, _, _dir) = setup(true);
413 let (_, signer) = make_keypair();
414 let result = rpc.submit(
415 Bytes(vec![1, 2, 3]),
416 vec![Bytes(signer.encode())],
417 Bytes(vec![0u8; 3]),
418 Bytes(signer.encode()),
419 TEST_SUBMIT_TS,
420 );
421 assert!(result.is_err());
422 let err = result.unwrap_err();
423 assert!(err.message().contains("Invalid signature"), "got: {}", err.message());
424 }
425
426 #[test]
427 fn submit_bad_signature_returns_error() {
428 let (rpc, _, _dir) = setup(true);
429 let (_, signer) = make_keypair();
430 let wrong_pair = ed25519::Pair::from_seed(&[99u8; 32]);
432 let data = vec![1, 2, 3];
433 let sig = submit_sig(&wrong_pair, &data, TEST_SUBMIT_TS);
434
435 let result = rpc.submit(
436 Bytes(data),
437 vec![Bytes(signer.encode())],
438 sig,
439 Bytes(signer.encode()),
440 TEST_SUBMIT_TS,
441 );
442 assert!(result.is_err());
443 let err = result.unwrap_err();
444 assert!(err.message().contains("Invalid signature"), "got: {}", err.message());
445 }
446
447 #[test]
448 fn submit_unauthorized_account_returns_error() {
449 let (rpc, _, _dir) = setup(false);
450 let (pair, signer) = make_keypair();
451 let data = vec![1, 2, 3];
452 let sig = submit_sig(&pair, &data, TEST_SUBMIT_TS);
453
454 let result = rpc.submit(
455 Bytes(data),
456 vec![Bytes(signer.encode())],
457 sig,
458 Bytes(signer.encode()),
459 TEST_SUBMIT_TS,
460 );
461 assert!(result.is_err());
462 let err = result.unwrap_err();
463 assert!(err.message().contains("authorization"), "got: {}", err.message());
464 }
465
466 #[test]
467 fn submit_success() {
468 let (rpc, pool, _dir) = setup(true);
469 let (pair, signer) = make_keypair();
470 let data = vec![1, 2, 3, 4, 5];
471 let sig = submit_sig(&pair, &data, TEST_SUBMIT_TS);
472
473 let result = rpc.submit(
474 Bytes(data),
475 vec![Bytes(signer.encode())],
476 sig,
477 Bytes(signer.encode()),
478 TEST_SUBMIT_TS,
479 );
480 assert!(result.is_ok(), "submit failed: {:?}", result.err());
481 let submit_result = result.unwrap();
482 assert_eq!(submit_result.pool_status.entry_count, 1);
483 assert_eq!(submit_result.pool_status.total_bytes, crate::types::entry_accounted_size(5, 1),);
485 assert_eq!(pool.status().entry_count, 1);
486 }
487
488 #[test]
489 fn submit_rejects_oversized_recipient_list() {
490 let (rpc, _, _dir) = setup(true);
491 let (pair, signer) = make_keypair();
492 let data = vec![1, 2, 3];
493 let sig = submit_sig(&pair, &data, TEST_SUBMIT_TS);
494
495 let oversized: Vec<Bytes> = std::iter::repeat_with(|| Bytes(signer.encode()))
496 .take(MAX_RECIPIENTS as usize + 1)
497 .collect();
498
499 let result =
500 rpc.submit(Bytes(data), oversized, sig, Bytes(signer.encode()), TEST_SUBMIT_TS);
501 assert!(result.is_err());
502 let err = result.unwrap_err();
503 assert!(err.message().contains("Too many recipients"), "got: {}", err.message());
504 }
505
506 #[test]
507 fn claim_invalid_hash_length() {
508 let (rpc, _, _dir) = setup(true);
509 let result = rpc.claim(Bytes(vec![0u8; 31]), Bytes(vec![0u8; 64]));
510 assert!(result.is_err());
511 let err = result.unwrap_err();
512 assert!(err.message().contains("expected 32 bytes"), "got: {}", err.message());
513 }
514
515 #[test]
516 fn claim_and_ack_through_rpc() {
517 let (rpc, _, _dir) = setup(true);
518 let (pair, signer) = make_keypair();
519 let data = vec![10, 20, 30];
520 let sig = submit_sig(&pair, &data, TEST_SUBMIT_TS);
521
522 rpc.submit(
523 Bytes(data.clone()),
524 vec![Bytes(signer.encode())],
525 sig,
526 Bytes(signer.encode()),
527 TEST_SUBMIT_TS,
528 )
529 .unwrap();
530
531 let hash = H256(blake2_256(&data));
532 let claimed = rpc.claim(Bytes(hash.0.to_vec()), claim_sig(&pair, &hash)).unwrap();
533 assert_eq!(claimed.0, data);
534
535 rpc.ack(Bytes(hash.0.to_vec()), ack_sig(&pair, &hash)).unwrap();
536
537 let status = rpc.pool_status().unwrap();
538 assert_eq!(status.entry_count, 0);
539 }
540
541 #[test]
542 fn pool_status_returns_correct_values() {
543 let (rpc, _, _dir) = setup(true);
544 let status = rpc.pool_status().unwrap();
545 assert_eq!(status.entry_count, 0);
546 assert_eq!(status.total_bytes, 0);
547 assert_eq!(status.max_bytes, 1024 * 1024);
548 }
549}