Skip to content

Commit

Permalink
Add a create_application contract API method (#3111)
Browse files Browse the repository at this point in the history
## Motivation

In some applications, we might want to instantiate other application as
part of their functionality. For example, the "Requests for Quotes" app
will want to instantiate the Matching Engine.

## Proposal

Add a `create_application` method to the contract API, callable from
within applications, reflecting the flow of manually instantiating a new
application.

## Test Plan

This will be tested during test runs of the Requests for Quotes
application.

## Release Plan

- Nothing to do / These changes follow the usual release cycle.

## Links

- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
  • Loading branch information
bart-linera authored Jan 10, 2025
1 parent 1c8a710 commit 6485f3d
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 42 deletions.
32 changes: 30 additions & 2 deletions linera-execution/src/execution_state_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ use prometheus::HistogramVec;
use reqwest::{header::CONTENT_TYPE, Client};

use crate::{
system::{OpenChainConfig, Recipient},
system::{CreateApplicationResult, OpenChainConfig, Recipient},
util::RespondExt,
ExecutionError, ExecutionRuntimeContext, ExecutionStateView, RawExecutionOutcome,
BytecodeId, ExecutionError, ExecutionRuntimeContext, ExecutionStateView, RawExecutionOutcome,
RawOutgoingMessage, SystemExecutionError, SystemMessage, UserApplicationDescription,
UserApplicationId, UserContractCode, UserServiceCode,
};
Expand Down Expand Up @@ -289,6 +289,25 @@ where
}
}

CreateApplication {
next_message_id,
bytecode_id,
parameters,
required_application_ids,
callback,
} => {
let create_application_result = self
.system
.create_application(
next_message_id,
bytecode_id,
parameters,
required_application_ids,
)
.await?;
callback.respond(Ok(create_application_result));
}

FetchUrl { url, callback } => {
let bytes = reqwest::get(url).await?.bytes().await?.to_vec();
callback.respond(bytes);
Expand Down Expand Up @@ -467,6 +486,15 @@ pub enum ExecutionRequest {
callback: oneshot::Sender<Result<(), ExecutionError>>,
},

CreateApplication {
next_message_id: MessageId,
bytecode_id: BytecodeId,
parameters: Vec<u8>,
required_application_ids: Vec<UserApplicationId>,
#[debug(skip)]
callback: oneshot::Sender<Result<CreateApplicationResult, ExecutionError>>,
},

FetchUrl {
url: String,
#[debug(skip)]
Expand Down
9 changes: 9 additions & 0 deletions linera-execution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,15 @@ pub trait ContractRuntime: BaseRuntime {
/// Closes the current chain.
fn close_chain(&mut self) -> Result<(), ExecutionError>;

/// Creates a new application on chain.
fn create_application(
&mut self,
bytecode_id: BytecodeId,
parameters: Vec<u8>,
argument: Vec<u8>,
required_application_ids: Vec<UserApplicationId>,
) -> Result<UserApplicationId, ExecutionError>;

/// Writes a batch of changes.
fn write_batch(&mut self, batch: Batch) -> Result<(), ExecutionError>;
}
Expand Down
75 changes: 70 additions & 5 deletions linera-execution/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ use crate::{
execution::UserAction,
execution_state_actor::{ExecutionRequest, ExecutionStateSender},
resources::ResourceController,
system::CreateApplicationResult,
util::{ReceiverExt, UnboundedSenderExt},
BaseRuntime, ContractRuntime, ExecutionError, FinalizeContext, MessageContext,
BaseRuntime, BytecodeId, ContractRuntime, ExecutionError, FinalizeContext, MessageContext,
OperationContext, QueryContext, RawExecutionOutcome, ServiceRuntime, TransactionTracker,
UserApplicationDescription, UserApplicationId, UserContractCode, UserContractInstance,
UserServiceCode, UserServiceInstance, MAX_EVENT_KEY_LEN, MAX_STREAM_NAME_LEN,
Expand Down Expand Up @@ -1115,6 +1116,22 @@ impl ContractSyncRuntime {
chain_id: ChainId,
action: UserAction,
) -> Result<(ResourceController, TransactionTracker), ExecutionError> {
self.deref_mut()
.run_action(application_id, chain_id, action)?;
let runtime = self
.into_inner()
.expect("Runtime clones should have been freed by now");
Ok((runtime.resource_controller, runtime.transaction_tracker))
}
}

impl ContractSyncRuntimeHandle {
fn run_action(
&mut self,
application_id: UserApplicationId,
chain_id: ChainId,
action: UserAction,
) -> Result<(), ExecutionError> {
let finalize_context = FinalizeContext {
authenticated_signer: action.signer(),
chain_id,
Expand All @@ -1135,10 +1152,7 @@ impl ContractSyncRuntime {
UserAction::Message(context, message) => code.execute_message(context, message),
})?;
self.finalize(finalize_context)?;
let runtime = self
.into_inner()
.expect("Runtime clones should have been freed by now");
Ok((runtime.resource_controller, runtime.transaction_tracker))
Ok(())
}

/// Notifies all loaded applications that execution is finalizing.
Expand Down Expand Up @@ -1401,6 +1415,57 @@ impl ContractRuntime for ContractSyncRuntimeHandle {
.recv_response()?
}

fn create_application(
&mut self,
bytecode_id: BytecodeId,
parameters: Vec<u8>,
argument: Vec<u8>,
required_application_ids: Vec<UserApplicationId>,
) -> Result<UserApplicationId, ExecutionError> {
let chain_id = self.inner().chain_id;
let context = OperationContext {
chain_id,
authenticated_signer: self.authenticated_signer()?,
authenticated_caller_id: self.authenticated_caller_id()?,
height: self.block_height()?,
index: None,
};

let mut this = self.inner();
let message_id = MessageId {
chain_id: context.chain_id,
height: context.height,
index: this.transaction_tracker.next_message_index(),
};
let CreateApplicationResult {
app_id,
message,
blobs_to_register,
} = this
.execution_state_sender
.send_request(|callback| ExecutionRequest::CreateApplication {
next_message_id: message_id,
bytecode_id,
parameters,
required_application_ids,
callback,
})?
.recv_response()??;
for blob_id in blobs_to_register {
this.transaction_tracker
.replay_oracle_response(OracleResponse::Blob(blob_id))?;
}
let outcome = RawExecutionOutcome::default().with_message(message);
this.transaction_tracker.add_system_outcome(outcome)?;

drop(this);

let user_action = UserAction::Instantiate(context, argument);
self.run_action(app_id, chain_id, user_action)?;

Ok(app_id)
}

fn write_batch(&mut self, batch: Batch) -> Result<(), ExecutionError> {
let mut this = self.inner();
let id = this.application_id()?;
Expand Down
121 changes: 92 additions & 29 deletions linera-execution/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,13 @@ impl UserData {
}
}

#[derive(Clone, Debug)]
pub struct CreateApplicationResult {
pub app_id: UserApplicationId,
pub message: RawOutgoingMessage<SystemMessage, Amount>,
pub blobs_to_register: Vec<BlobId>,
}

#[derive(Error, Debug)]
pub enum SystemExecutionError {
#[error(transparent)]
Expand Down Expand Up @@ -634,31 +641,23 @@ where
instantiation_argument,
required_application_ids,
} => {
let id = UserApplicationId {
bytecode_id,
creation: context.next_message_id(txn_tracker.next_message_index()),
};
for application in required_application_ids.iter().chain(iter::once(&id)) {
self.check_and_record_bytecode_blobs(&application.bytecode_id, txn_tracker)
.await?;
}
self.registry
.register_new_application(
id,
parameters.clone(),
required_application_ids.clone(),
let next_message_id = context.next_message_id(txn_tracker.next_message_index());
let CreateApplicationResult {
app_id,
message,
blobs_to_register,
} = self
.create_application(
next_message_id,
bytecode_id,
parameters,
required_application_ids,
)
.await?;
// Send a message to ourself to increment the message ID.
let message = RawOutgoingMessage {
destination: Destination::Recipient(context.chain_id),
authenticated: false,
grant: Amount::ZERO,
kind: MessageKind::Protected,
message: SystemMessage::ApplicationCreated,
};
self.record_bytecode_blobs(blobs_to_register, txn_tracker)
.await?;
outcome.messages.push(message);
new_application = Some((id, instantiation_argument.clone()));
new_application = Some((app_id, instantiation_argument.clone()));
}
RequestApplication {
chain_id,
Expand Down Expand Up @@ -1024,6 +1023,49 @@ where
Ok(messages)
}

pub async fn create_application(
&mut self,
next_message_id: MessageId,
bytecode_id: BytecodeId,
parameters: Vec<u8>,
required_application_ids: Vec<UserApplicationId>,
) -> Result<CreateApplicationResult, SystemExecutionError> {
let id = UserApplicationId {
bytecode_id,
creation: next_message_id,
};
let mut blobs_to_register = vec![];
for application in required_application_ids.iter().chain(iter::once(&id)) {
let (contract_bytecode_blob_id, service_bytecode_blob_id) =
self.check_bytecode_blobs(&application.bytecode_id).await?;
// We only remember to register the blobs that aren't recorded in `used_blobs`
// already.
if !self.used_blobs.contains(&contract_bytecode_blob_id).await? {
blobs_to_register.push(contract_bytecode_blob_id);
}
if !self.used_blobs.contains(&service_bytecode_blob_id).await? {
blobs_to_register.push(service_bytecode_blob_id);
}
}
self.registry
.register_new_application(id, parameters.clone(), required_application_ids.clone())
.await?;
// Send a message to ourself to increment the message ID.
let message = RawOutgoingMessage {
destination: Destination::Recipient(next_message_id.chain_id),
authenticated: false,
grant: Amount::ZERO,
kind: MessageKind::Protected,
message: SystemMessage::ApplicationCreated,
};

Ok(CreateApplicationResult {
app_id: id,
message,
blobs_to_register,
})
}

/// Records a blob that is used in this block. If this is the first use on this chain, creates
/// an oracle response for it.
pub(crate) async fn blob_used(
Expand Down Expand Up @@ -1072,11 +1114,10 @@ where
}
}

async fn check_and_record_bytecode_blobs(
async fn check_bytecode_blobs(
&mut self,
bytecode_id: &BytecodeId,
txn_tracker: &mut TransactionTracker,
) -> Result<(), SystemExecutionError> {
) -> Result<(BlobId, BlobId), SystemExecutionError> {
let contract_bytecode_blob_id =
BlobId::new(bytecode_id.contract_blob_hash, BlobType::ContractBytecode);

Expand Down Expand Up @@ -1105,10 +1146,32 @@ where
missing_blobs.is_empty(),
SystemExecutionError::BlobsNotFound(missing_blobs)
);
self.blob_used(Some(txn_tracker), contract_bytecode_blob_id)
.await?;
self.blob_used(Some(txn_tracker), service_bytecode_blob_id)
.await?;

Ok((contract_bytecode_blob_id, service_bytecode_blob_id))
}

async fn record_bytecode_blobs(
&mut self,
blob_ids: Vec<BlobId>,
txn_tracker: &mut TransactionTracker,
) -> Result<(), SystemExecutionError> {
for blob_id in blob_ids {
self.blob_used(Some(txn_tracker), blob_id).await?;
}
Ok(())
}

async fn check_and_record_bytecode_blobs(
&mut self,
bytecode_id: &BytecodeId,
txn_tracker: &mut TransactionTracker,
) -> Result<(), SystemExecutionError> {
let (contract_bytecode_blob_id, service_bytecode_blob_id) =
self.check_bytecode_blobs(bytecode_id).await?;
self.record_bytecode_blobs(
vec![contract_bytecode_blob_id, service_bytecode_blob_id],
txn_tracker,
)
.await
}
}
20 changes: 18 additions & 2 deletions linera-execution/src/wasm/system_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use tracing::log;

use super::WasmExecutionError;
use crate::{
BaseRuntime, ContractRuntime, ContractSyncRuntimeHandle, ExecutionError, ServiceRuntime,
ServiceSyncRuntimeHandle,
BaseRuntime, BytecodeId, ContractRuntime, ContractSyncRuntimeHandle, ExecutionError,
ServiceRuntime, ServiceSyncRuntimeHandle,
};

/// Common host data used as the `UserData` of the system API implementations.
Expand Down Expand Up @@ -301,6 +301,22 @@ where
}
}

/// Creates a new application on the chain, based on the supplied bytecode and
/// parameters.
fn create_application(
caller: &mut Caller,
bytecode_id: BytecodeId,
parameters: Vec<u8>,
argument: Vec<u8>,
required_application_ids: Vec<ApplicationId>,
) -> Result<ApplicationId, RuntimeError> {
caller
.user_data_mut()
.runtime
.create_application(bytecode_id, parameters, argument, required_application_ids)
.map_err(|error| RuntimeError::Custom(error.into()))
}

/// Calls another application.
fn try_call_application(
caller: &mut Caller,
Expand Down
Loading

0 comments on commit 6485f3d

Please sign in to comment.