Skip to content

Commit 34ede10

Browse files
Chain extension: unit tests (#710)
1 parent 90d83ea commit 34ede10

File tree

8 files changed

+598
-58
lines changed

8 files changed

+598
-58
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use frame_support::weights::Weight;
2+
use pallet_contracts::{
3+
chain_extension::{BufInBufOutState, Environment as SubstrateEnvironment, Ext, SysConfig},
4+
ChargedAmount,
5+
};
6+
use sp_core::crypto::UncheckedFrom;
7+
use sp_runtime::DispatchError;
8+
use sp_std::vec::Vec;
9+
10+
use crate::chain_extension::ByteCount;
11+
12+
/// Abstraction around `pallet_contracts::chain_extension::Environment`. Makes testing easier.
13+
///
14+
/// Gathers all the methods that are used by `SnarcosChainExtension`. For now, all operations are
15+
/// performed in the `BufInBufOut` mode, so we don't have to take care of other modes.
16+
///
17+
/// Each method is already documented in `pallet_contracts::chain_extension`.
18+
pub(super) trait Environment: Sized {
19+
/// A type returned by `charge_weight` and passed to `adjust_weight`.
20+
///
21+
/// The original type `ChargedAmount` has only a private constructor and thus we have to
22+
/// abstract it as well to make testing it possible.
23+
type ChargedAmount;
24+
25+
fn in_len(&self) -> ByteCount;
26+
fn read(&self, max_len: u32) -> Result<Vec<u8>, DispatchError>;
27+
28+
fn charge_weight(&mut self, amount: Weight) -> Result<Self::ChargedAmount, DispatchError>;
29+
fn adjust_weight(&mut self, charged: Self::ChargedAmount, actual_weight: Weight);
30+
}
31+
32+
/// Transparent delegation.
33+
impl<E: Ext> Environment for SubstrateEnvironment<'_, '_, E, BufInBufOutState>
34+
where
35+
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
36+
{
37+
type ChargedAmount = ChargedAmount;
38+
39+
fn in_len(&self) -> ByteCount {
40+
self.in_len()
41+
}
42+
43+
fn read(&self, max_len: u32) -> Result<Vec<u8>, DispatchError> {
44+
self.read(max_len)
45+
}
46+
47+
fn charge_weight(&mut self, amount: Weight) -> Result<ChargedAmount, DispatchError> {
48+
self.charge_weight(amount)
49+
}
50+
51+
fn adjust_weight(&mut self, charged: ChargedAmount, actual_weight: Weight) {
52+
self.adjust_weight(charged, actual_weight)
53+
}
54+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use pallet_snarcos::{Error, Pallet as Snarcos, ProvingSystem, VerificationKeyIdentifier};
2+
use sp_std::vec::Vec;
3+
4+
use crate::Runtime;
5+
6+
/// Abstraction around `Runtime`. Makes testing easier.
7+
///
8+
/// Gathers all the methods that are used by `SnarcosChainExtension`.
9+
///
10+
/// Each method is already documented in `pallet_snarcos`.
11+
pub(super) trait Executor: Sized {
12+
/// The error returned from dispatchables is generic. For most purposes however, it doesn't
13+
/// matter what type will be passed there. Normally, `Runtime` will be the generic argument,
14+
/// but in testing it will be sufficient to instantiate it with `()`.
15+
type ErrorGenericType;
16+
17+
fn store_key(
18+
identifier: VerificationKeyIdentifier,
19+
key: Vec<u8>,
20+
) -> Result<(), Error<Self::ErrorGenericType>>;
21+
22+
fn verify(
23+
verification_key_identifier: VerificationKeyIdentifier,
24+
proof: Vec<u8>,
25+
public_input: Vec<u8>,
26+
system: ProvingSystem,
27+
) -> Result<(), Error<Self::ErrorGenericType>>;
28+
}
29+
30+
/// Transparent delegation.
31+
impl Executor for Runtime {
32+
type ErrorGenericType = Runtime;
33+
34+
fn store_key(
35+
identifier: VerificationKeyIdentifier,
36+
key: Vec<u8>,
37+
) -> Result<(), Error<Runtime>> {
38+
Snarcos::<Runtime>::bare_store_key(identifier, key)
39+
}
40+
41+
fn verify(
42+
verification_key_identifier: VerificationKeyIdentifier,
43+
proof: Vec<u8>,
44+
public_input: Vec<u8>,
45+
system: ProvingSystem,
46+
) -> Result<(), Error<Runtime>> {
47+
Snarcos::<Runtime>::bare_verify(verification_key_identifier, proof, public_input, system)
48+
}
49+
}

bin/runtime/src/chain_extension/mod.rs

Lines changed: 70 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
1-
use codec::Decode;
2-
use frame_support::{log::error, pallet_prelude::Weight};
1+
use codec::{Decode, Encode};
2+
use environment::Environment;
3+
use executor::Executor;
4+
use frame_support::{log::error, weights::Weight};
35
use pallet_contracts::chain_extension::{
4-
ChainExtension, Environment, Ext, InitState, RetVal, SysConfig,
5-
};
6-
use pallet_snarcos::{
7-
Config, Error, Pallet as Snarcos, ProvingSystem, VerificationKeyIdentifier, WeightInfo,
6+
ChainExtension, Environment as SubstrateEnvironment, Ext, InitState, RetVal, SysConfig,
87
};
8+
use pallet_snarcos::{Config, Error, ProvingSystem, VerificationKeyIdentifier, WeightInfo};
99
use sp_core::crypto::UncheckedFrom;
1010
use sp_runtime::DispatchError;
1111
use sp_std::{mem::size_of, vec::Vec};
1212
use Error::*;
1313

1414
use crate::{MaximumVerificationKeyLength, Runtime};
15+
mod environment;
16+
mod executor;
17+
#[cfg(test)]
18+
mod tests;
1519

1620
pub const SNARCOS_STORE_KEY_FUNC_ID: u32 = 41;
1721
pub const SNARCOS_VERIFY_FUNC_ID: u32 = 42;
1822

1923
// Return codes for `SNARCOS_STORE_KEY_FUNC_ID`.
2024
pub const SNARCOS_STORE_KEY_OK: u32 = 10_000;
2125
pub const SNARCOS_STORE_KEY_TOO_LONG_KEY: u32 = 10_001;
22-
pub const SNARCOS_STORE_KEY_IN_USE: u32 = 10_002;
26+
pub const SNARCOS_STORE_KEY_IDENTIFIER_IN_USE: u32 = 10_002;
2327
pub const SNARCOS_STORE_KEY_ERROR_UNKNOWN: u32 = 10_003;
2428

2529
// Return codes for `SNARCOS_VERIFY_FUNC_ID`.
@@ -35,13 +39,18 @@ pub const SNARCOS_VERIFY_ERROR_UNKNOWN: u32 = 11_007;
3539
pub struct SnarcosChainExtension;
3640

3741
impl ChainExtension<Runtime> for SnarcosChainExtension {
38-
fn call<E: Ext>(func_id: u32, env: Environment<E, InitState>) -> Result<RetVal, DispatchError>
42+
fn call<E: Ext>(
43+
func_id: u32,
44+
env: SubstrateEnvironment<E, InitState>,
45+
) -> Result<RetVal, DispatchError>
3946
where
4047
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
4148
{
4249
match func_id {
43-
SNARCOS_STORE_KEY_FUNC_ID => Self::snarcos_store_key(env),
44-
SNARCOS_VERIFY_FUNC_ID => Self::snarcos_verify(env),
50+
SNARCOS_STORE_KEY_FUNC_ID => {
51+
Self::snarcos_store_key::<_, Runtime>(env.buf_in_buf_out())
52+
}
53+
SNARCOS_VERIFY_FUNC_ID => Self::snarcos_verify::<_, Runtime>(env.buf_in_buf_out()),
4554
_ => {
4655
error!("Called an unregistered `func_id`: {}", func_id);
4756
Err(DispatchError::Other("Unimplemented func_id"))
@@ -58,7 +67,7 @@ pub type ByteCount = u32;
5867
/// the order of values is important.
5968
///
6069
/// It cannot be `MaxEncodedLen` due to `Vec<_>` and thus `Environment::read_as` cannot be used.
61-
#[derive(Decode)]
70+
#[derive(Decode, Encode)]
6271
struct StoreKeyArgs {
6372
pub identifier: VerificationKeyIdentifier,
6473
pub key: Vec<u8>,
@@ -70,35 +79,53 @@ struct StoreKeyArgs {
7079
/// the order of values is important.
7180
///
7281
/// It cannot be `MaxEncodedLen` due to `Vec<_>` and thus `Environment::read_as` cannot be used.
73-
#[derive(Decode)]
82+
#[derive(Decode, Encode)]
7483
struct VerifyArgs {
7584
pub identifier: VerificationKeyIdentifier,
7685
pub proof: Vec<u8>,
7786
pub input: Vec<u8>,
7887
pub system: ProvingSystem,
7988
}
8089

81-
impl SnarcosChainExtension {
82-
fn snarcos_store_key<E: Ext>(env: Environment<E, InitState>) -> Result<RetVal, DispatchError>
83-
where
84-
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
85-
{
86-
// We need to read input as plain bytes (encoded args).
87-
let mut env = env.buf_in_buf_out();
90+
/// Provides a weight of `store_key` dispatchable.
91+
fn weight_of_store_key(key_length: ByteCount) -> Weight {
92+
<<Runtime as Config>::WeightInfo as WeightInfo>::store_key(key_length)
93+
}
94+
95+
/// Provides a weight of `verify` dispatchable depending on the `ProvingSystem`. In case no system
96+
/// is passed, we return maximal amongst all the systems.
97+
fn weight_of_verify(system: Option<ProvingSystem>) -> Weight {
98+
match system {
99+
Some(ProvingSystem::Groth16) => {
100+
<<Runtime as Config>::WeightInfo as WeightInfo>::verify_groth16()
101+
}
102+
Some(ProvingSystem::Gm17) => <<Runtime as Config>::WeightInfo as WeightInfo>::verify_gm17(),
103+
Some(ProvingSystem::Marlin) => {
104+
<<Runtime as Config>::WeightInfo as WeightInfo>::verify_marlin()
105+
}
106+
None => weight_of_verify(Some(ProvingSystem::Groth16))
107+
.max(weight_of_verify(Some(ProvingSystem::Gm17)))
108+
.max(weight_of_verify(Some(ProvingSystem::Marlin))),
109+
}
110+
}
88111

89-
// Check if it makes sense to read and decode data.
90-
let key_length = env
112+
impl SnarcosChainExtension {
113+
fn snarcos_store_key<Env: Environment, Exc: Executor>(
114+
mut env: Env,
115+
) -> Result<RetVal, DispatchError> {
116+
// Check if it makes sense to read and decode data. This is only an upperbound for the key
117+
// length, because this bytes suffix contains (possibly compressed) info about actual key
118+
// length (needed for decoding).
119+
let approx_key_length = env
91120
.in_len()
92121
.saturating_sub(size_of::<VerificationKeyIdentifier>() as ByteCount);
93-
if key_length > MaximumVerificationKeyLength::get() {
122+
if approx_key_length > MaximumVerificationKeyLength::get() {
94123
return Ok(RetVal::Converging(SNARCOS_STORE_KEY_TOO_LONG_KEY));
95124
}
96125

97126
// We charge now - even if decoding fails and we shouldn't touch storage, we have to incur
98127
// fee for reading memory.
99-
env.charge_weight(<<Runtime as Config>::WeightInfo as WeightInfo>::store_key(
100-
key_length,
101-
))?;
128+
let pre_charged = env.charge_weight(weight_of_store_key(approx_key_length))?;
102129

103130
// Parsing will have to be done here. This is due to the fact that methods
104131
// `Environment<_,_,_,S: BufIn>::read*` don't move starting pointer and thus we can make
@@ -111,42 +138,27 @@ impl SnarcosChainExtension {
111138
let args = StoreKeyArgs::decode(&mut &*bytes)
112139
.map_err(|_| DispatchError::Other("Failed to decode arguments"))?;
113140

114-
let return_status = match Snarcos::<Runtime>::bare_store_key(args.identifier, args.key) {
141+
// Now we know the exact key length.
142+
env.adjust_weight(
143+
pre_charged,
144+
weight_of_store_key(args.key.len() as ByteCount),
145+
);
146+
147+
let return_status = match Exc::store_key(args.identifier, args.key) {
115148
Ok(_) => SNARCOS_STORE_KEY_OK,
116149
// In case `DispatchResultWithPostInfo` was returned (or some simpler equivalent for
117-
// `bare_store_key`), we could adjust weight. However, for the storing key action it
118-
// doesn't make sense.
150+
// `bare_store_key`), we could have adjusted weight. However, for the storing key action
151+
// it doesn't make much sense.
119152
Err(VerificationKeyTooLong) => SNARCOS_STORE_KEY_TOO_LONG_KEY,
120-
Err(IdentifierAlreadyInUse) => SNARCOS_STORE_KEY_IN_USE,
153+
Err(IdentifierAlreadyInUse) => SNARCOS_STORE_KEY_IDENTIFIER_IN_USE,
121154
_ => SNARCOS_STORE_KEY_ERROR_UNKNOWN,
122155
};
123156
Ok(RetVal::Converging(return_status))
124157
}
125158

126-
fn weight_of_verify(system: Option<ProvingSystem>) -> Weight {
127-
match system {
128-
Some(ProvingSystem::Groth16) => {
129-
<<Runtime as Config>::WeightInfo as WeightInfo>::verify_groth16()
130-
}
131-
Some(ProvingSystem::Gm17) => {
132-
<<Runtime as Config>::WeightInfo as WeightInfo>::verify_gm17()
133-
}
134-
Some(ProvingSystem::Marlin) => {
135-
<<Runtime as Config>::WeightInfo as WeightInfo>::verify_marlin()
136-
}
137-
None => Self::weight_of_verify(Some(ProvingSystem::Groth16))
138-
.max(Self::weight_of_verify(Some(ProvingSystem::Gm17)))
139-
.max(Self::weight_of_verify(Some(ProvingSystem::Marlin))),
140-
}
141-
}
142-
143-
fn snarcos_verify<E: Ext>(env: Environment<E, InitState>) -> Result<RetVal, DispatchError>
144-
where
145-
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
146-
{
147-
// We need to read input as plain bytes (encoded args).
148-
let mut env = env.buf_in_buf_out();
149-
159+
fn snarcos_verify<Env: Environment, Exc: Executor>(
160+
mut env: Env,
161+
) -> Result<RetVal, DispatchError> {
150162
// We charge optimistically, i.e. assuming that decoding succeeds and the verification
151163
// key is present. However, since we don't know the system yet, we have to charge maximal
152164
// possible fee. We will adjust it as soon as possible.
@@ -156,23 +168,23 @@ impl SnarcosChainExtension {
156168
// `pallet_snarcos::WeightInfo::verify_decoding_failure`, we can both charge less here
157169
// (with further `env.adjust_weight`) and in the pallet itself (returning
158170
// `DispatchErrorWithPostInfo` reducing actual fee and the block weight).
159-
let pre_charge = env.charge_weight(Self::weight_of_verify(None))?;
171+
let pre_charge = env.charge_weight(weight_of_verify(None))?;
160172

161173
// Parsing is done here for similar reasons as in `Self::snarcos_store_key`.
162174
let bytes = env.read(env.in_len())?;
163175

164176
let args: VerifyArgs = VerifyArgs::decode(&mut &*bytes)
165177
.map_err(|_| DispatchError::Other("Failed to decode arguments"))?;
166178

167-
env.adjust_weight(pre_charge, Self::weight_of_verify(Some(args.system)));
179+
// Now we know the proving system and we can charge appropriate amount of gas.
180+
env.adjust_weight(pre_charge, weight_of_verify(Some(args.system)));
168181

169-
let result =
170-
Snarcos::<Runtime>::bare_verify(args.identifier, args.proof, args.input, args.system);
182+
let result = Exc::verify(args.identifier, args.proof, args.input, args.system);
171183

172184
let return_status = match result {
173185
Ok(_) => SNARCOS_VERIFY_OK,
174186
// In case `DispatchResultWithPostInfo` was returned (or some simpler equivalent for
175-
// `bare_store_key`), we could adjust weight. However, we don't support it yet.
187+
// `bare_verify`), we could adjust weight. However, we don't support it yet.
176188
Err(DeserializingProofFailed) => SNARCOS_VERIFY_DESERIALIZING_PROOF_FAIL,
177189
Err(DeserializingPublicInputFailed) => SNARCOS_VERIFY_DESERIALIZING_INPUT_FAIL,
178190
Err(UnknownVerificationKeyIdentifier) => SNARCOS_VERIFY_UNKNOWN_IDENTIFIER,

0 commit comments

Comments
 (0)