1use std::{
5 iter::IntoIterator,
6 sync::{Arc, RwLock},
7};
8
9use futures::{stream, Stream};
10use linera_base::{
11 data_types::{ChainDescription, ChainOrigin},
12 identifiers::{AccountOwner, ChainId},
13};
14use linera_client::config::GenesisConfig;
15use linera_core::wallet;
16use linera_persistent as persistent;
17
18#[derive(serde::Serialize, serde::Deserialize)]
19struct Data {
20 pub chains: wallet::Memory,
21 default: Arc<RwLock<Option<ChainId>>>,
22 genesis_config: GenesisConfig,
23}
24
25struct ChainDetails {
26 is_default: bool,
27 is_admin: bool,
28 origin: Option<ChainOrigin>,
29 chain_id: ChainId,
30 user_chain: wallet::Chain,
31}
32
33impl ChainDetails {
34 fn new(chain_id: ChainId, wallet: &Data) -> Self {
35 let Some(user_chain) = wallet.chains.get(chain_id) else {
36 panic!("Chain {} not found.", chain_id);
37 };
38 ChainDetails {
39 is_default: Some(chain_id) == *wallet.default.read().unwrap(),
40 is_admin: chain_id == wallet.genesis_config.admin_chain_id(),
41 chain_id,
42 origin: wallet
43 .genesis_config
44 .chains
45 .iter()
46 .find(|description| description.id() == chain_id)
47 .map(ChainDescription::origin),
48 user_chain,
49 }
50 }
51
52 fn print_paragraph(&self) {
53 println!("-----------------------");
54 println!("{:<20} {}", "Chain ID:", self.chain_id);
55
56 let mut tags = Vec::new();
57 if self.is_default {
58 tags.push("DEFAULT");
59 }
60 if self.is_admin {
61 tags.push("ADMIN");
62 }
63 if self.user_chain.is_follow_only() {
64 tags.push("FOLLOW-ONLY");
65 }
66 if !tags.is_empty() {
67 println!("{:<20} {}", "Tags:", tags.join(", "));
68 }
69
70 match self.origin {
71 Some(ChainOrigin::Root(_)) | None => {
72 println!("{:<20} -", "Parent chain:");
73 }
74 Some(ChainOrigin::Child { parent, .. }) => {
75 println!("{:<20} {parent}", "Parent chain:");
76 }
77 }
78
79 if let Some(owner) = &self.user_chain.owner {
80 println!("{:<20} {owner}", "Default owner:");
81 } else {
82 println!("{:<20} No owner key", "Default owner:");
83 }
84
85 println!("{:<20} {}", "Timestamp:", self.user_chain.timestamp);
86 println!("{:<20} {}", "Blocks:", self.user_chain.next_block_height);
87
88 if let Some(epoch) = self.user_chain.epoch {
89 println!("{:<20} {epoch}", "Epoch:");
90 } else {
91 println!("{:<20} -", "Epoch:");
92 }
93
94 if let Some(hash) = self.user_chain.block_hash {
95 println!("{:<20} {hash}", "Latest block hash:");
96 }
97
98 if self.user_chain.pending_proposal.is_some() {
99 println!("{:<20} present", "Pending proposal:");
100 }
101 }
102}
103
104pub struct Wallet(persistent::File<Data>);
105
106impl linera_core::Wallet for Wallet {
110 type Error = persistent::file::Error;
111
112 async fn get(&self, id: ChainId) -> Result<Option<wallet::Chain>, Self::Error> {
113 Ok(self.get(id))
114 }
115
116 async fn remove(&self, id: ChainId) -> Result<Option<wallet::Chain>, Self::Error> {
117 self.remove(id)
118 }
119
120 fn items(&self) -> impl Stream<Item = Result<(ChainId, wallet::Chain), Self::Error>> {
121 stream::iter(self.items().into_iter().map(Ok))
122 }
123
124 async fn insert(
125 &self,
126 id: ChainId,
127 chain: wallet::Chain,
128 ) -> Result<Option<wallet::Chain>, Self::Error> {
129 self.insert(id, &chain)
130 }
131
132 async fn try_insert(
133 &self,
134 id: ChainId,
135 chain: wallet::Chain,
136 ) -> Result<Option<wallet::Chain>, Self::Error> {
137 let chain = self.try_insert(id, chain)?;
138 self.save()?;
139 Ok(chain)
140 }
141
142 async fn modify(
143 &self,
144 id: ChainId,
145 f: impl FnMut(&mut wallet::Chain) + Send,
146 ) -> Result<Option<()>, Self::Error> {
147 self.mutate(id, f).transpose()
148 }
149}
150
151impl Extend<(ChainId, wallet::Chain)> for Wallet {
152 fn extend<It: IntoIterator<Item = (ChainId, wallet::Chain)>>(&mut self, chains: It) {
153 for (id, chain) in chains {
154 if self.0.chains.try_insert(id, chain).is_none() {
155 self.try_set_default(id);
156 }
157 }
158 }
159}
160
161impl Wallet {
162 pub fn get(&self, id: ChainId) -> Option<wallet::Chain> {
163 self.0.chains.get(id)
164 }
165
166 pub fn remove(&self, id: ChainId) -> Result<Option<wallet::Chain>, persistent::file::Error> {
167 let chain = self.0.chains.remove(id);
168 {
169 let mut default = self.0.default.write().unwrap();
170 if *default == Some(id) {
171 *default = None;
172 }
173 }
174 self.0.save()?;
175 Ok(chain)
176 }
177
178 pub fn items(&self) -> Vec<(ChainId, wallet::Chain)> {
179 self.0.chains.items()
180 }
181
182 fn try_set_default(&self, id: ChainId) {
183 let mut guard = self.0.default.write().unwrap();
184 if guard.is_none() {
185 *guard = Some(id);
186 }
187 }
188
189 pub fn insert(
190 &self,
191 id: ChainId,
192 chain: &wallet::Chain,
193 ) -> Result<Option<wallet::Chain>, persistent::file::Error> {
194 let has_owner = chain.owner.is_some();
195 let old_chain = self.0.chains.insert(id, chain.clone());
196 if has_owner {
197 self.try_set_default(id);
198 }
199 self.0.save()?;
200 Ok(old_chain)
201 }
202
203 pub fn try_insert(
204 &self,
205 id: ChainId,
206 chain: wallet::Chain,
207 ) -> Result<Option<wallet::Chain>, persistent::file::Error> {
208 let chain = self.0.chains.try_insert(id, chain);
209 if chain.is_none() {
210 self.try_set_default(id);
211 }
212 self.save()?;
213 Ok(chain)
214 }
215
216 pub fn create(
217 path: &std::path::Path,
218 genesis_config: GenesisConfig,
219 ) -> Result<Self, persistent::file::Error> {
220 Ok(Self(persistent::File::new(
221 path,
222 Data {
223 chains: wallet::Memory::default(),
224 default: Arc::new(RwLock::new(None)),
225 genesis_config,
226 },
227 )?))
228 }
229
230 pub fn read(path: &std::path::Path) -> Result<Self, persistent::file::Error> {
231 Ok(Self(persistent::File::read(path)?))
232 }
233
234 pub fn genesis_config(&self) -> &GenesisConfig {
235 &self.0.genesis_config
236 }
237
238 pub fn genesis_admin_chain_id(&self) -> ChainId {
239 self.0.genesis_config.admin_chain_id()
240 }
241
242 pub fn default_chain(&self) -> Option<ChainId> {
245 *self.0.default.read().unwrap()
246 }
247
248 pub fn pretty_print(&self, chain_ids: Vec<ChainId>) {
249 let chain_ids: Vec<_> = chain_ids.into_iter().collect();
250 let total_chains = chain_ids.len();
251
252 let plural_s = if total_chains == 1 { "" } else { "s" };
253 tracing::info!("Found {total_chains} chain{plural_s}");
254
255 let mut chains = chain_ids
256 .into_iter()
257 .map(|chain_id| ChainDetails::new(chain_id, &self.0))
258 .collect::<Vec<_>>();
259 chains.sort_unstable_by_key(|chain| {
262 let root_id = chain
263 .origin
264 .and_then(|origin| origin.root())
265 .unwrap_or(u32::MAX);
266 let chain_id = chain.chain_id;
267 (!chain.is_default, !chain.is_admin, root_id, chain_id)
268 });
269 for chain in chains {
270 chain.print_paragraph();
271 }
272 println!("------------------------");
273 }
274
275 pub fn set_default_chain(&mut self, id: ChainId) -> Result<(), persistent::file::Error> {
276 assert!(self.0.chains.get(id).is_some());
277 *self.0.default.write().unwrap() = Some(id);
278 self.0.save()
279 }
280
281 pub fn mutate<R>(
282 &self,
283 chain_id: ChainId,
284 mutate: impl FnMut(&mut wallet::Chain) -> R,
285 ) -> Option<Result<R, persistent::file::Error>> {
286 self.0
287 .chains
288 .mutate(chain_id, mutate)
289 .map(|outcome| self.0.save().map(|()| outcome))
290 }
291
292 pub fn forget_keys(&self, chain_id: ChainId) -> anyhow::Result<AccountOwner> {
293 self.mutate(chain_id, |chain| chain.owner.take())
294 .ok_or_else(|| anyhow::anyhow!("nonexistent chain `{chain_id}`"))??
295 .ok_or_else(|| anyhow::anyhow!("keypair not found for chain `{chain_id}`"))
296 }
297
298 pub fn forget_chain(&self, chain_id: ChainId) -> anyhow::Result<wallet::Chain> {
299 let chain = self
300 .0
301 .chains
302 .remove(chain_id)
303 .ok_or_else(|| anyhow::anyhow!("nonexistent chain `{chain_id}`"))?;
304 self.0.save()?;
305 Ok(chain)
306 }
307
308 pub fn save(&self) -> Result<(), persistent::file::Error> {
309 self.0.save()
310 }
311
312 pub fn num_chains(&self) -> usize {
313 self.0.chains.items().len()
314 }
315
316 pub fn chain_ids(&self) -> Vec<ChainId> {
317 self.0.chains.chain_ids()
318 }
319
320 pub fn owned_chain_ids(&self) -> Vec<ChainId> {
322 self.0.chains.owned_chain_ids()
323 }
324}