Skip to content

Commit

Permalink
Remove the OracleRecord and OracleResponses types; never throw away r…
Browse files Browse the repository at this point in the history
…esponses. (#2232)

* Remove the OracleRecord type.

* Don't throw away returned oracle responses.

* Remove the OracleResponses type.

* Ensure matching block execution outcomes, including oracle responses.

* Fix tests; add an oracle entry also for rejected messages.
  • Loading branch information
afck committed Jul 12, 2024
1 parent ab05e6b commit 5832ae6
Show file tree
Hide file tree
Showing 26 changed files with 278 additions and 347 deletions.
18 changes: 1 addition & 17 deletions linera-base/src/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use std::fmt;

use anyhow::Context as _;
use async_graphql::{InputObject, SimpleObject};
use async_graphql::InputObject;
use base64::engine::{general_purpose::STANDARD_NO_PAD, Engine as _};
use linera_witty::{WitLoad, WitStore, WitType};
use serde::{Deserialize, Deserializer, Serialize};
Expand Down Expand Up @@ -711,22 +711,6 @@ impl ApplicationPermissions {
}
}

/// A record of oracle responses from the execution of a transaction.
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct OracleRecord {
/// The list of responses to all the oracle queries made by a transaction.
pub responses: Vec<OracleResponse>,
}

impl OracleRecord {
/// Wether an `OracleRecord` is permitted in fast blocks or not.
pub fn is_permitted_in_fast_blocks(&self) -> bool {
self.responses
.iter()
.all(|oracle_response| oracle_response.is_permitted_in_fast_blocks())
}
}

/// A record of a single oracle response.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum OracleResponse {
Expand Down
82 changes: 41 additions & 41 deletions linera-chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use async_graphql::SimpleObject;
use futures::stream::{self, StreamExt, TryStreamExt};
use linera_base::{
crypto::CryptoHash,
data_types::{Amount, ArithmeticError, BlockHeight, OracleRecord, Timestamp},
data_types::{Amount, ArithmeticError, BlockHeight, OracleResponse, Timestamp},
ensure,
identifiers::{ChainId, Destination, GenericApplicationId, MessageId},
};
Expand Down Expand Up @@ -720,7 +720,7 @@ where
&mut self,
block: &Block,
local_time: Timestamp,
oracle_records: Option<Vec<OracleRecord>>,
oracle_responses: Option<Vec<Vec<OracleResponse>>>,
) -> Result<BlockExecutionOutcome, ChainError> {
#[cfg(with_metrics)]
let _execution_latency = BLOCK_EXECUTION_LATENCY.measure_latency();
Expand Down Expand Up @@ -801,8 +801,8 @@ where
mandatory.is_empty(),
ChainError::MissingMandatoryApplications(mandatory.into_iter().collect())
);
let mut oracle_records = oracle_records.map(Vec::into_iter);
let mut new_oracle_records = Vec::new();
let mut oracle_responses = oracle_responses.map(Vec::into_iter);
let mut new_oracle_responses = Vec::new();
let mut next_message_index = 0;
for (index, message) in block.incoming_messages.iter().enumerate() {
#[cfg(with_metrics)]
Expand All @@ -824,29 +824,28 @@ where
refund_grant_to: message.event.refund_grant_to,
next_message_index,
};
let outcomes = match message.action {
let (outcomes, oracle_responses) = match message.action {
MessageAction::Accept => {
let mut grant = message.event.grant;
let (mut outcomes, oracle_record) = self
let (mut outcomes, oracle_responses) = self
.execution_state
.execute_message(
context,
local_time,
message.event.message.clone(),
(grant > Amount::ZERO).then_some(&mut grant),
match &mut oracle_records {
Some(records) => Some(
records
match &mut oracle_responses {
Some(responses) => Some(
responses
.next()
.ok_or_else(|| ChainError::MissingOracleRecord)?,
.ok_or_else(|| ChainError::MissingOracleResponseList)?,
),
None => None,
},
&mut resource_controller,
)
.await
.map_err(|err| ChainError::ExecutionError(err, chain_execution_context))?;
new_oracle_records.push(oracle_record);
if grant > Amount::ZERO {
if let Some(refund_grant_to) = message.event.refund_grant_to {
let outcome = self
Expand All @@ -859,7 +858,7 @@ where
outcomes.push(outcome);
}
}
outcomes
(outcomes, oracle_responses)
}
MessageAction::Reject => {
// If rejecting a message fails, the entire block proposal should be
Expand All @@ -873,7 +872,7 @@ where
event: message.event.clone(),
}
);
if message.event.is_tracked() {
let outcomes = if message.event.is_tracked() {
// Bounce the message.
self.execution_state
.bounce_message(
Expand All @@ -886,30 +885,31 @@ where
.map_err(|err| {
ChainError::ExecutionError(err, chain_execution_context)
})?
} else {
} else if message.event.grant > Amount::ZERO {
// Nothing to do except maybe refund the grant.
let mut outcomes = Vec::new();
if message.event.grant > Amount::ZERO {
let Some(refund_grant_to) = message.event.refund_grant_to else {
// See OperationContext::refund_grant_to()
return Err(ChainError::InternalError(
"Messages with grants should have a non-empty `refund_grant_to`".into()
));
};
// Refund grant.
let outcome = self
.execution_state
.send_refund(context, message.event.grant, refund_grant_to)
.await
.map_err(|err| {
ChainError::ExecutionError(err, chain_execution_context)
})?;
outcomes.push(outcome);
}
outcomes
}
let Some(refund_grant_to) = message.event.refund_grant_to else {
// See OperationContext::refund_grant_to()
return Err(ChainError::InternalError(
"Messages with grants should have a non-empty `refund_grant_to`"
.into(),
));
};
// Refund grant.
let outcome = self
.execution_state
.send_refund(context, message.event.grant, refund_grant_to)
.await
.map_err(|err| {
ChainError::ExecutionError(err, chain_execution_context)
})?;
vec![outcome]
} else {
Vec::new()
};
(outcomes, Vec::new())
}
};
new_oracle_responses.push(oracle_responses);
let messages_out = self
.process_execution_outcomes(context.height, outcomes)
.await?;
Expand Down Expand Up @@ -940,25 +940,25 @@ where
authenticated_caller_id: None,
next_message_index,
};
let (outcomes, oracle_record) = self
let (outcomes, oracle_responses) = self
.execution_state
.execute_operation(
context,
local_time,
operation.clone(),
match &mut oracle_records {
Some(records) => Some(
records
match &mut oracle_responses {
Some(responses) => Some(
responses
.next()
.ok_or_else(|| ChainError::MissingOracleRecord)?,
.ok_or_else(|| ChainError::MissingOracleResponseList)?,
),
None => None,
},
&mut resource_controller,
)
.await
.map_err(|err| ChainError::ExecutionError(err, chain_execution_context))?;
new_oracle_records.push(oracle_record);
new_oracle_responses.push(oracle_responses);
let messages_out = self
.process_execution_outcomes(context.height, outcomes)
.await?;
Expand Down Expand Up @@ -1030,7 +1030,7 @@ where
Ok(BlockExecutionOutcome {
messages,
state_hash,
oracle_records: new_oracle_records,
oracle_responses: new_oracle_responses,
})
}

Expand Down
16 changes: 8 additions & 8 deletions linera-chain/src/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{borrow::Cow, collections::HashSet};
use async_graphql::SimpleObject;
use linera_base::{
crypto::{BcsHashable, BcsSignable, CryptoError, CryptoHash, KeyPair, PublicKey, Signature},
data_types::{Amount, BlockHeight, HashedBlob, OracleRecord, OracleResponse, Round, Timestamp},
data_types::{Amount, BlockHeight, HashedBlob, OracleResponse, Round, Timestamp},
doc_scalar, ensure,
identifiers::{
Account, BlobId, ChainId, ChannelName, Destination, GenericApplicationId, MessageId, Owner,
Expand Down Expand Up @@ -274,7 +274,7 @@ pub struct BlockExecutionOutcome {
pub messages: Vec<Vec<OutgoingMessage>>,
pub state_hash: CryptoHash,
/// The record of oracle responses for each transaction.
pub oracle_records: Vec<OracleRecord>,
pub oracle_responses: Vec<Vec<OracleResponse>>,
}

/// A statement to be certified by the validators.
Expand Down Expand Up @@ -747,8 +747,8 @@ impl ExecutedBlock {

pub fn required_blob_ids(&self) -> HashSet<BlobId> {
let mut required_blob_ids = HashSet::new();
for record in &self.outcome.oracle_records {
for response in &record.responses {
for responses in &self.outcome.oracle_responses {
for response in responses {
if let OracleResponse::Blob(blob_id) = response {
required_blob_ids.insert(*blob_id);
}
Expand Down Expand Up @@ -833,9 +833,9 @@ pub struct ProposalContent {
pub block: Block,
/// The consensus round in which this proposal is made.
pub round: Round,
/// If this is a retry from an earlier round, the oracle records from when the block was
/// If this is a retry from an earlier round, the oracle responses from when the block was
/// first validated. These are reused so the execution outcome remains the same.
pub forced_oracle_records: Option<Vec<OracleRecord>>,
pub forced_oracle_responses: Option<Vec<Vec<OracleResponse>>>,
}

impl BlockProposal {
Expand All @@ -849,7 +849,7 @@ impl BlockProposal {
let content = ProposalContent {
round,
block,
forced_oracle_records: None,
forced_oracle_responses: None,
};
let signature = Signature::new(&content, secret);
Self {
Expand Down Expand Up @@ -878,7 +878,7 @@ impl BlockProposal {
let content = ProposalContent {
block: executed_block.block,
round,
forced_oracle_records: Some(executed_block.outcome.oracle_records),
forced_oracle_responses: Some(executed_block.outcome.oracle_responses),
};
let signature = Signature::new(&content, secret);
Self {
Expand Down
2 changes: 1 addition & 1 deletion linera-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ pub enum ChainError {
#[error("Can't use grant across different broadcast messages")]
GrantUseOnBroadcast,
#[error("ExecutedBlock contains fewer oracle responses than requests")]
MissingOracleRecord,
MissingOracleResponseList,
#[error("Unexpected hash for CertificateValue! Expected: {expected:?}, Actual: {actual:?}")]
CertificateValueHashMismatch {
expected: CryptoHash,
Expand Down
4 changes: 2 additions & 2 deletions linera-chain/src/unit_tests/data_types_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn test_signed_values() {
let executed_block = BlockExecutionOutcome {
messages: vec![Vec::new()],
state_hash: CryptoHash::test_hash("state"),
oracle_records: vec![OracleRecord::default()],
oracle_responses: vec![Vec::new()],
}
.with(block);
let value = HashedCertificateValue::new_confirmed(executed_block);
Expand Down Expand Up @@ -46,7 +46,7 @@ fn test_certificates() {
let executed_block = BlockExecutionOutcome {
messages: vec![Vec::new()],
state_hash: CryptoHash::test_hash("state"),
oracle_records: vec![OracleRecord::default()],
oracle_responses: vec![Vec::new()],
}
.with(block);
let value = HashedCertificateValue::new_confirmed(executed_block);
Expand Down
34 changes: 13 additions & 21 deletions linera-core/src/chain_worker/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ where
ProposalContent {
block,
round,
forced_oracle_records,
forced_oracle_responses,
},
owner,
hashed_certificate_values,
Expand All @@ -648,10 +648,10 @@ where
signature: _,
} = proposal;
ensure!(
validated_block_certificate.is_some() == forced_oracle_records.is_some(),
validated_block_certificate.is_some() == forced_oracle_responses.is_some(),
WorkerError::InvalidBlockProposal(
"Must contain a validation certificate if and only if \
oracle records are forced from a previous round"
oracle responses are forced from a previous round"
.to_string()
)
);
Expand Down Expand Up @@ -717,7 +717,7 @@ where
let outcome = Box::pin(self.0.chain.execute_block(
block,
local_time,
forced_oracle_records.clone(),
forced_oracle_responses.clone(),
))
.await?;
if let Some(lite_certificate) = &validated_block_certificate {
Expand All @@ -730,9 +730,10 @@ where
if round.is_fast() {
ensure!(
outcome
.oracle_records
.oracle_responses
.iter()
.all(|record| record.is_permitted_in_fast_blocks()),
.flatten()
.all(|response| response.is_permitted_in_fast_blocks()),
WorkerError::FastBlockUsingOracles
);
}
Expand Down Expand Up @@ -1027,11 +1028,6 @@ where
panic!("Expecting a confirmation certificate");
};
let block = &executed_block.block;
let BlockExecutionOutcome {
messages,
state_hash,
oracle_records,
} = &executed_block.outcome;
// Check that the chain is active and ready for this confirmation.
let tip = self.state.chain.tip_state.get().clone();
if tip.next_block_height < block.height {
Expand Down Expand Up @@ -1134,28 +1130,24 @@ where
let verified_outcome = Box::pin(self.state.chain.execute_block(
block,
local_time,
Some(oracle_records.clone()),
Some(executed_block.outcome.oracle_responses.clone()),
))
.await?;
// We should always agree on the messages and state hash.
ensure!(
*messages == verified_outcome.messages,
WorkerError::IncorrectMessages {
computed: verified_outcome.messages,
submitted: messages.clone(),
executed_block.outcome == verified_outcome,
WorkerError::IncorrectOutcome {
submitted: executed_block.outcome.clone(),
computed: verified_outcome,
}
);
ensure!(
*state_hash == verified_outcome.state_hash,
WorkerError::IncorrectStateHash
);
// Advance to next block height.
let tip = self.state.chain.tip_state.get_mut();
tip.block_hash = Some(certificate.hash());
tip.next_block_height.try_add_assign_one()?;
tip.num_incoming_messages += block.incoming_messages.len() as u32;
tip.num_operations += block.operations.len() as u32;
tip.num_outgoing_messages += messages.len() as u32;
tip.num_outgoing_messages += executed_block.outcome.messages.len() as u32;
self.state.chain.confirmed_log.push(certificate.hash());
let info = ChainInfoResponse::new(&self.state.chain, self.state.config.key_pair());
let mut actions = self.state.create_network_actions().await?;
Expand Down
6 changes: 2 additions & 4 deletions linera-core/src/unit_tests/client_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1450,11 +1450,9 @@ where
.executed_block()
.unwrap()
.outcome
.oracle_records
.oracle_responses
.iter()
.any(|record| record
.responses
.contains(&OracleResponse::Blob(expected_blob_id))));
.any(|responses| responses.contains(&OracleResponse::Blob(expected_blob_id))));
let previous_block_hash = client_a.block_hash().unwrap();

// Validators goes back up
Expand Down
Loading

0 comments on commit 5832ae6

Please sign in to comment.