1use std::{collections::BTreeMap, iter::IntoIterator};
5
6use linera_base::{
7 crypto::CryptoHash,
8 data_types::{BlockHeight, ChainDescription, Timestamp},
9 ensure,
10 identifiers::{AccountOwner, ChainId},
11};
12use linera_core::{client::PendingProposal, data_types::ChainInfo};
13use serde::{Deserialize, Serialize};
14
15use crate::{config::GenesisConfig, error, Error};
16
17#[derive(Serialize, Deserialize)]
18pub struct Wallet {
19 pub chains: BTreeMap<ChainId, UserChain>,
20 pub default: Option<ChainId>,
21 pub genesis_config: GenesisConfig,
22}
23
24impl Extend<UserChain> for Wallet {
25 fn extend<Chains: IntoIterator<Item = UserChain>>(&mut self, chains: Chains) {
26 for chain in chains.into_iter() {
27 self.insert(chain);
28 }
29 }
30}
31
32impl Wallet {
33 pub fn new(genesis_config: GenesisConfig) -> Self {
34 Wallet {
35 chains: BTreeMap::new(),
36 default: None,
37 genesis_config,
38 }
39 }
40
41 pub fn get(&self, chain_id: ChainId) -> Option<&UserChain> {
42 self.chains.get(&chain_id)
43 }
44
45 pub fn insert(&mut self, chain: UserChain) {
46 if self.default.is_none() {
47 self.default = Some(chain.chain_id);
48 }
49
50 self.chains.insert(chain.chain_id, chain);
51 }
52
53 pub fn forget_keys(&mut self, chain_id: &ChainId) -> Result<AccountOwner, Error> {
54 let chain = self
55 .chains
56 .get_mut(chain_id)
57 .ok_or(error::Inner::NonexistentChain(*chain_id))?;
58
59 let owner = chain
60 .owner
61 .take()
62 .ok_or(error::Inner::NonexistentKeypair(*chain_id))?;
63
64 Ok(owner)
65 }
66
67 pub fn forget_chain(&mut self, chain_id: &ChainId) -> Result<UserChain, Error> {
68 let user_chain = self
69 .chains
70 .remove(chain_id)
71 .ok_or::<Error>(error::Inner::NonexistentChain(*chain_id).into())?;
72 Ok(user_chain)
73 }
74
75 pub fn default_chain(&self) -> Option<ChainId> {
76 self.default
77 }
78
79 pub fn first_non_admin_chain(&self) -> Option<ChainId> {
80 self.chain_ids()
81 .into_iter()
82 .find(|chain_id| *chain_id != self.genesis_config.admin_id())
83 }
84
85 pub fn chain_ids(&self) -> Vec<ChainId> {
86 self.chains.keys().copied().collect()
87 }
88
89 pub fn owned_chain_ids(&self) -> Vec<ChainId> {
91 self.chains
92 .iter()
93 .filter_map(|(chain_id, chain)| chain.owner.is_some().then_some(*chain_id))
94 .collect()
95 }
96
97 pub fn num_chains(&self) -> usize {
98 self.chains.len()
99 }
100
101 pub fn chains_mut(&mut self) -> impl Iterator<Item = &mut UserChain> {
102 self.chains.values_mut()
103 }
104
105 pub fn assign_new_chain_to_owner(
106 &mut self,
107 owner: AccountOwner,
108 chain_id: ChainId,
109 timestamp: Timestamp,
110 ) -> Result<(), Error> {
111 let user_chain = UserChain {
112 chain_id,
113 owner: Some(owner),
114 block_hash: None,
115 timestamp,
116 next_block_height: BlockHeight(0),
117 pending_proposal: None,
118 };
119 self.insert(user_chain);
120 Ok(())
121 }
122
123 pub fn set_default_chain(&mut self, chain_id: ChainId) -> Result<(), Error> {
124 ensure!(
125 self.chains.contains_key(&chain_id),
126 error::Inner::NonexistentChain(chain_id)
127 );
128 self.default = Some(chain_id);
129 Ok(())
130 }
131
132 pub fn update_from_info(
133 &mut self,
134 pending_proposal: Option<PendingProposal>,
135 owner: Option<AccountOwner>,
136 info: &ChainInfo,
137 ) {
138 self.insert(UserChain {
139 chain_id: info.chain_id,
140 owner,
141 block_hash: info.block_hash,
142 next_block_height: info.next_block_height,
143 timestamp: info.timestamp,
144 pending_proposal,
145 });
146 }
147
148 pub fn genesis_admin_chain(&self) -> ChainId {
149 self.genesis_config.admin_id()
150 }
151
152 pub fn genesis_config(&self) -> &GenesisConfig {
153 &self.genesis_config
154 }
155}
156
157#[derive(Serialize, Deserialize)]
158pub struct UserChain {
159 pub chain_id: ChainId,
160 pub owner: Option<AccountOwner>,
162 pub block_hash: Option<CryptoHash>,
163 pub timestamp: Timestamp,
164 pub next_block_height: BlockHeight,
165 pub pending_proposal: Option<PendingProposal>,
166}
167
168impl Clone for UserChain {
169 fn clone(&self) -> Self {
170 Self {
171 chain_id: self.chain_id,
172 owner: self.owner,
173 block_hash: self.block_hash,
174 timestamp: self.timestamp,
175 next_block_height: self.next_block_height,
176 pending_proposal: self.pending_proposal.clone(),
177 }
178 }
179}
180
181impl UserChain {
182 pub fn make_initial(
184 owner: AccountOwner,
185 description: ChainDescription,
186 timestamp: Timestamp,
187 ) -> Self {
188 Self {
189 chain_id: description.into(),
190 owner: Some(owner),
191 block_hash: None,
192 timestamp,
193 next_block_height: BlockHeight::ZERO,
194 pending_proposal: None,
195 }
196 }
197
198 pub fn make_other(chain_id: ChainId, timestamp: Timestamp) -> Self {
201 Self {
202 chain_id,
203 owner: None,
204 block_hash: None,
205 timestamp,
206 next_block_height: BlockHeight::ZERO,
207 pending_proposal: None,
208 }
209 }
210}