Skip to main content

soil_statement_store/
runtime_api.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Runtime support for the statement store.
8
9use crate::{Hash, Statement, Topic};
10use alloc::vec::Vec;
11use codec::{Decode, Encode};
12use scale_info::TypeInfo;
13use subsoil::runtime_interface::{
14	pass_by::{
15		AllocateAndReturnByCodec, PassFatPointerAndDecode, PassFatPointerAndDecodeSlice,
16		PassPointerAndRead, PassPointerAndReadCopy, ReturnAs,
17	},
18	runtime_interface,
19};
20use Debug;
21
22#[cfg(feature = "std")]
23use subsoil::externalities::ExternalitiesExt;
24
25/// The source of the statement.
26///
27/// Depending on the source we might apply different validation schemes.
28#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)]
29pub enum StatementSource {
30	/// Statement is coming from the on-chain worker.
31	Chain,
32	/// Statement has been received from the gossip network.
33	Network,
34	/// Statement has been submitted over the local api.
35	Local,
36}
37
38impl StatementSource {
39	/// Check if the source allows the statement to be resubmitted to the store, extending its
40	/// expiration date.
41	pub fn can_be_resubmitted(&self) -> bool {
42		match self {
43			StatementSource::Chain | StatementSource::Local => true,
44			StatementSource::Network => false,
45		}
46	}
47}
48
49#[cfg(feature = "std")]
50subsoil::decl_extension! {
51	/// The offchain database extension that will be registered at the Substrate externalities.
52	pub struct StatementStoreExt(std::sync::Arc<dyn crate::StatementStore>);
53}
54
55// Host extensions for the runtime.
56#[cfg(feature = "std")]
57impl StatementStoreExt {
58	/// Create new instance of externalities extensions.
59	pub fn new(store: std::sync::Arc<dyn crate::StatementStore>) -> Self {
60		Self(store)
61	}
62}
63
64/// Submission result.
65#[derive(Debug, Eq, PartialEq, Clone, Copy, Encode, Decode)]
66pub enum SubmitResult {
67	/// Accepted as new.
68	OkNew = 0,
69	/// Known statement
70	OkKnown = 1,
71	/// Statement failed validation.
72	Bad = 2,
73	/// The store is not available.
74	NotAvailable = 3,
75	/// Statement could not be inserted because of priority or size checks.
76	Full = 4,
77}
78
79impl TryFrom<u8> for SubmitResult {
80	type Error = ();
81	fn try_from(value: u8) -> Result<Self, Self::Error> {
82		match value {
83			0 => Ok(SubmitResult::OkNew),
84			1 => Ok(SubmitResult::OkKnown),
85			2 => Ok(SubmitResult::Bad),
86			3 => Ok(SubmitResult::NotAvailable),
87			4 => Ok(SubmitResult::Full),
88			_ => Err(()),
89		}
90	}
91}
92
93impl From<SubmitResult> for u8 {
94	fn from(value: SubmitResult) -> Self {
95		value as u8
96	}
97}
98
99/// Export functions for the WASM host.
100#[cfg(feature = "std")]
101pub type HostFunctions = (statement_store::HostFunctions,);
102
103/// Host interface
104#[runtime_interface]
105pub trait StatementStore {
106	/// Submit a new new statement. The statement will be broadcast to the network.
107	/// This is meant to be used by the offchain worker.
108	fn submit_statement(
109		&mut self,
110		statement: PassFatPointerAndDecode<Statement>,
111	) -> ReturnAs<SubmitResult, u8> {
112		if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
113			match store.submit(statement, StatementSource::Chain) {
114				crate::SubmitResult::New => SubmitResult::OkNew,
115				crate::SubmitResult::Known => SubmitResult::OkKnown,
116				crate::SubmitResult::Rejected(_) => SubmitResult::Full,
117				// This should not happen for `StatementSource::Chain`. An existing statement will
118				// be overwritten.
119				crate::SubmitResult::KnownExpired => SubmitResult::Bad,
120				crate::SubmitResult::Invalid(_) => SubmitResult::Bad,
121				crate::SubmitResult::InternalError(_) => SubmitResult::Bad,
122			}
123		} else {
124			SubmitResult::NotAvailable
125		}
126	}
127
128	/// Return all statements.
129	fn statements(&mut self) -> AllocateAndReturnByCodec<Vec<(Hash, Statement)>> {
130		if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
131			store.statements().unwrap_or_default()
132		} else {
133			Vec::default()
134		}
135	}
136
137	/// Return the data of all known statements which include all topics and have no `DecryptionKey`
138	/// field.
139	fn broadcasts(
140		&mut self,
141		match_all_topics: PassFatPointerAndDecodeSlice<&[Topic]>,
142	) -> AllocateAndReturnByCodec<Vec<Vec<u8>>> {
143		if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
144			store.broadcasts(match_all_topics).unwrap_or_default()
145		} else {
146			Vec::default()
147		}
148	}
149
150	/// Return the data of all known statements whose decryption key is identified as `dest` (this
151	/// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the
152	/// private key for symmetric ciphers).
153	fn posted(
154		&mut self,
155		match_all_topics: PassFatPointerAndDecodeSlice<&[Topic]>,
156		dest: PassPointerAndReadCopy<[u8; 32], 32>,
157	) -> AllocateAndReturnByCodec<Vec<Vec<u8>>> {
158		if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
159			store.posted(match_all_topics, dest).unwrap_or_default()
160		} else {
161			Vec::default()
162		}
163	}
164
165	/// Return the decrypted data of all known statements whose decryption key is identified as
166	/// `dest`. The key must be available to the client.
167	fn posted_clear(
168		&mut self,
169		match_all_topics: PassFatPointerAndDecodeSlice<&[Topic]>,
170		dest: PassPointerAndReadCopy<[u8; 32], 32>,
171	) -> AllocateAndReturnByCodec<Vec<Vec<u8>>> {
172		if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
173			store.posted_clear(match_all_topics, dest).unwrap_or_default()
174		} else {
175			Vec::default()
176		}
177	}
178
179	/// Remove a statement from the store by hash.
180	fn remove(&mut self, hash: PassPointerAndRead<&Hash, 32>) {
181		if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
182			store.remove(hash).unwrap_or_default()
183		}
184	}
185
186	/// Remove all statements from the store that were posted by the given public key.
187	fn remove_by(&mut self, who: PassPointerAndReadCopy<[u8; 32], 32>) {
188		if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
189			let _ = store.remove_by(who);
190		}
191	}
192}