Skip to content

Commit dfc8d46

Browse files
afckbart-lineradeuszx
authored
Clients update validators about missing committees. (#4493)
## Motivation If a validator doesn't have the latest committee and receives a block signed by that committee, it will fail with `EventsNotFound`. Also, the validator updater is sending the wrong blocks in some cases for sparse event publisher chains. ## Proposal Handle that in the client and update the validator about the admin chain. Use the correct block heights. Almost all the credit for this goes to @bart-linera! ## Test Plan A test was added that reproduced the issue. ## Release Plan - These changes should be backported to the latest `devnet` branch, then - be released in a new SDK. - These changes should be backported to the latest `testnet` branch, then - be released in a new SDK. ## Links - Closes #4490. - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist) --------- Co-authored-by: Bartłomiej Kamiński <[email protected]> Co-authored-by: deuszx <[email protected]>
1 parent ad2fec6 commit dfc8d46

File tree

7 files changed

+226
-69
lines changed

7 files changed

+226
-69
lines changed

linera-base/src/data_types.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,12 @@ pub enum OracleResponse {
10441044
/// The block's validation round.
10451045
Round(Option<u32>),
10461046
/// An event was read.
1047-
Event(EventId, Vec<u8>),
1047+
Event(
1048+
EventId,
1049+
#[debug(with = "hex_debug")]
1050+
#[serde(with = "serde_bytes")]
1051+
Vec<u8>,
1052+
),
10481053
/// An event exists.
10491054
EventExists(EventId),
10501055
}

linera-chain/src/chain.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,16 @@ where
537537
}
538538
}
539539

540+
/// Returns the height of the highest block we have, plus one. Includes preprocessed blocks.
541+
///
542+
/// The "+ 1" is so that it can be used in the same places as `next_block_height`.
543+
pub async fn next_height_to_preprocess(&self) -> Result<BlockHeight, ChainError> {
544+
if let Some(height) = self.preprocessed_blocks.indices().await?.last() {
545+
return Ok(height.saturating_add(BlockHeight(1)));
546+
}
547+
Ok(self.tip_state.get().next_block_height)
548+
}
549+
540550
pub async fn last_anticipated_block_height(
541551
&self,
542552
origin: &ChainId,

linera-core/src/client/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,6 @@ impl<Env: Environment> Client<Env> {
590590
height: BlockHeight,
591591
delivery: CrossChainMessageDelivery,
592592
) -> Result<(), ChainClientError> {
593-
let local_node = self.local_node.clone();
594593
let nodes = self.make_nodes(committee)?;
595594
communicate_with_quorum(
596595
&nodes,
@@ -599,7 +598,8 @@ impl<Env: Environment> Client<Env> {
599598
|remote_node| {
600599
let mut updater = ValidatorUpdater {
601600
remote_node,
602-
local_node: local_node.clone(),
601+
local_node: self.local_node.clone(),
602+
admin_id: self.admin_id,
603603
};
604604
Box::pin(async move {
605605
updater
@@ -625,7 +625,6 @@ impl<Env: Environment> Client<Env> {
625625
action: CommunicateAction,
626626
value: T,
627627
) -> Result<GenericCertificate<T>, ChainClientError> {
628-
let local_node = self.local_node.clone();
629628
let nodes = self.make_nodes(committee)?;
630629
let ((votes_hash, votes_round), votes) = communicate_with_quorum(
631630
&nodes,
@@ -634,7 +633,8 @@ impl<Env: Environment> Client<Env> {
634633
|remote_node| {
635634
let mut updater = ValidatorUpdater {
636635
remote_node,
637-
local_node: local_node.clone(),
636+
local_node: self.local_node.clone(),
637+
admin_id: self.admin_id,
638638
};
639639
let action = action.clone();
640640
Box::pin(async move { updater.send_chain_update(action).await })

linera-core/src/local_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ where
155155
Ok(storage.read_blobs(blob_ids).await?.into_iter().collect())
156156
}
157157

158-
/// Reads blob states from storage
158+
/// Reads blob states from storage.
159159
pub async fn read_blob_states_from_storage(
160160
&self,
161161
blob_ids: &[BlobId],

linera-core/src/unit_tests/client_tests.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,3 +2675,109 @@ where
26752675
assert_eq!(client.local_balance().await.unwrap(), expected_balance);
26762676
Ok(())
26772677
}
2678+
2679+
#[test_case(MemoryStorageBuilder::default(); "memory")]
2680+
#[cfg_attr(feature = "storage-service", test_case(ServiceStorageBuilder::new().await; "storage_service"))]
2681+
#[cfg_attr(feature = "rocksdb", test_case(RocksDbStorageBuilder::new().await; "rocks_db"))]
2682+
#[cfg_attr(feature = "dynamodb", test_case(DynamoDbStorageBuilder::default(); "dynamo_db"))]
2683+
#[cfg_attr(feature = "scylladb", test_case(ScyllaDbStorageBuilder::default(); "scylla_db"))]
2684+
#[test_log::test(tokio::test)]
2685+
async fn test_validator_outdated_admin_chain<B>(storage_builder: B) -> anyhow::Result<()>
2686+
where
2687+
B: StorageBuilder,
2688+
{
2689+
let mut signer = InMemorySigner::new(None);
2690+
let new_public_key = signer.generate_new();
2691+
let mut builder = TestBuilder::new(storage_builder, 4, 0, signer).await?;
2692+
2693+
let admin_client = builder.add_root_chain(0, Amount::from_tokens(1000)).await?;
2694+
let client1 = builder.add_root_chain(1, Amount::from_tokens(1000)).await?;
2695+
2696+
// Take one validator down - they will miss committee changes.
2697+
builder.set_fault_type([3], FaultType::Offline);
2698+
2699+
// Start by creating a block in epoch 0.
2700+
let certificate0 = client1
2701+
.transfer(
2702+
AccountOwner::CHAIN,
2703+
Amount::ONE,
2704+
Account::chain(admin_client.chain_id()),
2705+
)
2706+
.await
2707+
.unwrap_ok_committed();
2708+
2709+
assert_eq!(certificate0.block().header.epoch, Epoch::from(0));
2710+
2711+
// Advance the epoch.
2712+
admin_client
2713+
.stage_new_committee(builder.initial_committee.clone())
2714+
.await
2715+
.unwrap();
2716+
2717+
// Process the inbox to migrate the client's chain.
2718+
client1.synchronize_from_validators().await.unwrap();
2719+
client1.process_inbox().await.unwrap();
2720+
2721+
// Open a chain
2722+
let (new_chain_desc, certificate1) = client1
2723+
.open_chain(
2724+
ChainOwnership::single(new_public_key.into()),
2725+
ApplicationPermissions::default(),
2726+
Amount::from_tokens(10),
2727+
)
2728+
.await
2729+
.unwrap_ok_committed();
2730+
2731+
// Check that the epoch has been migrated.
2732+
assert_eq!(certificate1.block().header.epoch, Epoch::from(1));
2733+
2734+
// Make a client to try the new chain.
2735+
let mut client2 = builder
2736+
.make_client(new_chain_desc.id(), None, BlockHeight::ZERO)
2737+
.await?;
2738+
client2.set_preferred_owner(new_public_key.into());
2739+
client2.synchronize_from_validators().await.unwrap();
2740+
client2
2741+
.receive_certificate_and_update_validators(certificate1)
2742+
.await
2743+
.unwrap();
2744+
2745+
// Let's deactivate another validator and reactivate the one that was offline.
2746+
// Now the client will have to update validator 3 on the admin chain in order for the
2747+
// next blocks to be correctly processed.
2748+
builder.set_fault_type([2], FaultType::Offline);
2749+
builder.set_fault_type([3], FaultType::Honest);
2750+
2751+
let admin_tip = builder
2752+
.node(3)
2753+
.chain_info_with_manager_values(admin_client.chain_id())
2754+
.await
2755+
.unwrap()
2756+
.next_block_height;
2757+
// At this point, validator 3 should have zero blocks on the admin chain.
2758+
assert_eq!(admin_tip, 0.into());
2759+
2760+
// Update the validators on the chain.
2761+
// If it works, it means the validator has been correctly updated.
2762+
client2.update_validators(None).await.unwrap();
2763+
2764+
client2
2765+
.transfer(
2766+
AccountOwner::CHAIN,
2767+
Amount::from_tokens(3),
2768+
Account::chain(admin_client.chain_id()),
2769+
)
2770+
.await
2771+
.unwrap_ok_committed();
2772+
2773+
let admin_tip = builder
2774+
.node(3)
2775+
.chain_info_with_manager_values(admin_client.chain_id())
2776+
.await
2777+
.unwrap()
2778+
.next_block_height;
2779+
// Validator 3 should be up to date on the admin chain.
2780+
assert_eq!(admin_tip, 2.into());
2781+
2782+
Ok(())
2783+
}

0 commit comments

Comments
 (0)