Skip to content

Commit 848f0bf

Browse files
authored
Unbox the Signer (#4005)
## Motivation As a stopgap measure, when @deuszx first implemented the `Signer` trait, he passed it as a trait object, boxing all its occurrences and associated types. Now that we have #3828, this is no longer necessary, and will make implementing it for non-`Send` types harder, as each boxing site will need to decide whether the resulting type should be `Box<dyn Signer>` or `Box<dyn Signer + Send>`. ## Proposal Unbox it, passing it in as a type parameter through the `linera_core::Environment` trait. ## Test Plan CI. ## Release Plan - Nothing to do / These changes follow the usual release cycle. ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 966e81b commit 848f0bf

File tree

21 files changed

+234
-369
lines changed

21 files changed

+234
-369
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

linera-base/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ tokio = { workspace = true, features = ["time"] }
6363
tokio-stream.workspace = true
6464
tracing.workspace = true
6565
tracing-subscriber = { workspace = true, features = ["json", "fmt", "ansi"] }
66+
trait-variant.workspace = true
6667
wasm-bindgen = { workspace = true, optional = true }
6768
wasm-bindgen-futures = { workspace = true, optional = true }
6869
wasm_thread = { workspace = true, optional = true }

linera-base/src/crypto/signer.rs

Lines changed: 29 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Zefchain Labs, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use async_trait::async_trait;
54
pub use in_mem::InMemorySigner;
65

76
use super::CryptoHash;
@@ -11,8 +10,11 @@ use crate::{
1110
};
1211

1312
/// A trait for signing keys.
14-
#[async_trait]
15-
pub trait Signer: Send + Sync {
13+
#[cfg_attr(not(web), trait_variant::make(Send))]
14+
pub trait Signer {
15+
/// The type of errors arising from operations on this `Signer`.
16+
type Error: std::error::Error + Send + Sync + 'static;
17+
1618
/// Creates a signature for the given `value` using the provided `owner`.
1719
// DEV: We sign `CryptoHash` type, rather than `&[u8]` to make sure we don't sign
1820
// things accidentally. See [`CryptoHash::new`] for how the type's name is included
@@ -21,38 +23,13 @@ pub trait Signer: Send + Sync {
2123
&self,
2224
owner: &AccountOwner,
2325
value: &CryptoHash,
24-
) -> Result<AccountSignature, Box<dyn std::error::Error>>;
26+
) -> Result<AccountSignature, Self::Error>;
2527

2628
/// Returns the public key corresponding to the given `owner`.
27-
async fn get_public_key(
28-
&self,
29-
owner: &AccountOwner,
30-
) -> Result<AccountPublicKey, Box<dyn std::error::Error>>;
29+
async fn get_public_key(&self, owner: &AccountOwner) -> Result<AccountPublicKey, Self::Error>;
3130

3231
/// Returns whether the given `owner` is a known signer.
33-
async fn contains_key(&self, owner: &AccountOwner) -> Result<bool, Box<dyn std::error::Error>>;
34-
}
35-
36-
#[async_trait]
37-
impl Signer for Box<dyn Signer> {
38-
async fn sign(
39-
&self,
40-
owner: &AccountOwner,
41-
value: &CryptoHash,
42-
) -> Result<AccountSignature, Box<dyn std::error::Error>> {
43-
(**self).sign(owner, value).await
44-
}
45-
46-
async fn get_public_key(
47-
&self,
48-
owner: &AccountOwner,
49-
) -> Result<AccountPublicKey, Box<dyn std::error::Error>> {
50-
(**self).get_public_key(owner).await
51-
}
52-
53-
async fn contains_key(&self, owner: &AccountOwner) -> Result<bool, Box<dyn std::error::Error>> {
54-
(**self).contains_key(owner).await
55-
}
32+
async fn contains_key(&self, owner: &AccountOwner) -> Result<bool, Self::Error>;
5633
}
5734

5835
/// In-memory implementation of the [`Signer`] trait.
@@ -62,7 +39,6 @@ mod in_mem {
6239
sync::{Arc, RwLock},
6340
};
6441

65-
use async_trait::async_trait;
6642
use serde::{Deserialize, Serialize};
6743

6844
#[cfg(with_getrandom)]
@@ -72,6 +48,12 @@ mod in_mem {
7248
identifiers::AccountOwner,
7349
};
7450

51+
#[derive(Debug, thiserror::Error)]
52+
pub enum Error {
53+
#[error("no key found for the given owner")]
54+
NoSuchOwner,
55+
}
56+
7557
/// In-memory signer.
7658
#[derive(Clone)]
7759
pub struct InMemorySigner(Arc<RwLock<InMemSignerInner>>);
@@ -173,42 +155,39 @@ mod in_mem {
173155
}
174156
}
175157

176-
#[async_trait]
177158
impl Signer for InMemorySigner {
159+
type Error = Error;
160+
178161
/// Creates a signature for the given `value` using the provided `owner`.
179162
async fn sign(
180163
&self,
181164
owner: &AccountOwner,
182165
value: &CryptoHash,
183-
) -> Result<AccountSignature, Box<dyn std::error::Error>> {
166+
) -> Result<AccountSignature, Error> {
184167
let inner = self.0.read().unwrap();
185168
if let Some(secret) = inner.keys.get(owner) {
186169
let signature = secret.sign_prehash(*value);
187170
Ok(signature)
188171
} else {
189-
Err("No key found for the given owner".into())
172+
Err(Error::NoSuchOwner)
190173
}
191174
}
192175

193176
/// Returns the public key corresponding to the given `owner`.
194-
async fn get_public_key(
195-
&self,
196-
owner: &AccountOwner,
197-
) -> Result<AccountPublicKey, Box<dyn std::error::Error>> {
198-
let inner = self.0.read().unwrap();
199-
match inner.keys.get(owner).map(|s| s.public()) {
200-
Some(public) => Ok(public),
201-
None => Err("No key found for the given owner".into()),
202-
}
177+
async fn get_public_key(&self, owner: &AccountOwner) -> Result<AccountPublicKey, Error> {
178+
Ok(self
179+
.0
180+
.read()
181+
.unwrap()
182+
.keys
183+
.get(owner)
184+
.ok_or(Error::NoSuchOwner)?
185+
.public())
203186
}
204187

205188
/// Returns whether the given `owner` is a known signer.
206-
async fn contains_key(
207-
&self,
208-
owner: &AccountOwner,
209-
) -> Result<bool, Box<dyn std::error::Error>> {
210-
let inner = self.0.read().unwrap();
211-
Ok(inner.keys.contains_key(owner))
189+
async fn contains_key(&self, owner: &AccountOwner) -> Result<bool, Error> {
190+
Ok(self.0.read().unwrap().keys.contains_key(owner))
212191
}
213192
}
214193

linera-base/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
#![deny(missing_docs)]
99
#![deny(clippy::large_futures)]
10+
#![allow(async_fn_in_trait)]
1011

1112
use std::fmt;
1213

linera-chain/src/data_types.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
// Copyright (c) Zefchain Labs, Inc.
33
// SPDX-License-Identifier: Apache-2.0
44

5-
use std::{
6-
collections::{BTreeMap, BTreeSet, HashSet},
7-
error::Error,
8-
};
5+
use std::collections::{BTreeMap, BTreeSet, HashSet};
96

107
use async_graphql::SimpleObject;
118
use custom_debug_derive::Debug;
@@ -501,12 +498,12 @@ pub struct ProposalContent {
501498
}
502499

503500
impl BlockProposal {
504-
pub async fn new_initial(
501+
pub async fn new_initial<S: Signer + ?Sized>(
505502
owner: AccountOwner,
506503
round: Round,
507504
block: ProposedBlock,
508-
signer: &(impl Signer + ?Sized),
509-
) -> Result<Self, Box<dyn Error>> {
505+
signer: &S,
506+
) -> Result<Self, S::Error> {
510507
let content = ProposalContent {
511508
round,
512509
block,
@@ -523,12 +520,12 @@ impl BlockProposal {
523520
})
524521
}
525522

526-
pub async fn new_retry_fast(
523+
pub async fn new_retry_fast<S: Signer + ?Sized>(
527524
owner: AccountOwner,
528525
round: Round,
529526
old_proposal: BlockProposal,
530-
signer: &(impl Signer + ?Sized),
531-
) -> Result<Self, Box<dyn Error>> {
527+
signer: &S,
528+
) -> Result<Self, S::Error> {
532529
let content = ProposalContent {
533530
round,
534531
block: old_proposal.content.block,
@@ -548,12 +545,12 @@ impl BlockProposal {
548545
})
549546
}
550547

551-
pub async fn new_retry_regular(
548+
pub async fn new_retry_regular<S: Signer>(
552549
owner: AccountOwner,
553550
round: Round,
554551
validated_block_certificate: ValidatedBlockCertificate,
555-
signer: &(impl Signer + ?Sized),
556-
) -> Result<Self, Box<dyn Error>> {
552+
signer: &S,
553+
) -> Result<Self, S::Error> {
557554
let certificate = validated_block_certificate.lite_certificate().cloned();
558555
let block = validated_block_certificate.into_inner().into_inner();
559556
let (block, outcome) = block.into_proposal();

linera-chain/src/test/mod.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,22 +83,22 @@ pub trait BlockTestExt: Sized {
8383

8484
/// Returns a block proposal in the first round in a default ownership configuration
8585
/// (`Round::MultiLeader(0)`) without any hashed certificate values or validated block.
86-
async fn into_first_proposal(
86+
async fn into_first_proposal<S: Signer + ?Sized>(
8787
self,
8888
owner: AccountOwner,
89-
signer: &(impl Signer + ?Sized),
90-
) -> Result<BlockProposal, Box<dyn std::error::Error>> {
89+
signer: &S,
90+
) -> Result<BlockProposal, S::Error> {
9191
self.into_proposal_with_round(owner, signer, Round::MultiLeader(0))
9292
.await
9393
}
9494

9595
/// Returns a block proposal without any hashed certificate values or validated block.
96-
async fn into_proposal_with_round(
96+
async fn into_proposal_with_round<S: Signer + ?Sized>(
9797
self,
9898
owner: AccountOwner,
99-
signer: &(impl Signer + ?Sized),
99+
signer: &S,
100100
round: Round,
101-
) -> Result<BlockProposal, Box<dyn std::error::Error>>;
101+
) -> Result<BlockProposal, S::Error>;
102102
}
103103

104104
impl BlockTestExt for ProposedBlock {
@@ -147,12 +147,12 @@ impl BlockTestExt for ProposedBlock {
147147
self
148148
}
149149

150-
async fn into_proposal_with_round(
150+
async fn into_proposal_with_round<S: Signer + ?Sized>(
151151
self,
152152
owner: AccountOwner,
153-
signer: &(impl Signer + ?Sized),
153+
signer: &S,
154154
round: Round,
155-
) -> Result<BlockProposal, Box<dyn std::error::Error>> {
155+
) -> Result<BlockProposal, S::Error> {
156156
BlockProposal::new_initial(owner, round, self, signer).await
157157
}
158158
}

linera-client/src/client_context.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{collections::HashSet, sync::Arc};
77

88
use futures::Future;
99
use linera_base::{
10-
crypto::{CryptoHash, Signer, ValidatorPublicKey},
10+
crypto::{CryptoHash, ValidatorPublicKey},
1111
data_types::{BlockHeight, Timestamp},
1212
identifiers::{Account, AccountOwner, ChainId},
1313
ownership::ChainOwnership,
@@ -126,17 +126,13 @@ where
126126
}
127127
}
128128

129-
impl<S, W> ClientContext<linera_core::environment::Impl<S, NodeProvider>, W>
129+
impl<S, Si, W> ClientContext<linera_core::environment::Impl<S, NodeProvider, Si>, W>
130130
where
131131
S: linera_core::environment::Storage,
132+
Si: linera_core::environment::Signer,
132133
W: Persist<Target = Wallet>,
133134
{
134-
pub fn new(
135-
storage: S,
136-
options: ClientContextOptions,
137-
wallet: W,
138-
signer: Box<dyn Signer>,
139-
) -> Self {
135+
pub fn new(storage: S, options: ClientContextOptions, wallet: W, signer: Si) -> Self {
140136
let node_provider = NodeProvider::new(NodeOptions {
141137
send_timeout: options.send_timeout,
142138
recv_timeout: options.recv_timeout,
@@ -154,8 +150,8 @@ where
154150
linera_core::environment::Impl {
155151
network: node_provider,
156152
storage,
153+
signer,
157154
},
158-
signer,
159155
options.max_pending_message_bundles,
160156
wallet.genesis_admin_chain(),
161157
delivery,
@@ -181,7 +177,7 @@ where
181177
}
182178

183179
#[cfg(with_testing)]
184-
pub fn new_test_client_context(storage: S, wallet: W, signer: Box<dyn Signer>) -> Self {
180+
pub fn new_test_client_context(storage: S, wallet: W, signer: Si) -> Self {
185181
use linera_core::DEFAULT_GRACE_PERIOD;
186182

187183
let send_recv_timeout = Duration::from_millis(4000);
@@ -205,8 +201,8 @@ where
205201
linera_core::environment::Impl {
206202
storage,
207203
network: NodeProvider::new(node_options),
204+
signer,
208205
},
209-
signer,
210206
10,
211207
wallet.genesis_admin_chain(),
212208
delivery,

linera-client/src/unit_tests/chain_listener.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ async fn test_chain_listener() -> anyhow::Result<()> {
104104
let config = ChainListenerConfig::default();
105105
let storage_builder = MemoryStorageBuilder::default();
106106
let clock = storage_builder.clock().clone();
107-
let mut builder = TestBuilder::new(storage_builder, 4, 1, &mut signer).await?;
107+
let mut builder = TestBuilder::new(storage_builder, 4, 1, signer.clone()).await?;
108108
let client0 = builder.add_root_chain(0, Amount::ONE).await?;
109109
let chain_id0 = client0.chain_id();
110110
let client1 = builder.add_root_chain(1, Amount::ONE).await?;
@@ -120,8 +120,8 @@ async fn test_chain_listener() -> anyhow::Result<()> {
120120
environment::Impl {
121121
storage: storage.clone(),
122122
network: builder.make_node_provider(),
123+
signer,
123124
},
124-
Box::new(signer),
125125
10,
126126
admin_id,
127127
delivery,

linera-client/src/unit_tests/wallet.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async fn test_save_wallet_with_pending_blobs() -> anyhow::Result<()> {
2626
let mut signer = InMemorySigner::new(Some(42));
2727
let new_pubkey = signer.generate_new();
2828
let clock = storage_builder.clock().clone();
29-
let mut builder = TestBuilder::new(storage_builder, 4, 1, &mut signer).await?;
29+
let mut builder = TestBuilder::new(storage_builder, 4, 1, signer.clone()).await?;
3030
builder.add_root_chain(0, Amount::ONE).await?;
3131
let chain_id = builder.admin_id();
3232
let storage = builder.make_storage().await?;
@@ -66,7 +66,7 @@ async fn test_save_wallet_with_pending_blobs() -> anyhow::Result<()> {
6666
},
6767
blobs: vec![Blob::new_data(b"blob".to_vec())],
6868
});
69-
let mut context = ClientContext::new_test_client_context(storage, wallet, Box::new(signer));
69+
let mut context = ClientContext::new_test_client_context(storage, wallet, signer);
7070
context.save_wallet().await?;
7171
Ok(())
7272
}

0 commit comments

Comments
 (0)