Skip to main content

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/// The locally tracked state of a single chain.
19#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
20#[allow(missing_docs)]
21pub struct Chain {
22    pub owner: Option<AccountOwner>,
23    pub block_hash: Option<CryptoHash>,
24    pub next_block_height: BlockHeight,
25    pub timestamp: Timestamp,
26    pub pending_fast_proposal: Option<PendingProposal>,
27    pub epoch: Option<Epoch>,
28}
29
30impl From<&ChainInfo> for Chain {
31    fn from(info: &ChainInfo) -> Self {
32        Self {
33            owner: None,
34            block_hash: info.block_hash,
35            next_block_height: info.next_block_height,
36            timestamp: info.timestamp,
37            pending_fast_proposal: None,
38            epoch: Some(info.epoch),
39        }
40    }
41}
42
43impl From<ChainInfo> for Chain {
44    fn from(info: ChainInfo) -> Self {
45        Self::from(&info)
46    }
47}
48
49impl From<&ChainDescription> for Chain {
50    fn from(description: &ChainDescription) -> Self {
51        Self::new(None, description.config().epoch, description.timestamp())
52    }
53}
54
55impl From<ChainDescription> for Chain {
56    fn from(description: ChainDescription) -> Self {
57        (&description).into()
58    }
59}
60
61impl Chain {
62    /// Creates a chain that we haven't interacted with before.
63    pub fn new(owner: Option<AccountOwner>, current_epoch: Epoch, now: Timestamp) -> Self {
64        Self {
65            owner,
66            block_hash: None,
67            timestamp: now,
68            next_block_height: BlockHeight::ZERO,
69            pending_fast_proposal: None,
70            epoch: Some(current_epoch),
71        }
72    }
73
74    /// Returns `true` if we only follow this chain's blocks without participating in consensus.
75    ///
76    /// A chain is follow-only if there is no key pair configured for it, i.e., if `owner` is
77    /// `None`.
78    pub fn is_follow_only(&self) -> bool {
79        self.owner.is_none()
80    }
81}
82
83/// A trait for the wallet (i.e. set of chain states) tracked by the client.
84#[cfg_attr(not(web), trait_variant::make(Send))]
85pub trait Wallet {
86    /// The error type returned by the wallet's operations.
87    type Error: std::error::Error + Send + Sync;
88    /// Returns the state of the chain with the given ID, if it is tracked.
89    async fn get(&self, id: ChainId) -> Result<Option<Chain>, Self::Error>;
90    /// Removes the chain with the given ID, returning its previous state if any.
91    async fn remove(&self, id: ChainId) -> Result<Option<Chain>, Self::Error>;
92    /// Returns a stream over all tracked chains and their states.
93    fn items(&self) -> impl Stream<Item = Result<(ChainId, Chain), Self::Error>>;
94    /// Inserts or replaces the state of the given chain, returning the previous state if any.
95    async fn insert(&self, id: ChainId, chain: Chain) -> Result<Option<Chain>, Self::Error>;
96    /// Inserts the given chain only if it is not already tracked, returning the existing state otherwise.
97    async fn try_insert(&self, id: ChainId, chain: Chain) -> Result<Option<Chain>, Self::Error>;
98
99    /// Returns a stream over the IDs of all tracked chains.
100    fn chain_ids(&self) -> impl Stream<Item = Result<ChainId, Self::Error>> {
101        self.items().map(|result| result.map(|kv| kv.0))
102    }
103
104    /// Returns a stream over the IDs of the tracked chains that have an owner.
105    fn owned_chain_ids(&self) -> impl Stream<Item = Result<ChainId, Self::Error>> {
106        self.items()
107            .try_filter_map(|(id, chain)| async move { Ok(chain.owner.map(|_| id)) })
108    }
109
110    /// Modifies a chain in the wallet. Returns `Ok(None)` if the chain doesn't exist.
111    ///
112    /// The closure may be called more than once (e.g. on CAS contention), so it
113    /// must be idempotent. `Fn` (not `FnMut`) is required to discourage reliance
114    /// on mutable captured state.
115    async fn modify(
116        &self,
117        id: ChainId,
118        f: impl Fn(&mut Chain) + Send,
119    ) -> Result<Option<()>, Self::Error>;
120}
121
122impl<W: Deref<Target: Wallet> + linera_base::util::traits::AutoTraits> Wallet for W {
123    type Error = <W::Target as Wallet>::Error;
124
125    async fn get(&self, id: ChainId) -> Result<Option<Chain>, Self::Error> {
126        self.deref().get(id).await
127    }
128
129    async fn remove(&self, id: ChainId) -> Result<Option<Chain>, Self::Error> {
130        self.deref().remove(id).await
131    }
132
133    fn items(&self) -> impl Stream<Item = Result<(ChainId, Chain), Self::Error>> {
134        self.deref().items()
135    }
136
137    async fn insert(&self, id: ChainId, chain: Chain) -> Result<Option<Chain>, Self::Error> {
138        self.deref().insert(id, chain).await
139    }
140
141    async fn try_insert(&self, id: ChainId, chain: Chain) -> Result<Option<Chain>, Self::Error> {
142        self.deref().try_insert(id, chain).await
143    }
144
145    fn chain_ids(&self) -> impl Stream<Item = Result<ChainId, Self::Error>> {
146        self.deref().chain_ids()
147    }
148
149    fn owned_chain_ids(&self) -> impl Stream<Item = Result<ChainId, Self::Error>> {
150        self.deref().owned_chain_ids()
151    }
152
153    async fn modify(
154        &self,
155        id: ChainId,
156        f: impl Fn(&mut Chain) + Send,
157    ) -> Result<Option<()>, Self::Error> {
158        self.deref().modify(id, f).await
159    }
160}