linera_core/environment/wallet/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::ops::Deref;
5
6use futures::{Stream, StreamExt as _, TryStreamExt as _};
7use linera_base::{
8    crypto::CryptoHash,
9    data_types::{BlockHeight, ChainDescription, Epoch, Timestamp},
10    identifiers::{AccountOwner, ChainId},
11};
12
13use crate::{client::PendingProposal, data_types::ChainInfo};
14
15mod memory;
16pub use memory::Memory;
17
18#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
19pub struct Chain {
20    pub owner: Option<AccountOwner>,
21    pub block_hash: Option<CryptoHash>,
22    pub next_block_height: BlockHeight,
23    pub timestamp: Timestamp,
24    pub pending_proposal: Option<PendingProposal>,
25    pub epoch: Option<Epoch>,
26}
27
28impl From<&ChainInfo> for Chain {
29    fn from(info: &ChainInfo) -> Self {
30        Self {
31            owner: None,
32            block_hash: info.block_hash,
33            next_block_height: info.next_block_height,
34            timestamp: info.timestamp,
35            pending_proposal: None,
36            epoch: Some(info.epoch),
37        }
38    }
39}
40
41impl From<ChainInfo> for Chain {
42    fn from(info: ChainInfo) -> Self {
43        Self::from(&info)
44    }
45}
46
47impl From<&ChainDescription> for Chain {
48    fn from(description: &ChainDescription) -> Self {
49        Self::new(None, description.config().epoch, description.timestamp())
50    }
51}
52
53impl From<ChainDescription> for Chain {
54    fn from(description: ChainDescription) -> Self {
55        (&description).into()
56    }
57}
58
59impl Chain {
60    /// Creates a chain that we haven't interacted with before.
61    pub fn new(owner: Option<AccountOwner>, current_epoch: Epoch, now: Timestamp) -> Self {
62        Self {
63            owner,
64            block_hash: None,
65            timestamp: now,
66            next_block_height: BlockHeight::ZERO,
67            pending_proposal: None,
68            epoch: Some(current_epoch),
69        }
70    }
71
72    /// Returns `true` if we only follow this chain's blocks without participating in consensus.
73    ///
74    /// A chain is follow-only if there is no key pair configured for it, i.e., if `owner` is
75    /// `None`.
76    pub fn is_follow_only(&self) -> bool {
77        self.owner.is_none()
78    }
79}
80
81/// A trait for the wallet (i.e. set of chain states) tracked by the client.
82#[cfg_attr(not(web), trait_variant::make(Send))]
83pub trait Wallet {
84    type Error: std::error::Error + Send + Sync;
85    async fn get(&self, id: ChainId) -> Result<Option<Chain>, Self::Error>;
86    async fn remove(&self, id: ChainId) -> Result<Option<Chain>, Self::Error>;
87    fn items(&self) -> impl Stream<Item = Result<(ChainId, Chain), Self::Error>>;
88    async fn insert(&self, id: ChainId, chain: Chain) -> Result<Option<Chain>, Self::Error>;
89    async fn try_insert(&self, id: ChainId, chain: Chain) -> Result<Option<Chain>, Self::Error>;
90
91    fn chain_ids(&self) -> impl Stream<Item = Result<ChainId, Self::Error>> {
92        self.items().map(|result| result.map(|kv| kv.0))
93    }
94
95    fn owned_chain_ids(&self) -> impl Stream<Item = Result<ChainId, Self::Error>> {
96        self.items()
97            .try_filter_map(|(id, chain)| async move { Ok(chain.owner.map(|_| id)) })
98    }
99
100    /// Modifies a chain in the wallet. Returns `Ok(None)` if the chain doesn't exist.
101    async fn modify(
102        &self,
103        id: ChainId,
104        f: impl FnMut(&mut Chain) + Send,
105    ) -> Result<Option<()>, Self::Error>;
106}
107
108impl<W: Deref<Target: Wallet> + linera_base::util::traits::AutoTraits> Wallet for W {
109    type Error = <W::Target as Wallet>::Error;
110
111    async fn get(&self, id: ChainId) -> Result<Option<Chain>, Self::Error> {
112        self.deref().get(id).await
113    }
114
115    async fn remove(&self, id: ChainId) -> Result<Option<Chain>, Self::Error> {
116        self.deref().remove(id).await
117    }
118
119    fn items(&self) -> impl Stream<Item = Result<(ChainId, Chain), Self::Error>> {
120        self.deref().items()
121    }
122
123    async fn insert(&self, id: ChainId, chain: Chain) -> Result<Option<Chain>, Self::Error> {
124        self.deref().insert(id, chain).await
125    }
126
127    async fn try_insert(&self, id: ChainId, chain: Chain) -> Result<Option<Chain>, Self::Error> {
128        self.deref().try_insert(id, chain).await
129    }
130
131    fn chain_ids(&self) -> impl Stream<Item = Result<ChainId, Self::Error>> {
132        self.deref().chain_ids()
133    }
134
135    fn owned_chain_ids(&self) -> impl Stream<Item = Result<ChainId, Self::Error>> {
136        self.deref().owned_chain_ids()
137    }
138
139    async fn modify(
140        &self,
141        id: ChainId,
142        f: impl FnMut(&mut Chain) + Send,
143    ) -> Result<Option<()>, Self::Error> {
144        self.deref().modify(id, f).await
145    }
146}