diff --git a/src/kernel_evm/ethereum/src/rlp_helpers.rs b/src/kernel_evm/ethereum/src/rlp_helpers.rs index d21b8686e3de9eebd07081e16061accfde005a2f..0b4c0e08f6224d18041797d11830dd401e6de89e 100644 --- a/src/kernel_evm/ethereum/src/rlp_helpers.rs +++ b/src/kernel_evm/ethereum/src/rlp_helpers.rs @@ -69,6 +69,19 @@ pub fn decode_list( decoder.as_list().map_err(custom_err) } +pub fn decode_array( + item: rlp::Rlp<'_>, + size: usize, + vec: &mut [u8], +) -> Result<(), DecoderError> { + let list = item.data()?; + if list.len() != size { + return Err(DecoderError::RlpIncorrectListLen); + } + vec.copy_from_slice(list); + Ok(()) +} + pub fn append_option( stream: &mut RlpStream, data: Option, diff --git a/src/kernel_evm/kernel/src/block.rs b/src/kernel_evm/kernel/src/block.rs index 1cb3c5ed8cdc877b2bae6ea8abbc6b8177486b82..0ce3221f3b6013c7358c5bf5f33fead555751401 100644 --- a/src/kernel_evm/kernel/src/block.rs +++ b/src/kernel_evm/kernel/src/block.rs @@ -6,7 +6,7 @@ use crate::apply::apply_transaction; use crate::block_in_progress; -use crate::blueprint::Queue; +use crate::blueprint::{Queue, QueueElement}; use crate::current_timestamp; use crate::error::Error; use crate::indexable_storage::IndexableStorage; @@ -115,7 +115,7 @@ pub fn produce( queue: Queue, chain_id: U256, base_fee_per_gas: U256, -) -> Result<(), anyhow::Error> { +) -> Result { let (mut current_constants, mut current_block_number, mut current_block_parent_hash) = match storage::read_current_block(host) { Ok(block) => ( @@ -139,7 +139,9 @@ pub fn produce( let precompiles = precompiles::precompile_set::(); let mut tick_counter = TickCounter::new(0u64); - for proposal in queue.proposals { + let mut iter = queue.proposals.into_iter(); + while let Some(proposal) = iter.next() { + // proposal is turned into a ring to allow popping from the front let mut block_in_progress = BlockInProgress::from_queue_element( proposal, current_block_number, @@ -163,12 +165,14 @@ pub fn produce( "Ask for reboot. Estimated ticks: {}", &block_in_progress.estimated_ticks ); - storage::store_block_in_progress(host, &block_in_progress)?; + let remaining_queue = Queue { + proposals: remaining_proposals(block_in_progress, iter), + ..queue + }; + storage::store_queue(host, &remaining_queue)?; storage::add_reboot_flag(host)?; host.mark_for_reboot()?; - // TODO: https://gitlab.com/tezos/tezos/-/issues/5873 - // store the queue - return Ok(()); + return Ok(ComputationResult::RebootNeeded); } ComputationResult::Finished => { tick_counter = TickCounter::new(block_in_progress.estimated_ticks); @@ -178,12 +182,21 @@ pub fn produce( current_block_number = new_block.number + 1; current_block_parent_hash = new_block.hash; current_constants = new_block.constants(chain_id, base_fee_per_gas); - storage::delete_block_in_progress(host)?; } } } log!(host, Debug, "Estimated ticks: {}", tick_counter.c); - Ok(()) + Ok(ComputationResult::Finished) +} + +fn remaining_proposals( + bip: BlockInProgress, + iter: std::vec::IntoIter, +) -> Vec { + let mut proposals = Vec::new(); + proposals.push(QueueElement::BlockInProgress(Box::new(bip))); + proposals.extend(iter); + proposals } #[cfg(test)] @@ -389,7 +402,7 @@ mod tests { ); produce(host, queue, DUMMY_CHAIN_ID, DUMMY_BASE_FEE_PER_GAS.into()) - .expect("The block production failed.") + .expect("The block production failed."); } fn assert_current_block_reading_validity(host: &mut MockHost) { @@ -480,6 +493,17 @@ mod tests { assert_eq!(TransactionStatus::Success, status); } + #[test] + fn playground() { + let v = vec![1, 2, 3, 4]; + let l1 = v.len(); + let mut iter = v.into_iter(); + println!("{:?}", iter.next()); + let mut rest = vec![]; + rest.extend(iter); + println!("{:?}", rest); + assert!(l1 > rest.len()); + } #[test] // Test if a valid transaction is producing a receipt with a contract address fn test_valid_transactions_receipt_contract_address() { @@ -1297,13 +1321,14 @@ mod tests { let mut transactions = vec![]; let mut proposals = vec![]; - for n in 1..TOO_MANY_TRANSACTIONS { + for n in 0..TOO_MANY_TRANSACTIONS { transactions.push(dummy_transaction(n)); if n.rem(80) == 0 { proposals.push(blueprint(transactions)); transactions = vec![]; } } + let initial_number_of_proposals = proposals.len(); let queue = Queue { proposals, kernel_upgrade: None, @@ -1330,5 +1355,26 @@ mod tests { storage::was_rebooted(&mut host).expect("Should have found flag"), "Flag should be set" ); + + let queue = + storage::read_queue(&mut host).expect("There should be a queue in storage"); + let (first, rest) = queue + .proposals + .split_first() + .expect("Queue should be non empty"); + match first { + QueueElement::Blueprint(_) => { + panic!("first element should be a bip") + } + QueueElement::BlockInProgress(bip) => assert!( + bip.queue_length() > 0, + "There should be some transactions left" + ), + } + assert!(!rest.is_empty(), "There should proposals left"); + assert!( + initial_number_of_proposals > rest.len(), + "There should be less proposals left than originally present in the queue." + ); } } diff --git a/src/kernel_evm/kernel/src/blueprint.rs b/src/kernel_evm/kernel/src/blueprint.rs index fc2b6309a81d82cb15d1d6153550cf185d29b2de..3ec9029e1f386048ea5052cecb7bfd693171ff0e 100644 --- a/src/kernel_evm/kernel/src/blueprint.rs +++ b/src/kernel_evm/kernel/src/blueprint.rs @@ -9,18 +9,92 @@ use crate::block_in_progress::BlockInProgress; use crate::inbox::{read_inbox, KernelUpgrade, Transaction, TransactionContent}; use crate::tick_model::constants::MAX_TRANSACTION_GAS_LIMIT; use primitive_types::U256; +use rlp::{Decodable, DecoderError, Encodable}; use tezos_crypto_rs::hash::ContractKt1Hash; +use tezos_ethereum::rlp_helpers; use tezos_smart_rollup_host::runtime::Runtime; /// The blueprint of a block is a list of transactions. +#[derive(PartialEq, Debug, Clone)] pub struct Blueprint { pub transactions: Vec, } + +impl Encodable for Blueprint { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + stream.begin_list(1); + stream.append_list(&self.transactions); + } +} + +impl Decodable for Blueprint { + fn decode(decoder: &rlp::Rlp) -> Result { + if !decoder.is_list() { + return Err(DecoderError::RlpExpectedToBeList); + } + if decoder.item_count()? != 1 { + return Err(DecoderError::RlpIncorrectListLen); + } + + let mut it = decoder.iter(); + let transactions = + rlp_helpers::decode_list(&rlp_helpers::next(&mut it)?, "transactions")?; + Ok(Blueprint { transactions }) + } +} + +#[derive(PartialEq, Debug, Clone)] pub enum QueueElement { Blueprint(Blueprint), BlockInProgress(Box), } -#[derive(Default)] + +const BIP_QUEUEELT_TAG: u8 = 1; +const BLUEPRINT_QUEUEELT_TAG: u8 = 2; + +impl Decodable for QueueElement { + fn decode(decoder: &rlp::Rlp) -> Result { + if !decoder.is_list() { + return Err(DecoderError::RlpExpectedToBeList); + } + if decoder.item_count()? != 2 { + return Err(DecoderError::RlpIncorrectListLen); + } + let tag: u8 = decoder.at(0)?.as_val()?; + let elt = decoder.at(1)?; + match tag { + BIP_QUEUEELT_TAG => { + // block in progress + let bip = BlockInProgress::decode(&elt)?; + Ok(Self::BlockInProgress(Box::new(bip))) + } + BLUEPRINT_QUEUEELT_TAG => { + // blueprint + let bpt = Blueprint::decode(&elt)?; + Ok(Self::Blueprint(bpt)) + } + _ => Err(DecoderError::Custom("Unknown queue element tag.")), + } + } +} + +impl Encodable for QueueElement { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + stream.begin_list(2); + match self { + QueueElement::Blueprint(bpt) => { + stream.append(&BLUEPRINT_QUEUEELT_TAG); + bpt.rlp_append(stream) + } + QueueElement::BlockInProgress(bip) => { + stream.append(&BIP_QUEUEELT_TAG); + bip.rlp_append(stream) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Default)] pub struct Queue { // In our case, to make it simple and straightforward it will be // an array of pendings transactions even though it'll be only a @@ -44,6 +118,35 @@ impl Queue { } } +impl Decodable for Queue { + fn decode(decoder: &rlp::Rlp) -> Result { + if !decoder.is_list() { + return Err(DecoderError::RlpExpectedToBeList); + } + if decoder.item_count()? != 2 { + return Err(DecoderError::RlpIncorrectListLen); + } + + let mut it = decoder.iter(); + let proposals: Vec = + rlp_helpers::decode_list(&rlp_helpers::next(&mut it)?, "proposals")?; + let kernel_upgrade: Option = + rlp_helpers::decode_option(&rlp_helpers::next(&mut it)?, "kernel_upgrade")?; + Ok(Queue { + proposals, + kernel_upgrade, + }) + } +} + +impl Encodable for Queue { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + stream.begin_list(2); + stream.append_list(&self.proposals); + rlp_helpers::append_option(stream, self.kernel_upgrade.clone()); + } +} + fn filter_invalid_transactions( transactions: Vec, chain_id: U256, @@ -88,23 +191,27 @@ pub fn fetch( #[cfg(test)] mod tests { + use std::collections::VecDeque; + use super::*; use crate::inbox::TransactionContent::Ethereum; - use primitive_types::{H160, U256}; + use crate::parsing::UPGRADE_NONCE_SIZE; + use primitive_types::{H160, H256, U256}; + use rlp::Rlp; use tezos_ethereum::{ transaction::TRANSACTION_HASH_SIZE, tx_common::EthereumTransactionCommon, }; + use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE; fn address_from_str(s: &str) -> Option { let data = &hex::decode(s).unwrap(); Some(H160::from_slice(data)) } - - fn tx() -> EthereumTransactionCommon { + fn tx_(i: u64) -> EthereumTransactionCommon { EthereumTransactionCommon { type_: tezos_ethereum::transaction::TransactionType::Legacy, chain_id: U256::one(), - nonce: U256::from(40000000u64), + nonce: U256::from(i), max_priority_fee_per_gas: U256::from(40000000u64), max_fee_per_gas: U256::from(40000000u64), gas_limit: 21000u64, @@ -115,6 +222,16 @@ mod tests { signature: None, } } + fn tx() -> EthereumTransactionCommon { + tx_(40000000u64) + } + + fn dummy_transaction(i: u8) -> Transaction { + Transaction { + tx_hash: [i; TRANSACTION_HASH_SIZE], + content: Ethereum(tx_(i.into())), + } + } #[test] fn test_filter_invalid_chain_id() { @@ -168,4 +285,51 @@ mod tests { ); assert_eq!(vec![valid_transaction], filtered_transactions) } + + #[test] + fn test_encode_queue_elt() { + let proposal = QueueElement::Blueprint(Blueprint { + transactions: vec![dummy_transaction(0), dummy_transaction(1)], + }); + + let encoded = proposal.rlp_bytes(); + let decoder = Rlp::new(&encoded); + let decoded = QueueElement::decode(&decoder).expect("Should be decodable"); + assert_eq!(decoded, proposal); + } + + fn dummy_bip(i: usize) -> BlockInProgress { + BlockInProgress::new_with_ticks( + U256::from(i), + H256::zero(), + U256::zero(), + VecDeque::new(), + 0, + ) + } + + #[test] + fn test_encode_queue() { + let proposal = QueueElement::Blueprint(Blueprint { + transactions: vec![dummy_transaction(0), dummy_transaction(1)], + }); + + let proposals = vec![ + QueueElement::BlockInProgress(Box::new(dummy_bip(2))), + proposal, + ]; + let kernel_upgrade = Some(KernelUpgrade { + nonce: [3; UPGRADE_NONCE_SIZE], + preimage_hash: [3; PREIMAGE_HASH_SIZE], + }); + let queue = Queue { + proposals, + kernel_upgrade, + }; + + let encoded = queue.rlp_bytes(); + let decoder = Rlp::new(&encoded); + let decoded = Queue::decode(&decoder).expect("Should be decodable"); + assert_eq!(decoded, queue); + } } diff --git a/src/kernel_evm/kernel/src/inbox.rs b/src/kernel_evm/kernel/src/inbox.rs index 5eae5498253433f018568740527d9ec1e6f5cffa..7e5edf656edb7e1edfa67bc93d84303f4e2f71d1 100644 --- a/src/kernel_evm/kernel/src/inbox.rs +++ b/src/kernel_evm/kernel/src/inbox.rs @@ -21,7 +21,7 @@ use primitive_types::{H160, U256}; use rlp::{Decodable, DecoderError, Encodable}; use sha3::{Digest, Keccak256}; use tezos_crypto_rs::hash::ContractKt1Hash; -use tezos_ethereum::rlp_helpers::{decode_field, decode_tx_hash, next}; +use tezos_ethereum::rlp_helpers::{decode_array, decode_field, decode_tx_hash, next}; use tezos_ethereum::transaction::{TransactionHash, TRANSACTION_HASH_SIZE}; use tezos_ethereum::tx_common::EthereumTransactionCommon; use tezos_evm_logging::{log, Level::*}; @@ -161,6 +161,35 @@ pub struct KernelUpgrade { pub preimage_hash: [u8; PREIMAGE_HASH_SIZE], } +impl Decodable for KernelUpgrade { + fn decode(decoder: &rlp::Rlp) -> Result { + if !decoder.is_list() { + return Err(DecoderError::RlpExpectedToBeList); + } + if decoder.item_count()? != 2 { + return Err(DecoderError::RlpIncorrectListLen); + } + let mut nonce = [0u8; UPGRADE_NONCE_SIZE]; + let mut preimage_hash = [0u8; PREIMAGE_HASH_SIZE]; + + let mut it = decoder.iter(); + decode_array(next(&mut it)?, UPGRADE_NONCE_SIZE, &mut nonce)?; + decode_array(next(&mut it)?, PREIMAGE_HASH_SIZE, &mut preimage_hash)?; + Ok(Self { + nonce, + preimage_hash, + }) + } +} + +impl Encodable for KernelUpgrade { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + stream.begin_list(2); + stream.append_iter(self.nonce); + stream.append_iter(self.preimage_hash); + } +} + #[derive(Debug, PartialEq)] pub struct InboxContent { pub kernel_upgrade: Option, diff --git a/src/kernel_evm/kernel/src/lib.rs b/src/kernel_evm/kernel/src/lib.rs index eda22833644418a67590d5e874cd55a53fd1b16c..fe90b23901f44c036fecce5ad80bcdc38888109a 100644 --- a/src/kernel_evm/kernel/src/lib.rs +++ b/src/kernel_evm/kernel/src/lib.rs @@ -6,6 +6,7 @@ // SPDX-License-Identifier: MIT use anyhow::Context; +use block::ComputationResult; use evm_execution::Config; use migration::MigrationStatus; use primitive_types::U256; @@ -133,14 +134,27 @@ fn produce_and_upgrade( // Since a kernel upgrade was detected, in case an error is thrown // by the block production, we exceptionally "recover" from it and // still process the kernel upgrade. - if let Err(e) = block::produce(host, queue, chain_id, base_fee_per_gas) { - log!( + // In case of a reboot request, the upgrade is delayed. + match block::produce(host, queue, chain_id, base_fee_per_gas) { + Ok(ComputationResult::RebootNeeded) => Ok(()), + Err(e) => { + log!( host, Error, "{:?} happened during block production but a kernel upgrade was detected.", - e - ); + e); + upgrade(host, kernel_upgrade) + } + Ok(ComputationResult::Finished) => upgrade(host, kernel_upgrade), } +} + +fn upgrade( + host: &mut Host, + kernel_upgrade: KernelUpgrade, +) -> Result<(), anyhow::Error> { + // TODO: #5873 + // reboot before upgrade just in case let upgrade_status = upgrade_kernel(host, kernel_upgrade.preimage_hash) .context("Failed to upgrade kernel"); if upgrade_status.is_ok() { @@ -162,7 +176,7 @@ pub fn stage_two( if let Some(kernel_upgrade) = kernel_upgrade { produce_and_upgrade(host, queue, kernel_upgrade, chain_id, base_fee_per_gas) } else { - block::produce(host, queue, chain_id, base_fee_per_gas) + block::produce(host, queue, chain_id, base_fee_per_gas).map(|_| ()) } } @@ -220,18 +234,6 @@ fn retrieve_base_fee_per_gas(host: &mut Host) -> Result(host: &mut Host) -> Result { - let mut queue = Queue::new(); - // fetch rest of queue - // TODO: https://gitlab.com/tezos/tezos/-/issues/5873 - // reload the queue - - // fetch Bip - let bip = storage::read_block_in_progress(host)?; - queue.proposals = vec![blueprint::QueueElement::BlockInProgress(Box::new(bip))]; - Ok(queue) -} - pub fn main(host: &mut Host) -> Result<(), anyhow::Error> { let chain_id = retrieve_chain_id(host).context("Failed to retrieve chain id")?; let queue = if storage::was_rebooted(host)? { @@ -244,7 +246,7 @@ pub fn main(host: &mut Host) -> Result<(), anyhow::Error> { ); storage::delete_reboot_flag(host)?; log!(host, Info, "Read queue."); - fetch_queue_left(host)? + storage::read_queue(host)? } else { // first kernel run of the level match stage_zero(host)? { @@ -262,6 +264,7 @@ pub fn main(host: &mut Host) -> Result<(), anyhow::Error> { MigrationStatus::InProgress => return Ok(()), } }; + let base_fee_per_gas = retrieve_base_fee_per_gas(host)?; stage_two(host, queue, chain_id, base_fee_per_gas).context("Failed during stage 2") } @@ -337,3 +340,164 @@ pub fn kernel_loop(host: &mut Host) { } kernel_entry!(kernel_loop); + +#[cfg(test)] +mod tests { + use std::{ops::Rem, str::FromStr}; + + use crate::{ + blueprint::{Blueprint, Queue, QueueElement}, + inbox::{KernelUpgrade, Transaction, TransactionContent}, + parsing::UPGRADE_NONCE_SIZE, + stage_two, storage, + }; + use evm_execution::account_storage::{self, EthereumAccountStorage}; + use primitive_types::{H160, U256}; + use tezos_ethereum::{ + transaction::{TransactionHash, TransactionType}, + tx_common::EthereumTransactionCommon, + }; + use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE; + use tezos_smart_rollup_mock::MockHost; + + const DUMMY_CHAIN_ID: U256 = U256::one(); + const DUMMY_BASE_FEE_PER_GAS: u64 = 21000u64; + const TOO_MANY_TRANSACTIONS: u64 = 500; + + fn set_balance( + host: &mut MockHost, + evm_account_storage: &mut EthereumAccountStorage, + address: &H160, + balance: U256, + ) { + let mut account = evm_account_storage + .get_or_create(host, &account_storage::account_path(address).unwrap()) + .unwrap(); + let current_balance = account.balance(host).unwrap(); + if current_balance > balance { + account + .balance_remove(host, current_balance - balance) + .unwrap(); + } else { + account + .balance_add(host, balance - current_balance) + .unwrap(); + } + } + + fn address_from_str(s: &str) -> Option { + let data = &hex::decode(s).unwrap(); + Some(H160::from_slice(data)) + } + + fn dummy_eth(nonce: u64) -> EthereumTransactionCommon { + let nonce = U256::from(nonce); + let gas_price = U256::from(40000000000u64); + let gas_limit = 21000; + let value = U256::from(1); + let to = address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"); + let tx = EthereumTransactionCommon { + type_: TransactionType::Legacy, + chain_id: U256::one(), + nonce, + max_fee_per_gas: gas_price, + max_priority_fee_per_gas: gas_price, + gas_limit, + to, + value, + data: vec![], + access_list: vec![], + signature: None, + }; + + // corresponding caller's address is 0xaf1276cbb260bb13deddb4209ae99ae6e497f446 + tx.sign_transaction( + "dcdff53b4f013dbcdc717f89fe3bf4d8b10512aae282b48e01d7530470382701" + .to_string(), + ) + .unwrap() + } + + fn hash_from_nonce(nonce: u64) -> TransactionHash { + let nonce = u64::to_le_bytes(nonce); + let mut hash = [0; 32]; + hash[..8].copy_from_slice(&nonce); + hash + } + + fn dummy_transaction(nonce: u64) -> Transaction { + Transaction { + tx_hash: hash_from_nonce(nonce), + content: TransactionContent::Ethereum(dummy_eth(nonce)), + } + } + + fn blueprint(transactions: Vec) -> QueueElement { + QueueElement::Blueprint(Blueprint { transactions }) + } + + #[test] + fn test_reboot_during_block_production() { + // init host + let mut host = MockHost::default(); + + // sanity check: no current block + assert!( + storage::read_current_block_number(&mut host).is_err(), + "Should not have found current block number" + ); + + //provision sender account + let sender = H160::from_str("af1276cbb260bb13deddb4209ae99ae6e497f446").unwrap(); + let sender_initial_balance = U256::from(10000000000000000000u64); + let mut evm_account_storage = account_storage::init_account_storage().unwrap(); + set_balance( + &mut host, + &mut evm_account_storage, + &sender, + sender_initial_balance, + ); + + let mut transactions = vec![]; + let mut proposals = vec![]; + for n in 0..TOO_MANY_TRANSACTIONS { + transactions.push(dummy_transaction(n)); + if n.rem(80) == 0 { + proposals.push(blueprint(transactions)); + transactions = vec![]; + } + } + // the upgrade mechanism should not start otherwise it will fail + let broken_kernel_upgrade = KernelUpgrade { + nonce: [0u8; UPGRADE_NONCE_SIZE], + preimage_hash: [0u8; PREIMAGE_HASH_SIZE], + }; + let queue = Queue { + proposals, + kernel_upgrade: Some(broken_kernel_upgrade), + }; + + // If the upgrade is started, it should raise an error + stage_two( + &mut host, + queue, + DUMMY_CHAIN_ID, + DUMMY_BASE_FEE_PER_GAS.into(), + ) + .expect("Should have produced"); + + // test there is a new block + assert!( + storage::read_current_block_number(&mut host) + .expect("should have found a block number") + > U256::zero(), + "There should have been multiple blocks registered" + ); + + // test reboot is set + assert!( + storage::was_rebooted(&mut host).expect("Should have found flag"), + "Flag should be set" + ); + } +} diff --git a/src/kernel_evm/kernel/src/storage.rs b/src/kernel_evm/kernel/src/storage.rs index a579d150ef9d4d9063c4937149207abcd0db929d..4d4e0e1fe586b06dff79362096486b2a9c09fa9a 100644 --- a/src/kernel_evm/kernel/src/storage.rs +++ b/src/kernel_evm/kernel/src/storage.rs @@ -5,6 +5,7 @@ // SPDX-License-Identifier: MIT #![allow(dead_code)] +use crate::blueprint::Queue; use crate::indexable_storage::IndexableStorage; use anyhow::Context; use evm_execution::account_storage::EthereumAccount; @@ -99,6 +100,9 @@ const TRANSACTION_RECEIPT_TYPE_SIZE: usize = 1; /// The size of one 256 bit word. Size in bytes pub const WORD_SIZE: usize = 32usize; +// Path to the queue left at end of previous reboot +const QUEUE_IN_PROGRESS: RefPath = RefPath::assert_from(b"/queue"); + // This function should be used when it makes sense that the value // stored under [path] can be empty. fn store_read_empty_safe( @@ -859,6 +863,24 @@ pub fn was_rebooted(host: &mut Host) -> Result { Ok(host.store_read(&REBOOTED, 0, 0).is_ok()) } +pub fn store_queue( + host: &mut Host, + queue: &Queue, +) -> Result<(), anyhow::Error> { + let queue_path = OwnedPath::from(QUEUE_IN_PROGRESS); + host.store_write_all(&queue_path, &queue.rlp_bytes()) + .context("Failed to store current queue") +} + +pub fn read_queue(host: &mut Host) -> Result { + let queue_path = OwnedPath::from(QUEUE_IN_PROGRESS); + let bytes = host + .store_read_all(&queue_path) + .context("Failed to read current queue")?; + let decoder = Rlp::new(bytes.as_slice()); + Queue::decode(&decoder).context("Failed to decode current queue") +} + pub(crate) mod internal_for_tests { use super::*;