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