Skip to content

Commit aea7ab0

Browse files
committed
runtime-sdk: Add support for incoming messages
1 parent d92bbaa commit aea7ab0

24 files changed

Lines changed: 898 additions & 23 deletions

File tree

client-sdk/go/modules/core/types.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ type GasCosts struct {
2525
AuthSignature uint64 `json:"auth_signature"`
2626
AuthMultisigSigner uint64 `json:"auth_multisig_signer"`
2727
CallformatX25519Deoxysii uint64 `json:"callformat_x25519_deoxysii"`
28+
29+
// Fields below have omitempty set for backwards compatibility. Once there are no deployed
30+
// runtimes using an old version of the SDK, this should be removed.
31+
32+
InMsgBase uint64 `json:"inmsg_base,omitempty"`
2833
}
2934

3035
// Parameters are the parameters for the consensus accounts module.
@@ -38,7 +43,8 @@ type Parameters struct {
3843
// Fields below have omitempty set for backwards compatibility. Once there are no deployed
3944
// runtimes using an old version of the SDK, this should be removed.
4045

41-
MaxTxSize uint32 `json:"max_tx_size,omitempty"`
46+
MaxTxSize uint32 `json:"max_tx_size,omitempty"`
47+
MaxInMsgGas uint32 `json:"max_inmsg_gas,omitempty"`
4248
}
4349

4450
// ModuleName is the core module name.

runtime-sdk/src/dispatcher.rs

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::{
3131
error::{Error as _, RuntimeError},
3232
event::IntoTags,
3333
keymanager::{KeyManagerClient, KeyManagerError},
34-
module::{self, BlockHandler, MethodHandler, TransactionHandler},
34+
module::{self, BlockHandler, InMsgHandler, InMsgResult, MethodHandler, TransactionHandler},
3535
modules,
3636
modules::core::API as _,
3737
runtime::Runtime,
@@ -582,7 +582,7 @@ impl<R: Runtime> Dispatcher<R> {
582582
messages,
583583
block_tags,
584584
tx_reject_hashes: vec![],
585-
in_msgs_count: 0, // TODO: Support processing incoming messages.
585+
in_msgs_count: 0,
586586
})
587587
})
588588
}
@@ -593,17 +593,63 @@ impl<R: Runtime + Send + Sync> transaction::dispatcher::Dispatcher for Dispatche
593593
&self,
594594
rt_ctx: transaction::Context<'_>,
595595
batch: &TxnBatch,
596-
_in_msgs: &[roothash::IncomingMessage],
596+
in_msgs: &[roothash::IncomingMessage],
597597
) -> Result<ExecuteBatchResult, RuntimeError> {
598-
self.execute_batch_common(
598+
let mut in_msgs_count = 0;
599+
600+
let mut result = self.execute_batch_common(
599601
rt_ctx,
600602
|ctx| -> Result<Vec<ExecuteTxResult>, RuntimeError> {
601603
// If prefetch limit is set enable prefetch.
602604
let prefetch_enabled = R::PREFETCH_LIMIT > 0;
605+
let mut results = Vec::with_capacity(batch.len());
606+
607+
// Process incoming messages first.
608+
let mut batch_it = batch.iter();
609+
'inmsg: for in_msg in in_msgs {
610+
match R::IncomingMessagesHandler::process_in_msg(ctx, &in_msg) {
611+
InMsgResult::Skip => {
612+
// Skip, but treat as processed.
613+
in_msgs_count += 1;
614+
}
615+
InMsgResult::Execute(raw_tx, tx) => {
616+
// Verify that the transaction has been included in the batch.
617+
match batch_it.next() {
618+
None => {
619+
// Nothing in the batch when there should be an incoming message.
620+
return Err(Error::MalformedTransactionInBatch(anyhow!(
621+
"missing incoming message"
622+
))
623+
.into());
624+
}
625+
Some(batch_tx) if batch_tx != raw_tx => {
626+
// Incoming message does not match what is in the batch.
627+
return Err(Error::MalformedTransactionInBatch(anyhow!(
628+
"mismatched incoming message"
629+
))
630+
.into());
631+
}
632+
_ => {
633+
// Everything is ok.
634+
}
635+
}
636+
637+
// Further execute the inner transaction. The transaction has already
638+
// passed checks so it is ok to include in a block.
639+
let tx_size = raw_tx.len().try_into().unwrap();
640+
let index = results.len();
641+
results.push(Self::execute_tx(ctx, tx_size, tx, index)?);
642+
643+
in_msgs_count += 1;
644+
}
645+
InMsgResult::Stop => break 'inmsg,
646+
}
647+
}
603648

649+
let inmsg_txs = results.len();
604650
let mut txs = Vec::with_capacity(batch.len());
605651
let mut prefixes: BTreeSet<Prefix> = BTreeSet::new();
606-
for tx in batch.iter() {
652+
for tx in batch.iter().skip(inmsg_txs) {
607653
let tx_size = tx.len().try_into().map_err(|_| {
608654
Error::MalformedTransactionInBatch(anyhow!("transaction too large"))
609655
})?;
@@ -629,23 +675,29 @@ impl<R: Runtime + Send + Sync> transaction::dispatcher::Dispatcher for Dispatche
629675

630676
// Execute the batch.
631677
let mut results = Vec::with_capacity(batch.len());
632-
for (index, (tx_size, tx_hash, tx)) in txs.into_iter().enumerate() {
678+
for (index, (tx_size, tx_hash, tx)) in txs.into_iter().skip(inmsg_txs).enumerate() {
633679
results.push(Self::execute_tx(ctx, tx_size, tx_hash, tx, index)?);
634680
}
635681

636682
Ok(results)
637683
},
638-
)
684+
)?;
685+
686+
// Include number of processed incoming messages in the final result.
687+
result.in_msgs_count = in_msgs_count;
688+
689+
Ok(result)
639690
}
640691

641692
fn schedule_and_execute_batch(
642693
&self,
643694
rt_ctx: transaction::Context<'_>,
644695
batch: &mut TxnBatch,
645-
_in_msgs: &[roothash::IncomingMessage],
696+
in_msgs: &[roothash::IncomingMessage],
646697
) -> Result<ExecuteBatchResult, RuntimeError> {
647698
let cfg = R::SCHEDULE_CONTROL;
648699
let mut tx_reject_hashes = Vec::new();
700+
let mut in_msgs_count = 0;
649701

650702
let mut result = self.execute_batch_common(
651703
rt_ctx,
@@ -655,13 +707,35 @@ impl<R: Runtime + Send + Sync> transaction::dispatcher::Dispatcher for Dispatche
655707
// The idea is to keep scheduling transactions as long as we have some space
656708
// available in the block as determined by gas use.
657709
let mut new_batch = Vec::new();
658-
let mut results = Vec::with_capacity(batch.len());
710+
let mut results = Vec::with_capacity(in_msgs.len() + batch.len());
659711
let mut requested_batch_len = cfg.initial_batch_size;
712+
713+
// Process incoming messages first.
714+
'inmsg: for in_msg in in_msgs {
715+
match R::IncomingMessagesHandler::process_in_msg(ctx, &in_msg) {
716+
InMsgResult::Skip => {
717+
// Skip, but treat as processed.
718+
in_msgs_count += 1;
719+
}
720+
InMsgResult::Execute(raw_tx, tx) => {
721+
// Further execute the inner transaction. The transaction has already
722+
// passed checks so it is ok to include in a block.
723+
let tx_size = raw_tx.len().try_into().unwrap();
724+
let index = new_batch.len();
725+
new_batch.push(raw_tx.to_owned());
726+
results.push(Self::execute_tx(ctx, tx_size, tx, index)?);
727+
728+
in_msgs_count += 1;
729+
}
730+
InMsgResult::Stop => break 'inmsg,
731+
}
732+
}
733+
734+
// Process regular transactions.
660735
'batch: loop {
661736
// Remember length of last batch.
662737
let last_batch_len = batch.len();
663738
let last_batch_tx_hash = batch.last().map(|raw_tx| Hash::digest_bytes(raw_tx));
664-
665739
for raw_tx in batch.drain(..) {
666740
// If we don't have enough gas for processing even the cheapest transaction
667741
// we are done. Same if we reached the runtime-imposed maximum tx count.
@@ -774,8 +848,10 @@ impl<R: Runtime + Send + Sync> transaction::dispatcher::Dispatcher for Dispatche
774848
},
775849
)?;
776850

777-
// Include rejected transaction hashes in the final result.
851+
// Include rejected transaction hashes and number of processed incoming messages in the
852+
// final result.
778853
result.tx_reject_hashes = tx_reject_hashes;
854+
result.in_msgs_count = in_msgs_count;
779855

780856
Ok(result)
781857
}
@@ -1000,6 +1076,7 @@ mod test {
10001076
core::Genesis {
10011077
parameters: core::Parameters {
10021078
max_batch_gas: u64::MAX,
1079+
max_inmsg_gas: 0,
10031080
max_tx_size: 32 * 1024,
10041081
max_tx_signers: 1,
10051082
max_multisig_signers: 8,

runtime-sdk/src/error.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//! Error types for runtimes.
2+
use std::fmt::Display;
3+
24
pub use oasis_core_runtime::types::Error as RuntimeError;
35

46
use crate::{dispatcher, module::CallResult};
@@ -56,6 +58,18 @@ pub trait Error: std::error::Error {
5658
{
5759
Err(self)
5860
}
61+
62+
/// Converts the error into a serializable error.
63+
fn into_serializable(self) -> SerializableError
64+
where
65+
Self: Sized,
66+
{
67+
SerializableError {
68+
module: self.module_name().to_owned(),
69+
code: self.code(),
70+
message: self.to_string(),
71+
}
72+
}
5973
}
6074

6175
impl Error for std::convert::Infallible {
@@ -68,6 +82,47 @@ impl Error for std::convert::Infallible {
6882
}
6983
}
7084

85+
/// A standardized serialized implementation for an error.
86+
#[derive(Debug, Default, Clone, thiserror::Error, cbor::Encode, cbor::Decode)]
87+
pub struct SerializableError {
88+
pub module: String,
89+
pub code: u32,
90+
pub message: String,
91+
}
92+
93+
impl Display for SerializableError {
94+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95+
write!(f, "{}", self.message)
96+
}
97+
}
98+
99+
impl Error for SerializableError {
100+
fn module_name(&self) -> &str {
101+
&self.module
102+
}
103+
104+
fn code(&self) -> u32 {
105+
self.code
106+
}
107+
}
108+
109+
impl From<CallResult> for SerializableError {
110+
fn from(result: CallResult) -> Self {
111+
match result {
112+
CallResult::Failed {
113+
module,
114+
code,
115+
message,
116+
} => Self {
117+
module,
118+
code,
119+
message,
120+
},
121+
_ => Default::default(),
122+
}
123+
}
124+
}
125+
71126
#[cfg(test)]
72127
mod test {
73128
use super::*;

runtime-sdk/src/module.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use impl_trait_for_tuples::impl_for_tuples;
99

1010
use crate::{
1111
context::Context,
12+
core::consensus::roothash,
1213
dispatcher, error,
1314
error::Error as _,
1415
event, modules,
@@ -562,6 +563,39 @@ impl ModuleInfoHandler for Tuple {
562563
}
563564
}
564565

566+
/// Incoming message handler.
567+
pub trait InMsgHandler {
568+
/// Process an incoming message.
569+
fn process_in_msg<'a, C: Context>(
570+
ctx: &mut C,
571+
in_msg: &'a roothash::IncomingMessage,
572+
) -> InMsgResult<'a>;
573+
}
574+
575+
/// Result of processing an incoming message.
576+
#[derive(Debug)]
577+
pub enum InMsgResult<'a> {
578+
/// Skip to next incoming message, but count as processed.
579+
Skip,
580+
/// Add to batch/verify inclusion and execute.
581+
Execute(&'a [u8], Transaction),
582+
/// Stop processing incoming messages.
583+
Stop,
584+
}
585+
586+
/// An incoming message handler which discards all incoming messages.
587+
pub struct InMsgDiscard;
588+
589+
impl InMsgHandler for InMsgDiscard {
590+
fn process_in_msg<'a, C: Context>(
591+
_ctx: &mut C,
592+
_in_msg: &'a roothash::IncomingMessage,
593+
) -> InMsgResult<'a> {
594+
// Just skip all messages without doing anything.
595+
InMsgResult::Skip
596+
}
597+
}
598+
565599
/// A runtime module.
566600
pub trait Module {
567601
/// Module name.
@@ -590,6 +624,11 @@ pub trait Module {
590624

591625
/// Set the module's parameters.
592626
fn set_params(params: Self::Parameters) {
627+
params
628+
.validate_basic()
629+
.map_err(|_| ())
630+
.expect("module parameters are invalid");
631+
593632
CurrentState::with_store(|store| {
594633
let store = storage::PrefixStore::new(store, &Self::NAME);
595634
let mut store = storage::TypedStore::new(store);

runtime-sdk/src/modules/consensus/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl Default for Parameters {
7474
fn default() -> Self {
7575
Self {
7676
gas_costs: Default::default(),
77-
consensus_denomination: token::Denomination::from_str("TEST").unwrap(),
77+
consensus_denomination: "TEST".parse().unwrap(),
7878
consensus_scaling_factor: 1,
7979
min_delegate_amount: 0,
8080
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use crate::modules;
2+
3+
/// Incoming message handler configuration.
4+
pub trait Config: 'static {
5+
/// The accounts module to use.
6+
type Accounts: modules::accounts::API;
7+
/// The consensus module to use.
8+
type Consensus: modules::consensus::API;
9+
10+
/// Maximum number of outgoing consensus message slots that an incoming message can claim.
11+
///
12+
/// When this is configured to be greater than zero it allows incoming messages to also emit
13+
/// consensus messages as a result of executing a transaction.
14+
const MAX_CONSENSUS_MSG_SLOTS_PER_TX: u32 = 1;
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use super::MODULE_NAME;
2+
use crate::error;
3+
4+
/// Events emitted by the consensus incoming message handler module.
5+
#[derive(Debug, cbor::Encode, oasis_runtime_sdk_macros::Event)]
6+
#[cbor(untagged)]
7+
pub enum Event {
8+
#[sdk_event(code = 1)]
9+
Processed {
10+
id: u64,
11+
#[cbor(optional)]
12+
tag: u64,
13+
#[cbor(optional)]
14+
error: Option<error::SerializableError>,
15+
},
16+
}

0 commit comments

Comments
 (0)