Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime-sdk: Add support for incoming messages #1133

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion client-sdk/go/modules/core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ type GasCosts struct {
AuthSignature uint64 `json:"auth_signature"`
AuthMultisigSigner uint64 `json:"auth_multisig_signer"`
CallformatX25519Deoxysii uint64 `json:"callformat_x25519_deoxysii"`

// Fields below have omitempty set for backwards compatibility. Once there are no deployed
// runtimes using an old version of the SDK, this should be removed.

InMsgBase uint64 `json:"inmsg_base,omitempty"`
}

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

MaxTxSize uint32 `json:"max_tx_size,omitempty"`
MaxTxSize uint32 `json:"max_tx_size,omitempty"`
MaxInMsgGas uint32 `json:"max_inmsg_gas,omitempty"`
}

// ModuleName is the core module name.
Expand Down
4 changes: 4 additions & 0 deletions runtime-sdk/modules/evm/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,18 +286,21 @@ fn test_evm_calls() {

#[test]
fn test_c10l_evm_calls_enc() {
let _guard = crypto::signature::context::test_using_chain_context();
crypto::signature::context::set_chain_context(Default::default(), "test");
do_test_evm_calls::<ConfidentialEVMConfig>(false);
}

#[test]
fn test_c10l_evm_calls_plain() {
let _guard = crypto::signature::context::test_using_chain_context();
crypto::signature::context::set_chain_context(Default::default(), "test");
do_test_evm_calls::<ConfidentialEVMConfig>(true /* force_plain */);
}

#[test]
fn test_c10l_evm_balance_transfer() {
let _guard = crypto::signature::context::test_using_chain_context();
crypto::signature::context::set_chain_context(Default::default(), "test");
let mut mock = mock::Mock::default();
let ctx = mock.create_ctx();
Expand Down Expand Up @@ -800,6 +803,7 @@ fn test_evm_runtime() {

#[test]
fn test_c10l_evm_runtime() {
let _guard = crypto::signature::context::test_using_chain_context();
crypto::signature::context::set_chain_context(Default::default(), "test");
do_test_evm_runtime::<ConfidentialEVMConfig>();
}
Expand Down
34 changes: 22 additions & 12 deletions runtime-sdk/src/crypto/signature/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,32 @@ pub fn set_chain_context(runtime_id: Namespace, consensus_chain_context: &str) {
*guard = Some(ctx.into_bytes());
}

/// Test helper to serialize unit tests using the global chain context. The chain context is reset
/// when this method is called.
///
/// # Example
///
/// ```rust
/// # use oasis_runtime_sdk::crypto::signature::context::test_using_chain_context;
/// let _guard = test_using_chain_context();
/// // ... rest of the test code follows ...
/// ```
#[cfg(any(test, feature = "test"))]
pub fn test_using_chain_context() -> std::sync::MutexGuard<'static, ()> {
static TEST_USING_CHAIN_CONTEXT: Lazy<Mutex<()>> = Lazy::new(Default::default);
let guard = TEST_USING_CHAIN_CONTEXT.lock().unwrap();
*CHAIN_CONTEXT.lock().unwrap() = None;

guard
}

#[cfg(test)]
mod test {
use super::*;

static TEST_GUARD: Lazy<Mutex<()>> = Lazy::new(Default::default);

fn reset_chain_context() {
*CHAIN_CONTEXT.lock().unwrap() = None;
}

#[test]
fn test_chain_context() {
let _guard = TEST_GUARD.lock().unwrap();
reset_chain_context();
let _guard = test_using_chain_context();
set_chain_context(
"8000000000000000000000000000000000000000000000000000000000000000".into(),
"643fb06848be7e970af3b5b2d772eb8cfb30499c8162bc18ac03df2f5e22520e",
Expand All @@ -94,17 +106,15 @@ mod test {

#[test]
fn test_chain_context_not_configured() {
let _guard = TEST_GUARD.lock().unwrap();
reset_chain_context();
let _guard = test_using_chain_context();

let result = std::panic::catch_unwind(|| get_chain_context_for(b"test"));
assert!(result.is_err());
}

#[test]
fn test_chain_context_already_configured() {
let _guard = TEST_GUARD.lock().unwrap();
reset_chain_context();
let _guard = test_using_chain_context();
set_chain_context(
"8000000000000000000000000000000000000000000000000000000000000000".into(),
"643fb06848be7e970af3b5b2d772eb8cfb30499c8162bc18ac03df2f5e22520e",
Expand Down
65 changes: 65 additions & 0 deletions runtime-sdk/src/crypto/signature/sr25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,68 @@ impl From<&'static str> for PublicKey {
PublicKey::from_bytes(&base64::decode(s).unwrap()).unwrap()
}
}

/// A memory-backed signer for Sr25519.
pub struct MemorySigner {
kp: schnorrkel::Keypair,
}

impl MemorySigner {
/// Creates a new signer from a seed.
pub fn new_from_seed(seed: &[u8]) -> Result<Self, Error> {
let sk =
schnorrkel::MiniSecretKey::from_bytes(&seed).map_err(|_| Error::InvalidArgument)?;
let kp = sk.expand_to_keypair(schnorrkel::keys::ExpansionMode::Ed25519);
Ok(Self { kp })
}

/// Generates a new signer deterministically from a test key name string.
pub fn new_test(name: &str) -> Self {
let mut digest = Sha512Trunc256::new();
digest.update(name.as_bytes());
let seed = digest.finalize();

Self::new_from_seed(&seed).unwrap()
}

/// Public key corresponding to the signer.
pub fn public(&self) -> PublicKey {
PublicKey::from_bytes(&self.kp.public.to_bytes()).unwrap()
}

/// Generates a signature with the private key over the context and message.
pub fn context_sign(&self, context: &[u8], message: &[u8]) -> Result<Signature, Error> {
// Convert the context to a Sr25519 SigningContext.
let context = schnorrkel::context::SigningContext::new(context);

// Generate a SigningTranscript from the context, and a pre-hash
// of the message.
//
// Note: This requires using Sha512Trunc256 instead of our hash,
// due to the need for FixedOutput.
let mut digest = Sha512Trunc256::new();
digest.update(message);
let transcript = context.hash256(digest);

let signature = self.kp.sign(transcript);

Ok(signature.to_bytes().to_vec().into())
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_memory_signer() {
let signer = MemorySigner::new_test("memory signer test");
let ctx = b"oasis-core/test: context";
let message = b"this is a message";
let signature = signer.context_sign(ctx, message).unwrap();
let pk = signer.public();

pk.verify(ctx, message, &signature)
.expect("signature should verify");
}
}
99 changes: 88 additions & 11 deletions runtime-sdk/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::{
error::{Error as _, RuntimeError},
event::IntoTags,
keymanager::{KeyManagerClient, KeyManagerError},
module::{self, BlockHandler, MethodHandler, TransactionHandler},
module::{self, BlockHandler, InMsgHandler, InMsgResult, MethodHandler, TransactionHandler},
modules,
modules::core::API as _,
runtime::Runtime,
Expand Down Expand Up @@ -582,7 +582,7 @@ impl<R: Runtime> Dispatcher<R> {
messages,
block_tags,
tx_reject_hashes: vec![],
in_msgs_count: 0, // TODO: Support processing incoming messages.
in_msgs_count: 0,
})
})
}
Expand All @@ -593,17 +593,63 @@ impl<R: Runtime + Send + Sync> transaction::dispatcher::Dispatcher for Dispatche
&self,
rt_ctx: transaction::Context<'_>,
batch: &TxnBatch,
_in_msgs: &[roothash::IncomingMessage],
in_msgs: &[roothash::IncomingMessage],
) -> Result<ExecuteBatchResult, RuntimeError> {
self.execute_batch_common(
let mut in_msgs_count = 0;

let mut result = self.execute_batch_common(
rt_ctx,
|ctx| -> Result<Vec<ExecuteTxResult>, RuntimeError> {
// If prefetch limit is set enable prefetch.
let prefetch_enabled = R::PREFETCH_LIMIT > 0;
let mut results = Vec::with_capacity(batch.len());

// Process incoming messages first.
let mut batch_it = batch.iter();
'inmsg: for in_msg in in_msgs {
match R::IncomingMessagesHandler::process_in_msg(ctx, &in_msg) {
InMsgResult::Skip => {
// Skip, but treat as processed.
in_msgs_count += 1;
}
InMsgResult::Execute(raw_tx, tx) => {
// Verify that the transaction has been included in the batch.
match batch_it.next() {
None => {
// Nothing in the batch when there should be an incoming message.
return Err(Error::MalformedTransactionInBatch(anyhow!(
"missing incoming message"
))
.into());
}
Some(batch_tx) if batch_tx != raw_tx => {
// Incoming message does not match what is in the batch.
return Err(Error::MalformedTransactionInBatch(anyhow!(
"mismatched incoming message"
))
.into());
}
_ => {
// Everything is ok.
}
}

// Further execute the inner transaction. The transaction has already
// passed checks so it is ok to include in a block.
let tx_size = raw_tx.len().try_into().unwrap();
let index = results.len();
results.push(Self::execute_tx(ctx, tx_size, tx, index)?);

in_msgs_count += 1;
}
InMsgResult::Stop => break 'inmsg,
}
}

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

// Execute the batch.
let mut results = Vec::with_capacity(batch.len());
for (index, (tx_size, tx_hash, tx)) in txs.into_iter().enumerate() {
for (index, (tx_size, tx_hash, tx)) in txs.into_iter().skip(inmsg_txs).enumerate() {
results.push(Self::execute_tx(ctx, tx_size, tx_hash, tx, index)?);
}

Ok(results)
},
)
)?;

// Include number of processed incoming messages in the final result.
result.in_msgs_count = in_msgs_count;

Ok(result)
}

fn schedule_and_execute_batch(
&self,
rt_ctx: transaction::Context<'_>,
batch: &mut TxnBatch,
_in_msgs: &[roothash::IncomingMessage],
in_msgs: &[roothash::IncomingMessage],
) -> Result<ExecuteBatchResult, RuntimeError> {
let cfg = R::SCHEDULE_CONTROL;
let mut tx_reject_hashes = Vec::new();
let mut in_msgs_count = 0;

let mut result = self.execute_batch_common(
rt_ctx,
Expand All @@ -655,13 +707,35 @@ impl<R: Runtime + Send + Sync> transaction::dispatcher::Dispatcher for Dispatche
// The idea is to keep scheduling transactions as long as we have some space
// available in the block as determined by gas use.
let mut new_batch = Vec::new();
let mut results = Vec::with_capacity(batch.len());
let mut results = Vec::with_capacity(in_msgs.len() + batch.len());
let mut requested_batch_len = cfg.initial_batch_size;

// Process incoming messages first.
'inmsg: for in_msg in in_msgs {
match R::IncomingMessagesHandler::process_in_msg(ctx, &in_msg) {
InMsgResult::Skip => {
// Skip, but treat as processed.
in_msgs_count += 1;
}
InMsgResult::Execute(raw_tx, tx) => {
// Further execute the inner transaction. The transaction has already
// passed checks so it is ok to include in a block.
let tx_size = raw_tx.len().try_into().unwrap();
let index = new_batch.len();
new_batch.push(raw_tx.to_owned());
results.push(Self::execute_tx(ctx, tx_size, tx, index)?);

in_msgs_count += 1;
}
InMsgResult::Stop => break 'inmsg,
}
}

// Process regular transactions.
'batch: loop {
// Remember length of last batch.
let last_batch_len = batch.len();
let last_batch_tx_hash = batch.last().map(|raw_tx| Hash::digest_bytes(raw_tx));

for raw_tx in batch.drain(..) {
// If we don't have enough gas for processing even the cheapest transaction
// we are done. Same if we reached the runtime-imposed maximum tx count.
Expand Down Expand Up @@ -774,8 +848,10 @@ impl<R: Runtime + Send + Sync> transaction::dispatcher::Dispatcher for Dispatche
},
)?;

// Include rejected transaction hashes in the final result.
// Include rejected transaction hashes and number of processed incoming messages in the
// final result.
result.tx_reject_hashes = tx_reject_hashes;
result.in_msgs_count = in_msgs_count;

Ok(result)
}
Expand Down Expand Up @@ -1000,6 +1076,7 @@ mod test {
core::Genesis {
parameters: core::Parameters {
max_batch_gas: u64::MAX,
max_inmsg_gas: 0,
max_tx_size: 32 * 1024,
max_tx_signers: 1,
max_multisig_signers: 8,
Expand Down
Loading
Loading