linera_client/
wallet.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    /// Returns the list of all chain IDs for which we have a secret key.
92    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    /// The owner of the chain, if we own it.
163    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    /// Create a user chain that we own.
185    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    /// Creates an entry for a chain that we don't own. The timestamp must be the genesis
201    /// timestamp or earlier.
202    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}