1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
use std::num::NonZeroUsize;
use clru::CLruCache;
use namada_core::address::{Address, EstablishedAddressGen, InternalAddress};
use namada_core::borsh::{BorshDeserialize, BorshSerialize};
use namada_core::chain::{BLOCK_HEIGHT_LENGTH, CHAIN_ID_LENGTH, ChainId};
use namada_core::hash::Hash;
use namada_core::parameters::{EpochDuration, Parameters};
use namada_core::time::DateTimeUtc;
use namada_core::{encode, ethereum_structs};
use namada_gas::{Gas, MEMORY_ACCESS_GAS_PER_BYTE};
use namada_macros::BorshDeserializer;
use namada_merkle_tree::{MerkleRoot, MerkleTree};
#[cfg(feature = "migrations")]
use namada_migrations::*;
use namada_storage::conversion_state::ConversionState;
use namada_storage::tx_queue::ExpiredTxsQueue;
use namada_storage::types::CommitOnlyData;
use namada_storage::{
BlockHeader, BlockHeight, BlockResults, EPOCH_TYPE_LENGTH, Epoch, Epochs,
EthEventsQueue, Key, KeySeg, StorageHasher, TxIndex,
};
use crate::Result;
/// The ledger's state
#[derive(Debug)]
pub struct InMemory<H>
where
H: StorageHasher,
{
/// The ID of the chain
pub chain_id: ChainId,
/// The address of the native token - this is not stored in DB, but read
/// from genesis
pub native_token: Address,
/// Block storage data
pub block: BlockStorage<H>,
/// During `FinalizeBlock`, this is the header of the block that is
/// going to be committed. After a block is committed, this is reset to
/// `None` until the next `FinalizeBlock` phase is reached.
pub header: Option<BlockHeader>,
/// The most recently committed block, if any.
pub last_block: Option<LastBlock>,
/// The epoch of the most recently committed block. If it is `Epoch(0)`,
/// then no block may have been committed for this chain yet.
pub last_epoch: Epoch,
/// Minimum block height at which the next epoch may start
pub next_epoch_min_start_height: BlockHeight,
/// Minimum block time at which the next epoch may start
pub next_epoch_min_start_time: DateTimeUtc,
/// The current established address generator
pub address_gen: EstablishedAddressGen,
/// We delay the switch to a new epoch by the number of blocks set in here.
/// This is `Some` when minimum number of blocks has been created and
/// minimum time has passed since the beginning of the last epoch.
/// Once the value is `Some(0)`, we're ready to switch to a new epoch and
/// this is reset back to `None`.
pub update_epoch_blocks_delay: Option<u32>,
/// The shielded transaction index
pub tx_index: TxIndex,
/// The currently saved conversion state
pub conversion_state: ConversionState,
/// Queue of expired transactions that need to be retransmitted.
///
/// These transactions do not need to be persisted, as they are
/// retransmitted at the **COMMIT** phase immediately following
/// the block when they were queued.
pub expired_txs_queue: ExpiredTxsQueue,
/// The latest block height on Ethereum processed, if
/// the bridge is enabled.
pub ethereum_height: Option<ethereum_structs::BlockHeight>,
/// The queue of Ethereum events to be processed in order.
pub eth_events_queue: EthEventsQueue,
/// How many block heights in the past can the storage be queried
pub storage_read_past_height_limit: Option<u64>,
/// Data that needs to be committed to the merkle tree
pub commit_only_data: CommitOnlyData,
/// Cache of the results of process proposal for the next height to decide.
/// A LRU cache is used to prevent consuming too much memory at times where
/// a node cannot make progress and keeps evaluating new proposals. The
/// different proposed blocks are indexed by their hash. This is used
/// to avoid running process proposal more than once internally because of
/// the shim or the recheck option (comet only calls it at most once
/// for a given height/round)
pub block_proposals_cache: CLruCache<Hash, ProcessProposalCachedResult>,
}
/// Last committed block
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)]
pub struct LastBlock {
/// Block height
pub height: BlockHeight,
/// Block time
pub time: DateTimeUtc,
}
/// The result of process proposal that can be cached for future lookup
#[must_use]
#[derive(Debug, Clone)]
pub enum ProcessProposalCachedResult {
/// The proposed block was accepted by this node with the attached results
/// for every included tx
Accepted(Vec<(u32, String)>),
/// The proposed block was rejected by this node
Rejected,
}
/// The block storage data
#[derive(Debug)]
pub struct BlockStorage<H: StorageHasher> {
/// Merkle tree of all the other data in block storage
pub tree: MerkleTree<H>,
/// From the start of `FinalizeBlock` until the end of `Commit`, this is
/// height of the block that is going to be committed. Otherwise, it is the
/// height of the most recently committed block, or `BlockHeight::sentinel`
/// (0) if no block has been committed yet.
pub height: BlockHeight,
/// From the start of `FinalizeBlock` until the end of `Commit`, this is
/// height of the block that is going to be committed. Otherwise it is the
/// epoch of the most recently committed block, or `Epoch(0)` if no block
/// has been committed yet.
pub epoch: Epoch,
/// Results of applying transactions
pub results: BlockResults,
/// Predecessor block epochs
pub pred_epochs: Epochs,
}
impl<H> InMemory<H>
where
H: StorageHasher,
{
/// Create a new instance of the state
pub fn new(
chain_id: ChainId,
native_token: Address,
storage_read_past_height_limit: Option<u64>,
) -> Self {
let block = BlockStorage {
tree: MerkleTree::default(),
height: BlockHeight::default(),
epoch: Epoch::default(),
pred_epochs: Epochs::default(),
results: BlockResults::default(),
};
InMemory::<H> {
chain_id,
block,
header: None,
last_block: None,
last_epoch: Epoch::default(),
next_epoch_min_start_height: BlockHeight::default(),
#[allow(clippy::disallowed_methods)]
next_epoch_min_start_time: DateTimeUtc::now(),
address_gen: EstablishedAddressGen::new(
"Privacy is a function of liberty.",
),
update_epoch_blocks_delay: None,
tx_index: TxIndex::default(),
conversion_state: ConversionState::default(),
expired_txs_queue: ExpiredTxsQueue::default(),
native_token,
ethereum_height: None,
eth_events_queue: EthEventsQueue::default(),
storage_read_past_height_limit,
commit_only_data: CommitOnlyData::default(),
block_proposals_cache: CLruCache::new(
NonZeroUsize::new(10).unwrap(),
),
}
}
/// Returns the Merkle root hash and the height of the committed block. If
/// no block exists, returns None.
pub fn get_state(&self) -> Option<(MerkleRoot, u64)> {
if self.block.height.0 != 0 {
Some((self.block.tree.root(), self.block.height.0))
} else {
None
}
}
/// Find the root hash of the merkle tree
pub fn merkle_root(&self) -> MerkleRoot {
self.block.tree.root()
}
/// Set the block header.
/// The header is not in the Merkle tree as it's tracked by Tendermint.
/// Hence, we don't update the tree when this is set.
pub fn set_header(&mut self, header: BlockHeader) -> Result<()> {
self.header = Some(header);
Ok(())
}
/// Block data is in the Merkle tree as it's tracked by Tendermint in the
/// block header. Hence, we don't update the tree when this is set.
pub fn begin_block(&mut self, height: BlockHeight) -> Result<()> {
self.block.height = height;
Ok(())
}
/// Store in memory a total gas of a transaction with the given hash.
pub fn add_tx_gas(&mut self, tx_hash: Hash, gas: Gas) {
self.commit_only_data.tx_gas.insert(tx_hash, gas.into());
}
/// Get the chain ID as a raw string
pub fn get_chain_id(&self) -> (ChainId, Gas) {
// Adding consts that cannot overflow
#[allow(clippy::arithmetic_side_effects)]
(
self.chain_id.clone(),
(CHAIN_ID_LENGTH as u64 * MEMORY_ACCESS_GAS_PER_BYTE).into(),
)
}
/// Get the block height
pub fn get_block_height(&self) -> (BlockHeight, Gas) {
// Adding consts that cannot overflow
#[allow(clippy::arithmetic_side_effects)]
(
self.block.height,
(BLOCK_HEIGHT_LENGTH as u64 * MEMORY_ACCESS_GAS_PER_BYTE).into(),
)
}
/// Get the current (yet to be committed) block epoch
pub fn get_current_epoch(&self) -> (Epoch, Gas) {
// Adding consts that cannot overflow
#[allow(clippy::arithmetic_side_effects)]
(
self.block.epoch,
(EPOCH_TYPE_LENGTH as u64 * MEMORY_ACCESS_GAS_PER_BYTE).into(),
)
}
/// Get the epoch of the last committed block
pub fn get_last_epoch(&self) -> (Epoch, Gas) {
// Adding consts that cannot overflow
#[allow(clippy::arithmetic_side_effects)]
(
self.last_epoch,
(EPOCH_TYPE_LENGTH as u64 * MEMORY_ACCESS_GAS_PER_BYTE).into(),
)
}
/// Initialize the first epoch. The first epoch begins at genesis time.
pub fn init_genesis_epoch(
&mut self,
initial_height: BlockHeight,
genesis_time: DateTimeUtc,
parameters: &Parameters,
) -> Result<()> {
let EpochDuration {
min_num_of_blocks,
min_duration,
} = parameters.epoch_duration;
self.next_epoch_min_start_height = initial_height
.checked_add(min_num_of_blocks)
.expect("Next epoch min block height shouldn't overflow");
// Time must not overflow
#[allow(clippy::arithmetic_side_effects)]
{
self.next_epoch_min_start_time = genesis_time + min_duration;
}
self.block.pred_epochs = Epochs {
first_block_heights: vec![initial_height],
};
self.update_epoch_in_merkle_tree()
}
/// Get the current conversions
pub fn get_conversion_state(&self) -> &ConversionState {
&self.conversion_state
}
/// Update the merkle tree with epoch data
pub fn update_epoch_in_merkle_tree(&mut self) -> Result<()> {
let key_prefix: Key =
Address::Internal(InternalAddress::PoS).to_db_key().into();
let key = key_prefix.push(&"epoch_start_height".to_string())?;
self.block
.tree
.update(&key, encode(&self.next_epoch_min_start_height))?;
let key = key_prefix.push(&"epoch_start_time".to_string())?;
self.block
.tree
.update(&key, encode(&self.next_epoch_min_start_time))?;
let key = key_prefix.push(&"current_epoch".to_string())?;
self.block.tree.update(&key, encode(&self.block.epoch))?;
Ok(())
}
/// Get the height of the last committed block or 0 if no block has been
/// committed yet. The first block is at height 1.
pub fn get_last_block_height(&self) -> BlockHeight {
self.last_block
.as_ref()
.map(|b| b.height)
.unwrap_or_default()
}
/// Get the oldest epoch where we can read a value
pub fn get_oldest_epoch(&self) -> Epoch {
let oldest_height = match self.storage_read_past_height_limit {
Some(limit) if limit < self.get_last_block_height().0 => (self
.get_last_block_height()
.0
.checked_sub(limit)
.expect("Cannot underflow"))
.into(),
_ => BlockHeight(1),
};
self.block
.pred_epochs
.get_epoch(oldest_height)
.unwrap_or_default()
}
}