Skip to content

Commit 5660150

Browse files
authored
Replace execution state hash with zeroes in high epochs (#4824)
## Motivation Hashing gets slow when the execution state grows to large sizes. ## Proposal Temporarily - for testnet only - skip the computation of the execution state hash and return zeroes in high epochs (>20 in this PR). ## Test Plan CI ## Release Plan - Release a new SDK - Update all validators - Move to epoch 20 once that's done ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 8b016be commit 5660150

File tree

10 files changed

+172
-52
lines changed

10 files changed

+172
-52
lines changed

linera-chain/src/chain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use linera_views::{
3030
reentrant_collection_view::{ReadGuardedView, ReentrantCollectionView},
3131
register_view::RegisterView,
3232
set_view::SetView,
33-
views::{ClonableView, CryptoHashView, RootView, View},
33+
views::{ClonableView, RootView, View},
3434
};
3535
use serde::{Deserialize, Serialize};
3636
use tracing::instrument;

linera-core/src/chain_worker/state.rs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ where
716716
notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
717717
) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
718718
let block = certificate.block();
719+
let block_hash = certificate.hash();
719720
let height = block.header.height;
720721
let chain_id = block.header.chain_id;
721722

@@ -854,29 +855,28 @@ where
854855
.await?;
855856
let oracle_responses = Some(block.body.oracle_responses.clone());
856857
let (proposed_block, outcome) = block.clone().into_proposal();
857-
let verified_outcome = if let Some(mut execution_state) =
858-
self.execution_state_cache.remove(&outcome.state_hash)
859-
{
860-
chain.execution_state = execution_state
861-
.with_context(|ctx| {
862-
chain
863-
.execution_state
864-
.context()
865-
.clone_with_base_key(ctx.base_key().bytes.clone())
866-
})
867-
.await;
868-
outcome.clone()
869-
} else {
870-
chain
871-
.execute_block(
872-
&proposed_block,
873-
local_time,
874-
None,
875-
&published_blobs,
876-
oracle_responses,
877-
)
878-
.await?
879-
};
858+
let verified_outcome =
859+
if let Some(mut execution_state) = self.execution_state_cache.remove(&block_hash) {
860+
chain.execution_state = execution_state
861+
.with_context(|ctx| {
862+
chain
863+
.execution_state
864+
.context()
865+
.clone_with_base_key(ctx.base_key().bytes.clone())
866+
})
867+
.await;
868+
outcome.clone()
869+
} else {
870+
chain
871+
.execute_block(
872+
&proposed_block,
873+
local_time,
874+
None,
875+
&published_blobs,
876+
oracle_responses,
877+
)
878+
.await?
879+
};
880880
// We should always agree on the messages and state hash.
881881
ensure!(
882882
outcome == verified_outcome,
@@ -1553,8 +1553,10 @@ where
15531553
.execute_block(block, local_time, round, published_blobs, None),
15541554
)
15551555
.await?;
1556+
let block = Block::new(block.clone(), outcome.clone());
1557+
let block_hash = CryptoHash::new(&block);
15561558
self.execution_state_cache.insert_owned(
1557-
&outcome.state_hash,
1559+
&block_hash,
15581560
self.chain
15591561
.execution_state
15601562
.with_context(|ctx| InactiveContext(ctx.base_key().clone()))

linera-core/src/unit_tests/wasm_worker_tests.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,7 @@ use linera_views::dynamo_db::DynamoDbDatabase;
3838
use linera_views::rocks_db::RocksDbDatabase;
3939
#[cfg(feature = "scylladb")]
4040
use linera_views::scylla_db::ScyllaDbDatabase;
41-
use linera_views::{
42-
context::Context,
43-
memory::MemoryDatabase,
44-
views::{CryptoHashView, View},
45-
};
41+
use linera_views::{context::Context, memory::MemoryDatabase, views::View};
4642
use test_case::test_case;
4743

4844
use super::TestEnvironment;

linera-core/src/unit_tests/worker_tests.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,8 @@ use linera_execution::{
5252
};
5353
use linera_storage::{DbStorage, Storage, TestClock};
5454
use linera_views::{
55-
context::Context,
56-
memory::MemoryDatabase,
57-
random::generate_test_namespace,
58-
store::TestKeyValueDatabase as _,
59-
views::{CryptoHashView, RootView},
55+
context::Context, memory::MemoryDatabase, random::generate_test_namespace,
56+
store::TestKeyValueDatabase as _, views::RootView,
6057
};
6158
use test_case::test_case;
6259
use test_log::test;
@@ -706,7 +703,7 @@ where
706703
let value = ConfirmedBlock::new(
707704
BlockExecutionOutcome {
708705
state_hash,
709-
messages: vec![vec![]],
706+
messages: vec![vec![direct_credit_message(chain_2, small_transfer)]],
710707
oracle_responses: vec![vec![]],
711708
events: vec![vec![]],
712709
blobs: vec![vec![]],
@@ -4185,14 +4182,14 @@ where
41854182

41864183
let value = ConfirmedBlock::new(
41874184
BlockExecutionOutcome {
4188-
messages: vec![vec![]],
4185+
messages: vec![vec![direct_credit_message(chain_2, small_transfer)]],
41894186
previous_message_blocks: BTreeMap::new(),
41904187
previous_event_blocks: BTreeMap::new(),
41914188
events: vec![vec![]],
41924189
blobs: vec![vec![]],
41934190
state_hash: state.crypto_hash_mut().await?,
41944191
oracle_responses: vec![vec![]],
4195-
operation_results: vec![],
4192+
operation_results: vec![OperationResult::default()],
41964193
}
41974194
.with(block),
41984195
);

linera-execution/src/execution.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::{collections::BTreeMap, vec};
55

66
use futures::{FutureExt, StreamExt};
77
use linera_base::{
8+
crypto::CryptoHash,
89
data_types::{BlobContent, BlockHeight, StreamUpdate},
910
identifiers::{AccountOwner, BlobId, StreamId},
1011
time::Instant,
@@ -14,9 +15,10 @@ use linera_views::{
1415
key_value_store_view::KeyValueStoreView,
1516
map_view::MapView,
1617
reentrant_collection_view::HashedReentrantCollectionView,
17-
views::{ClonableView, ReplaceContext, View},
18+
views::{ClonableView, HashableView as _, ReplaceContext, View},
19+
ViewError,
1820
};
19-
use linera_views_derive::CryptoHashView;
21+
use linera_views_derive::HashableView;
2022
#[cfg(with_testing)]
2123
use {
2224
crate::{
@@ -30,14 +32,14 @@ use {
3032
use super::{execution_state_actor::ExecutionRequest, runtime::ServiceRuntimeRequest};
3133
use crate::{
3234
execution_state_actor::ExecutionStateActor, resources::ResourceController,
33-
system::SystemExecutionStateView, ApplicationDescription, ApplicationId, ExecutionError,
34-
ExecutionRuntimeConfig, ExecutionRuntimeContext, MessageContext, OperationContext,
35-
ProcessStreamsContext, Query, QueryContext, QueryOutcome, ServiceSyncRuntime, Timestamp,
36-
TransactionTracker,
35+
system::SystemExecutionStateView, ApplicationDescription, ApplicationId, BcsHashable,
36+
Deserialize, ExecutionError, ExecutionRuntimeConfig, ExecutionRuntimeContext, MessageContext,
37+
OperationContext, ProcessStreamsContext, Query, QueryContext, QueryOutcome, Serialize,
38+
ServiceSyncRuntime, Timestamp, TransactionTracker, FLAG_ZERO_HASH,
3739
};
3840

3941
/// A view accessing the execution state of a chain.
40-
#[derive(Debug, ClonableView, CryptoHashView)]
42+
#[derive(Debug, ClonableView, HashableView)]
4143
pub struct ExecutionStateView<C> {
4244
/// System application.
4345
pub system: SystemExecutionStateView<C>,
@@ -47,6 +49,34 @@ pub struct ExecutionStateView<C> {
4749
pub stream_event_counts: MapView<C, StreamId, u32>,
4850
}
4951

52+
impl<C> ExecutionStateView<C>
53+
where
54+
C: Context + Clone + Send + Sync + 'static,
55+
C::Extra: ExecutionRuntimeContext,
56+
{
57+
pub async fn crypto_hash_mut(&mut self) -> Result<CryptoHash, ViewError> {
58+
if self
59+
.system
60+
.current_committee()
61+
.is_some_and(|(_epoch, committee)| {
62+
committee
63+
.policy()
64+
.http_request_allow_list
65+
.contains(FLAG_ZERO_HASH)
66+
})
67+
{
68+
Ok(CryptoHash::from([0; 32]))
69+
} else {
70+
#[derive(Serialize, Deserialize)]
71+
struct ExecutionStateViewHash([u8; 32]);
72+
impl BcsHashable<'_> for ExecutionStateViewHash {}
73+
self.hash_mut()
74+
.await
75+
.map(|hash| CryptoHash::new(&ExecutionStateViewHash(hash.into())))
76+
}
77+
}
78+
}
79+
5080
impl<C: Context, C2: Context> ReplaceContext<C2> for ExecutionStateView<C> {
5181
type Target = ExecutionStateView<C2>;
5282

linera-execution/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ pub const LINERA_TYPES_SOL: &str = include_str!("../solidity/LineraTypes.sol");
8585
/// The maximum length of a stream name.
8686
const MAX_STREAM_NAME_LEN: usize = 64;
8787

88+
/// The flag that, if present in `http_request_allow_list` field of the content policy of
89+
/// current committee, causes the execution state not to be hashed, and instead the hash
90+
/// returned to be all zeros.
91+
// Note: testnet-only! This should not survive to mainnet.
92+
pub const FLAG_ZERO_HASH: &str = "FLAG_ZERO_HASH.linera.network";
93+
8894
/// An implementation of [`UserContractModule`].
8995
#[derive(Clone)]
9096
pub struct UserContractCode(Box<dyn UserContractModule>);

linera-execution/src/test_utils/system_execution_state.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ use linera_base::{
1313
identifiers::{AccountOwner, ApplicationId, BlobId, ChainId},
1414
ownership::ChainOwnership,
1515
};
16-
use linera_views::{
17-
context::MemoryContext,
18-
views::{CryptoHashView, View},
19-
};
16+
use linera_views::{context::MemoryContext, views::View};
2017

2118
use super::{dummy_chain_description, dummy_committees, MockApplication, RegisterMockApplication};
2219
use crate::{

linera-execution/src/unit_tests/system_tests.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use linera_base::vm::VmRuntime;
77
use linera_views::context::MemoryContext;
88

99
use super::*;
10-
use crate::{test_utils::dummy_chain_description, ExecutionStateView, TestExecutionRuntimeContext};
10+
use crate::{
11+
test_utils::dummy_chain_description, ExecutionStateView, TestExecutionRuntimeContext,
12+
FLAG_ZERO_HASH,
13+
};
1114

1215
/// Returns an execution state view and a matching operation context, for epoch 1, with root
1316
/// chain 0 as the admin ID and one empty committee.
@@ -129,3 +132,32 @@ async fn empty_accounts_are_removed() -> anyhow::Result<()> {
129132

130133
Ok(())
131134
}
135+
136+
#[tokio::test]
137+
async fn hashing_test() -> anyhow::Result<()> {
138+
let (mut view, _) = new_view_and_context().await;
139+
140+
let zero_hash = CryptoHash::from([0u8; 32]);
141+
142+
assert_ne!(view.crypto_hash_mut().await?, zero_hash);
143+
144+
let mut committees = BTreeMap::new();
145+
committees.insert(Epoch(0), Committee::default());
146+
view.system.committees.set(committees.clone());
147+
view.system.epoch.set(Epoch(0));
148+
// The hash should still be nonzero before the threshold epoch.
149+
assert_ne!(view.crypto_hash_mut().await?, zero_hash);
150+
151+
let mut committee = Committee::default();
152+
committee
153+
.policy_mut()
154+
.http_request_allow_list
155+
.insert(FLAG_ZERO_HASH.to_owned());
156+
committees.insert(Epoch(1), committee);
157+
view.system.committees.set(committees);
158+
view.system.epoch.set(Epoch(1));
159+
// Starting from this epoch, the hash should be all zeros.
160+
assert_eq!(view.crypto_hash_mut().await?, zero_hash);
161+
162+
Ok(())
163+
}

linera-service/src/cli_wrappers/wallet.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,17 @@ impl ClientWrapper {
10781078
Ok(())
10791079
}
10801080

1081+
pub async fn change_http_whitelist(&self, list: &[&str]) -> Result<()> {
1082+
self.command()
1083+
.await?
1084+
.arg("resource-control-policy")
1085+
.arg("--http-request-allow-list")
1086+
.arg(list.join(","))
1087+
.spawn_and_wait_for_stdout()
1088+
.await?;
1089+
Ok(())
1090+
}
1091+
10811092
/// Runs `linera keygen`.
10821093
pub async fn keygen(&self) -> Result<AccountOwner> {
10831094
let stdout = self

linera-service/tests/local_net_tests.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use linera_base::{
2222
vm::VmRuntime,
2323
};
2424
use linera_core::{data_types::ChainInfoQuery, node::ValidatorNode};
25+
use linera_execution::FLAG_ZERO_HASH;
2526
use linera_sdk::linera_base_types::AccountSecretKey;
2627
use linera_service::{
2728
cli_wrappers::{
@@ -87,7 +88,7 @@ async fn test_end_to_end_reconfiguration(config: LocalNetConfig) -> Result<()> {
8788
.open_and_assign(&client_2, Amount::from_tokens(3))
8889
.await?;
8990
let port = get_node_port().await;
90-
let node_service_2 = match network {
91+
let mut node_service_2 = match network {
9192
Network::Grpc | Network::Grpcs => {
9293
Some(client_2.run_node_service(port, ProcessInbox::Skip).await?)
9394
}
@@ -237,7 +238,7 @@ async fn test_end_to_end_reconfiguration(config: LocalNetConfig) -> Result<()> {
237238
)
238239
.await?;
239240

240-
if let Some(mut service) = node_service_2 {
241+
if let Some(service) = &mut node_service_2 {
241242
assert!(
242243
eventually(|| async { !service.process_inbox(&chain_2).await.unwrap().is_empty() })
243244
.await
@@ -271,6 +272,54 @@ async fn test_end_to_end_reconfiguration(config: LocalNetConfig) -> Result<()> {
271272
.await?;
272273
}
273274

275+
// Test whether setting the zero hash flag works
276+
client.change_http_whitelist(&[FLAG_ZERO_HASH]).await?;
277+
278+
if let Some(service) = &mut node_service_2 {
279+
assert!(
280+
eventually(|| async { !service.process_inbox(&chain_2).await.unwrap().is_empty() })
281+
.await
282+
);
283+
let committees = service.query_committees(&chain_2).await?;
284+
let epochs = committees.into_keys().collect::<Vec<_>>();
285+
assert_eq!(&epochs, &[Epoch(3), Epoch(4)]);
286+
287+
service.ensure_is_running()?;
288+
} else {
289+
client_2.sync(chain_2).await?;
290+
client_2.process_inbox(chain_2).await?;
291+
}
292+
293+
// Check that we can still produce blocks
294+
let recipient =
295+
AccountOwner::from(AccountSecretKey::Secp256k1(Secp256k1SecretKey::generate()).public());
296+
let account_recipient = Account::new(chain_2, recipient);
297+
client
298+
.transfer_with_accounts(
299+
Amount::from_tokens(5),
300+
Account::chain(chain_1),
301+
account_recipient,
302+
)
303+
.await?;
304+
305+
if let Some(mut service) = node_service_2 {
306+
assert!(
307+
eventually(|| async { !service.process_inbox(&chain_2).await.unwrap().is_empty() })
308+
.await
309+
);
310+
let balance = service.balance(&account_recipient).await?;
311+
assert_eq!(balance, Amount::from_tokens(5));
312+
313+
service.ensure_is_running()?;
314+
} else {
315+
client_2.sync(chain_2).await?;
316+
client_2.process_inbox(chain_2).await?;
317+
assert_eq!(
318+
client_2.local_balance(account_recipient).await?,
319+
Amount::from_tokens(5),
320+
);
321+
}
322+
274323
net.ensure_is_running().await?;
275324
net.terminate().await?;
276325

0 commit comments

Comments
 (0)