From 5625921b2a0f6e824db44b55dba3432ef30b666b Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 19 Dec 2023 00:07:20 +0800 Subject: [PATCH 001/294] begin to work to verify --- Cargo.lock | 1 + tx-pool/Cargo.toml | 1 + tx-pool/src/component/mod.rs | 1 + tx-pool/src/component/verify_queue.rs | 113 ++++++++++++++++++++++++++ tx-pool/src/lib.rs | 1 + tx-pool/src/process.rs | 42 +++------- tx-pool/src/service.rs | 8 +- tx-pool/src/verify_mgr.rs | 61 ++++++++++++++ tx-pool/src/verify_worker.rs | 0 9 files changed, 198 insertions(+), 30 deletions(-) create mode 100644 tx-pool/src/component/verify_queue.rs create mode 100644 tx-pool/src/verify_mgr.rs create mode 100644 tx-pool/src/verify_worker.rs diff --git a/Cargo.lock b/Cargo.lock index 90db61f9df..b607cc836d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1627,6 +1627,7 @@ dependencies = [ "hyper", "lru", "multi_index_map", + "num_cpus", "rand 0.8.5", "rustc-hash", "sentry", diff --git a/tx-pool/Cargo.toml b/tx-pool/Cargo.toml index 198563ed3a..5761c57a5c 100644 --- a/tx-pool/Cargo.toml +++ b/tx-pool/Cargo.toml @@ -16,6 +16,7 @@ ckb-logger = { path = "../util/logger", version = "= 0.114.0-pre" } ckb-verification = { path = "../verification", version = "= 0.114.0-pre" } ckb-systemtime = { path = "../util/systemtime", version = "= 0.114.0-pre" } lru = "0.7.1" +num_cpus = "1.16.0" ckb-dao = { path = "../util/dao", version = "= 0.114.0-pre" } ckb-reward-calculator = { path = "../util/reward-calculator", version = "= 0.114.0-pre" } diff --git a/tx-pool/src/component/mod.rs b/tx-pool/src/component/mod.rs index e5b8ab3cfc..4cf8685f32 100644 --- a/tx-pool/src/component/mod.rs +++ b/tx-pool/src/component/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod orphan; pub(crate) mod pool_map; pub(crate) mod recent_reject; pub(crate) mod sort_key; +pub(crate) mod verify_queue; #[cfg(test)] mod tests; diff --git a/tx-pool/src/component/verify_queue.rs b/tx-pool/src/component/verify_queue.rs new file mode 100644 index 0000000000..b5875abfe3 --- /dev/null +++ b/tx-pool/src/component/verify_queue.rs @@ -0,0 +1,113 @@ +use ckb_network::PeerIndex; +use ckb_types::{ + core::{Cycle, TransactionView}, + packed::ProposalShortId, +}; +use ckb_util::{shrink_to_fit, LinkedHashMap}; + +const SHRINK_THRESHOLD: usize = 100; +pub(crate) const DEFAULT_MAX_CHUNK_TRANSACTIONS: usize = 100; + +#[derive(Debug, Clone, Eq)] +pub(crate) struct Entry { + pub(crate) tx: TransactionView, + pub(crate) remote: Option<(Cycle, PeerIndex)>, +} + +impl PartialEq for Entry { + fn eq(&self, other: &Entry) -> bool { + self.tx == other.tx + } +} + +#[derive(Default)] +pub(crate) struct VerifyQueue { + inner: LinkedHashMap, + // memory last pop value for atomic reset + front: Option, +} + +impl VerifyQueue { + pub(crate) fn new() -> Self { + VerifyQueue { + inner: LinkedHashMap::default(), + front: None, + } + } + + pub fn len(&self) -> usize { + self.inner.len() + } + + #[allow(dead_code)] + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn is_full(&self) -> bool { + self.len() > DEFAULT_MAX_CHUNK_TRANSACTIONS + } + + pub fn contains_key(&self, id: &ProposalShortId) -> bool { + self.front + .as_ref() + .map(|e| e.tx.proposal_short_id()) + .as_ref() + == Some(id) + || self.inner.contains_key(id) + } + + pub fn shrink_to_fit(&mut self) { + shrink_to_fit!(self.inner, SHRINK_THRESHOLD); + } + + pub fn clean_front(&mut self) { + self.front = None; + } + + pub fn pop_front(&mut self) -> Option { + if let Some(entry) = &self.front { + Some(entry.clone()) + } else { + match self.inner.pop_front() { + Some((_id, entry)) => { + self.front = Some(entry.clone()); + Some(entry) + } + None => None, + } + } + } + + pub fn remove_chunk_tx(&mut self, id: &ProposalShortId) -> Option { + let ret = self.inner.remove(id); + self.shrink_to_fit(); + ret + } + + pub fn remove_chunk_txs(&mut self, ids: impl Iterator) { + for id in ids { + self.inner.remove(&id); + } + self.shrink_to_fit(); + } + + /// If the queue did not have this tx present, true is returned. + /// If the queue did have this tx present, false is returned. + pub fn add_tx(&mut self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>) -> bool { + if self.contains_key(&tx.proposal_short_id()) { + return false; + } + + self.inner + .insert(tx.proposal_short_id(), Entry { tx, remote }) + .is_none() + } + + /// Clears the map, removing all elements. + pub fn clear(&mut self) { + self.inner.clear(); + self.clean_front(); + self.shrink_to_fit() + } +} diff --git a/tx-pool/src/lib.rs b/tx-pool/src/lib.rs index d122177c01..2f6d4af83b 100644 --- a/tx-pool/src/lib.rs +++ b/tx-pool/src/lib.rs @@ -12,6 +12,7 @@ mod pool_cell; mod process; pub mod service; mod util; +mod verify_mgr; pub use ckb_jsonrpc_types::BlockTemplate; pub use component::entry::TxEntry; diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 85b6d523fc..700330882b 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -302,31 +302,14 @@ impl TxPoolService { return Err(Reject::Duplicated(tx.hash())); } - if let Some((ret, snapshot)) = self._resumeble_process_tx(tx.clone(), remote).await { - match ret { - Ok(processed) => { - if let ProcessResult::Completed(completed) = processed { - self.after_process(tx, remote, &snapshot, &Ok(completed)) - .await; - } - Ok(()) - } - Err(e) => { - self.after_process(tx, remote, &snapshot, &Err(e.clone())) - .await; - Err(e) - } - } - } else { - Ok(()) - } + self.add_tx_to_verify_queue(tx, remote).await } pub(crate) async fn process_tx( &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, - ) -> Result { + ) -> Result<(), Reject> { // non contextual verify first self.non_contextual_verify(&tx, remote)?; @@ -334,16 +317,7 @@ impl TxPoolService { return Err(Reject::Duplicated(tx.hash())); } - if let Some((ret, snapshot)) = self._process_tx(tx.clone(), remote.map(|r| r.0)).await { - self.after_process(tx, remote, &snapshot, &ret).await; - ret - } else { - // currently, the returned cycles is not been used, mock 0 if delay - Ok(Completed { - cycles: 0, - fee: Capacity::zero(), - }) - } + self.add_tx_to_verify_queue(tx, remote).await } pub(crate) async fn put_recent_reject(&self, tx_hash: &Byte32, reject: &Reject) { @@ -627,6 +601,16 @@ impl TxPoolService { self.network.ban_peer(peer, DEFAULT_BAN_TIME, reason); } + async fn add_tx_to_verify_queue( + &self, + tx: TransactionView, + remote: Option<(Cycle, PeerIndex)>, + ) -> Result<(), Reject> { + let mut queue = self.verify_queue.write().await; + queue.add_tx(tx, remote); + Ok(()) + } + async fn _resumeble_process_tx( &self, tx: TransactionView, diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index e1a851f250..3b7c04500e 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -4,7 +4,8 @@ use crate::block_assembler::{self, BlockAssembler}; use crate::callback::{Callbacks, PendingCallback, ProposedCallback, RejectCallback}; use crate::chunk_process::ChunkCommand; use crate::component::pool_map::{PoolEntry, Status}; -use crate::component::{chunk::ChunkQueue, orphan::OrphanPool}; +use crate::component::verify_queue; +use crate::component::{chunk::ChunkQueue, orphan::OrphanPool, verify_queue::VerifyQueue}; use crate::error::{handle_recv_error, handle_send_cmd_error, handle_try_send_error}; use crate::pool::TxPool; use crate::util::after_delay_window; @@ -380,6 +381,7 @@ pub struct TxPoolServiceBuilder { pub(crate) tx_relay_sender: ckb_channel::Sender, pub(crate) chunk_rx: watch::Receiver, pub(crate) chunk: Arc>, + pub(crate) verify_queue: Arc>, pub(crate) started: Arc, pub(crate) block_assembler_channel: ( mpsc::Sender, @@ -403,6 +405,7 @@ impl TxPoolServiceBuilder { let signal_receiver: CancellationToken = new_tokio_exit_rx(); let (chunk_tx, chunk_rx) = watch::channel(ChunkCommand::Resume); let chunk = Arc::new(RwLock::new(ChunkQueue::new())); + let verify_queue = Arc::new(RwLock::new(VerifyQueue::new())); let started = Arc::new(AtomicBool::new(false)); let controller = TxPoolController { @@ -430,6 +433,7 @@ impl TxPoolServiceBuilder { chunk_rx, chunk, started, + verify_queue, block_assembler_channel, }; @@ -482,6 +486,7 @@ impl TxPoolServiceBuilder { tx_relay_sender: self.tx_relay_sender, block_assembler_sender, chunk: self.chunk, + verify_queue: self.verify_queue, network, consensus, delay: Arc::new(RwLock::new(LinkedHashMap::new())), @@ -638,6 +643,7 @@ pub(crate) struct TxPoolService { pub(crate) network: NetworkController, pub(crate) tx_relay_sender: ckb_channel::Sender, pub(crate) chunk: Arc>, + pub(crate) verify_queue: Arc>, pub(crate) block_assembler_sender: mpsc::Sender, pub(crate) delay: Arc>>, pub(crate) after_delay: Arc, diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs new file mode 100644 index 0000000000..541ccd0787 --- /dev/null +++ b/tx-pool/src/verify_mgr.rs @@ -0,0 +1,61 @@ +use rand::Rng; +use tokio::sync::{mpsc, oneshot}; +use tokio::task; + +type Job = Box; + +struct Manager { + workers: Vec<( + task::JoinHandle<()>, + mpsc::UnboundedSender, + oneshot::Sender<()>, + mpsc::UnboundedReceiver<()>, + )>, +} + +impl Manager { + fn new() -> Self { + let mut workers = Vec::new(); + for _ in 0..num_cpus::get() { + let (job_sender, job_receiver) = mpsc::unbounded_channel::(); + let (exit_sender, exit_receiver) = oneshot::channel(); + let (res_sender, res_receiver) = mpsc::unbounded_channel::<()>(); + let handler = + task::spawn(async { worker(job_receiver, exit_receiver, res_sender).await }); + workers.push((handler, job_sender, exit_sender, res_receiver)); + } + Self { workers } + } + + async fn send_job(&mut self, job: Job) { + // pick a random worker from workers and send job to it + let mut rng = rand::thread_rng(); + let idx = rng.gen_range(0..self.workers.len()); + let worker = self.workers.get_mut(idx).unwrap(); + worker.1.send(job).unwrap(); + let res = worker.3.recv().await; + println!("res: {:?}", res); + } +} + +async fn worker( + mut job_receiver: mpsc::UnboundedReceiver, + mut exit_receiver: oneshot::Receiver<()>, + res_sender: mpsc::UnboundedSender<()>, +) { + loop { + tokio::select! { + job = job_receiver.recv() => { + if let Some(job) = job { + job(); + let _ = res_sender.send(()); + } else { + break; + } + } + _ = &mut exit_receiver => { + break; + } + } + } +} diff --git a/tx-pool/src/verify_worker.rs b/tx-pool/src/verify_worker.rs new file mode 100644 index 0000000000..e69de29bb2 From badfae33b45ea20d4569c1a35251514682df7523 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 27 Dec 2023 13:50:05 +0800 Subject: [PATCH 002/294] use multi_index_map in verify queue --- tx-pool/src/component/verify_queue.rs | 82 +++++++++++++-------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/tx-pool/src/component/verify_queue.rs b/tx-pool/src/component/verify_queue.rs index b5875abfe3..9b8f5dc21a 100644 --- a/tx-pool/src/component/verify_queue.rs +++ b/tx-pool/src/component/verify_queue.rs @@ -1,12 +1,15 @@ +extern crate rustc_hash; +extern crate slab; use ckb_network::PeerIndex; use ckb_types::{ core::{Cycle, TransactionView}, packed::ProposalShortId, }; -use ckb_util::{shrink_to_fit, LinkedHashMap}; +use ckb_util::shrink_to_fit; +use multi_index_map::MultiIndexMap; -const SHRINK_THRESHOLD: usize = 100; -pub(crate) const DEFAULT_MAX_CHUNK_TRANSACTIONS: usize = 100; +const DEFAULT_MAX_VERIFY_TRANSACTIONS: usize = 100; +const SHRINK_THRESHOLD: usize = 120; #[derive(Debug, Clone, Eq)] pub(crate) struct Entry { @@ -20,18 +23,32 @@ impl PartialEq for Entry { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum VerifyStatus { + Fresh, + Verifying, + Completed, +} + +#[derive(MultiIndexMap, Clone)] +pub struct VerifyEntry { + #[multi_index(hashed_unique)] + pub id: ProposalShortId, + #[multi_index(hashed_non_unique)] + pub status: VerifyStatus, + // other sort key + pub inner: Entry, +} + #[derive(Default)] pub(crate) struct VerifyQueue { - inner: LinkedHashMap, - // memory last pop value for atomic reset - front: Option, + inner: MultiIndexVerifyEntryMap, } impl VerifyQueue { pub(crate) fn new() -> Self { VerifyQueue { - inner: LinkedHashMap::default(), - front: None, + inner: MultiIndexVerifyEntryMap::default(), } } @@ -45,49 +62,26 @@ impl VerifyQueue { } pub fn is_full(&self) -> bool { - self.len() > DEFAULT_MAX_CHUNK_TRANSACTIONS + self.len() > DEFAULT_MAX_VERIFY_TRANSACTIONS } pub fn contains_key(&self, id: &ProposalShortId) -> bool { - self.front - .as_ref() - .map(|e| e.tx.proposal_short_id()) - .as_ref() - == Some(id) - || self.inner.contains_key(id) + self.inner.get_by_id(id).is_some() } pub fn shrink_to_fit(&mut self) { shrink_to_fit!(self.inner, SHRINK_THRESHOLD); } - pub fn clean_front(&mut self) { - self.front = None; - } - - pub fn pop_front(&mut self) -> Option { - if let Some(entry) = &self.front { - Some(entry.clone()) - } else { - match self.inner.pop_front() { - Some((_id, entry)) => { - self.front = Some(entry.clone()); - Some(entry) - } - None => None, - } - } - } - - pub fn remove_chunk_tx(&mut self, id: &ProposalShortId) -> Option { - let ret = self.inner.remove(id); + pub fn remove_tx(&mut self, id: &ProposalShortId) -> Option { + let ret = self.inner.remove_by_id(id); self.shrink_to_fit(); - ret + Some(ret.unwrap().inner) } - pub fn remove_chunk_txs(&mut self, ids: impl Iterator) { + pub fn remove_txs(&mut self, ids: impl Iterator) { for id in ids { - self.inner.remove(&id); + self.inner.remove_by_id(&id); } self.shrink_to_fit(); } @@ -98,16 +92,18 @@ impl VerifyQueue { if self.contains_key(&tx.proposal_short_id()) { return false; } - - self.inner - .insert(tx.proposal_short_id(), Entry { tx, remote }) - .is_none() + let entry = Entry { tx, remote }; + self.inner.insert(VerifyEntry { + id: tx.proposal_short_id(), + status: VerifyStatus::Fresh, + inner: entry.clone(), + }); + true } /// Clears the map, removing all elements. pub fn clear(&mut self) { self.inner.clear(); - self.clean_front(); self.shrink_to_fit() } } From 5f13960e1f52831260efab88438e7da3711fac97 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 27 Dec 2023 15:34:36 +0800 Subject: [PATCH 003/294] backup --- Cargo.lock | 9 +- script/Cargo.toml | 1 + script/src/types.rs | 31 ++++ tx-pool/src/component/mod.rs | 1 - tx-pool/src/lib.rs | 1 + tx-pool/src/process.rs | 161 ++++++++++++++++++-- tx-pool/src/service.rs | 28 ++-- tx-pool/src/verify_mgr.rs | 153 ++++++++++++++----- tx-pool/src/{component => }/verify_queue.rs | 24 ++- 9 files changed, 330 insertions(+), 79 deletions(-) rename tx-pool/src/{component => }/verify_queue.rs (80%) diff --git a/Cargo.lock b/Cargo.lock index b607cc836d..564e05afcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1427,6 +1427,7 @@ dependencies = [ "serde", "tempfile", "tiny-keccak", + "tokio", ] [[package]] @@ -5119,9 +5120,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -5138,9 +5139,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", diff --git a/script/Cargo.toml b/script/Cargo.toml index 75895fc25e..60c989b930 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -28,6 +28,7 @@ ckb-logger = { path = "../util/logger", version = "= 0.114.0-pre", optional = tr serde = { version = "1.0", features = ["derive"] } ckb-error = { path = "../error", version = "= 0.114.0-pre" } ckb-chain-spec = { path = "../spec", version = "= 0.114.0-pre" } +tokio = { version = "1.35.0", features = ["full"] } [dev-dependencies] proptest = "1.0" diff --git a/script/src/types.rs b/script/src/types.rs index b8b625b84b..ba393e1894 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -12,6 +12,7 @@ use ckb_vm::{ use serde::{Deserialize, Serialize}; use std::fmt; use std::sync::{Arc, Mutex}; +use tokio::sync::mpsc::{self, unbounded_channel}; #[cfg(has_asm)] use ckb_vm::machine::asm::{AsmCoreMachine, AsmMachine}; @@ -230,6 +231,36 @@ impl ResumableMachine { } } +async fn run_with_pause( + mut machine: Machine, + mut pause_signal: mpsc::UnboundedReceiver<()>, +) -> Result<(Result, Machine), Error> { + let pause = machine.machine.pause(); + let (res_tx, mut res_rx) = unbounded_channel(); + let jh = tokio::spawn(async move { + let result = machine.run(); + res_tx.send(result).unwrap(); + return machine; + }); + + loop { + tokio::select! { + _ = pause_signal.recv() => { + eprintln!("received pause signal ..."); + pause.interrupt(); + } + Some(res) = res_rx.recv() => { + eprintln!("finished ..."); + let machine = jh.await.unwrap(); + return Ok((res, machine)); + } + else => { + eprintln!("no signal ..."); + } + } + } +} + #[cfg(has_asm)] pub(crate) fn set_vm_max_cycles(vm: &mut Machine, cycles: Cycle) { vm.set_max_cycles(cycles) diff --git a/tx-pool/src/component/mod.rs b/tx-pool/src/component/mod.rs index 4cf8685f32..e5b8ab3cfc 100644 --- a/tx-pool/src/component/mod.rs +++ b/tx-pool/src/component/mod.rs @@ -8,7 +8,6 @@ pub(crate) mod orphan; pub(crate) mod pool_map; pub(crate) mod recent_reject; pub(crate) mod sort_key; -pub(crate) mod verify_queue; #[cfg(test)] mod tests; diff --git a/tx-pool/src/lib.rs b/tx-pool/src/lib.rs index 2f6d4af83b..043c10a511 100644 --- a/tx-pool/src/lib.rs +++ b/tx-pool/src/lib.rs @@ -13,6 +13,7 @@ mod process; pub mod service; mod util; mod verify_mgr; +pub mod verify_queue; pub use ckb_jsonrpc_types::BlockTemplate; pub use component::entry::TxEntry; diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 700330882b..ed284cf5d7 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -191,7 +191,7 @@ impl TxPoolService { } pub(crate) async fn chunk_contains(&self, tx: &TransactionView) -> bool { - let chunk = self.chunk.read().await; + let chunk = self.verify_queue.read().await; chunk.contains_key(&tx.proposal_short_id()) } @@ -293,23 +293,40 @@ impl TxPoolService { // non contextual verify first self.non_contextual_verify(&tx, remote)?; - if self.chunk_contains(&tx).await { + if self.orphan_contains(&tx).await { + debug!("reject tx {} already in orphan pool", tx.hash()); return Err(Reject::Duplicated(tx.hash())); } - if self.orphan_contains(&tx).await { - debug!("reject tx {} already in orphan pool", tx.hash()); + if self.chunk_contains(&tx).await { return Err(Reject::Duplicated(tx.hash())); } - self.add_tx_to_verify_queue(tx, remote).await + if let Some((ret, snapshot)) = self.verify_or_add_to_queue(tx.clone(), remote).await { + match ret { + Ok(processed) => { + if let ProcessResult::Completed(completed) = processed { + self.after_process(tx, remote, &snapshot, &Ok(completed)) + .await; + } + Ok(()) + } + Err(e) => { + self.after_process(tx, remote, &snapshot, &Err(e.clone())) + .await; + Err(e) + } + } + } else { + Ok(()) + } } pub(crate) async fn process_tx( &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, - ) -> Result<(), Reject> { + ) -> Result { // non contextual verify first self.non_contextual_verify(&tx, remote)?; @@ -317,7 +334,16 @@ impl TxPoolService { return Err(Reject::Duplicated(tx.hash())); } - self.add_tx_to_verify_queue(tx, remote).await + if let Some((ret, snapshot)) = self._process_tx(tx.clone(), remote.map(|r| r.0)).await { + self.after_process(tx, remote, &snapshot, &ret).await; + ret + } else { + // currently, the returned cycles is not been used, mock 0 if delay + Ok(Completed { + cycles: 0, + fee: Capacity::zero(), + }) + } } pub(crate) async fn put_recent_reject(&self, tx_hash: &Byte32, reject: &Reject) { @@ -601,14 +627,121 @@ impl TxPoolService { self.network.ban_peer(peer, DEFAULT_BAN_TIME, reason); } - async fn add_tx_to_verify_queue( + async fn verify_or_add_to_queue( &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, - ) -> Result<(), Reject> { - let mut queue = self.verify_queue.write().await; - queue.add_tx(tx, remote); - Ok(()) + ) -> Option<(Result, Arc)> { + let tx_hash = tx.hash(); + + let (ret, snapshot) = self.pre_check(&tx).await; + let (tip_hash, rtx, status, fee, tx_size) = try_or_return_with_snapshot!(ret, snapshot); + + if self.is_in_delay_window(&snapshot) { + let mut delay = self.delay.write().await; + if delay.len() < DELAY_LIMIT { + delay.insert(tx.proposal_short_id(), tx); + } + return None; + } + + let cached = self.fetch_tx_verify_cache(&tx_hash).await; + let tip_header = snapshot.tip_header(); + let tx_env = Arc::new(status.with_env(tip_header)); + + let data_loader = snapshot.as_data_loader(); + + let completed = if let Some(ref entry) = cached { + match entry { + CacheEntry::Completed(completed) => { + let ret = TimeRelativeTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&self.consensus), + data_loader, + tx_env, + ) + .verify() + .map_err(Reject::Verification); + try_or_return_with_snapshot!(ret, snapshot); + *completed + } + CacheEntry::Suspended(_) => { + return Some((Ok(ProcessResult::Suspended), snapshot)); + } + } + } else { + let is_chunk_full = self.is_chunk_full().await; + + let ret = block_in_place(|| { + let verifier = ContextualTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&self.consensus), + data_loader, + tx_env, + ); + + let (ret, fee) = verifier + .resumable_verify(self.tx_pool_config.max_tx_verify_cycles) + .map_err(Reject::Verification)?; + + match ret { + ScriptVerifyResult::Completed(cycles) => { + if let Err(e) = DaoScriptSizeVerifier::new( + Arc::clone(&rtx), + Arc::clone(&self.consensus), + snapshot.as_data_loader(), + ) + .verify() + { + return Err(Reject::Verification(e)); + } + if let Some((declared, _)) = remote { + if declared != cycles { + return Err(Reject::DeclaredWrongCycles(declared, cycles)); + } + } + Ok(CacheEntry::completed(cycles, fee)) + } + ScriptVerifyResult::Suspended(state) => { + if is_chunk_full { + Err(Reject::Full("chunk".to_owned())) + } else { + let snap = Arc::new(state.try_into().map_err(Reject::Verification)?); + Ok(CacheEntry::suspended(snap, fee)) + } + } + } + }); + + let entry = try_or_return_with_snapshot!(ret, snapshot); + match entry { + cached @ CacheEntry::Suspended(_) => { + let ret = self + .enqueue_suspended_tx(rtx.transaction.clone(), cached, remote) + .await; + try_or_return_with_snapshot!(ret, snapshot); + return Some((Ok(ProcessResult::Suspended), snapshot)); + } + CacheEntry::Completed(completed) => completed, + } + }; + + let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); + + let (ret, submit_snapshot) = self.submit_entry(tip_hash, entry, status).await; + try_or_return_with_snapshot!(ret, submit_snapshot); + + self.notify_block_assembler(status).await; + if cached.is_none() { + // update cache + let txs_verify_cache = Arc::clone(&self.txs_verify_cache); + tokio::spawn(async move { + let mut guard = txs_verify_cache.write().await; + guard.put(tx_hash, CacheEntry::Completed(completed)); + }); + } + + Some((Ok(ProcessResult::Completed(completed)), submit_snapshot)) } async fn _resumeble_process_tx( @@ -730,7 +863,7 @@ impl TxPoolService { } pub(crate) async fn is_chunk_full(&self) -> bool { - self.chunk.read().await.is_full() + self.verify_queue.read().await.is_full() } pub(crate) async fn enqueue_suspended_tx( @@ -740,7 +873,7 @@ impl TxPoolService { remote: Option<(Cycle, PeerIndex)>, ) -> Result<(), Reject> { let tx_hash = tx.hash(); - let mut chunk = self.chunk.write().await; + let mut chunk = self.verify_queue.write().await; if chunk.add_tx(tx, remote) { let mut guard = self.txs_verify_cache.write().await; guard.put(tx_hash, cached); diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index 3b7c04500e..bfd10ee9c3 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -2,13 +2,14 @@ use crate::block_assembler::{self, BlockAssembler}; use crate::callback::{Callbacks, PendingCallback, ProposedCallback, RejectCallback}; -use crate::chunk_process::ChunkCommand; use crate::component::pool_map::{PoolEntry, Status}; -use crate::component::verify_queue; -use crate::component::{chunk::ChunkQueue, orphan::OrphanPool, verify_queue::VerifyQueue}; +use crate::component::{chunk::ChunkQueue, orphan::OrphanPool}; use crate::error::{handle_recv_error, handle_send_cmd_error, handle_try_send_error}; use crate::pool::TxPool; use crate::util::after_delay_window; +use crate::verify_mgr::ChunkCommand; +use crate::verify_mgr::VerifyMgr; +use crate::verify_queue::VerifyQueue; use ckb_app_config::{BlockAssemblerConfig, TxPoolConfig}; use ckb_async_runtime::Handle; use ckb_chain_spec::consensus::Consensus; @@ -379,9 +380,10 @@ pub struct TxPoolServiceBuilder { pub(crate) signal_receiver: CancellationToken, pub(crate) handle: Handle, pub(crate) tx_relay_sender: ckb_channel::Sender, - pub(crate) chunk_rx: watch::Receiver, + //pub(crate) chunk_rx: watch::Receiver, pub(crate) chunk: Arc>, pub(crate) verify_queue: Arc>, + pub(crate) verify_mgr: Arc>, pub(crate) started: Arc, pub(crate) block_assembler_channel: ( mpsc::Sender, @@ -406,6 +408,11 @@ impl TxPoolServiceBuilder { let (chunk_tx, chunk_rx) = watch::channel(ChunkCommand::Resume); let chunk = Arc::new(RwLock::new(ChunkQueue::new())); let verify_queue = Arc::new(RwLock::new(VerifyQueue::new())); + let verify_mgr = Arc::new(RwLock::new(VerifyMgr::new( + chunk_rx, + signal_receiver.clone(), + verify_queue.clone(), + ))); let started = Arc::new(AtomicBool::new(false)); let controller = TxPoolController { @@ -430,10 +437,11 @@ impl TxPoolServiceBuilder { signal_receiver, handle: handle.clone(), tx_relay_sender, - chunk_rx, chunk, + //chunk_rx, started, verify_queue, + verify_mgr, block_assembler_channel, }; @@ -493,14 +501,10 @@ impl TxPoolServiceBuilder { after_delay: Arc::new(AtomicBool::new(after_delay_window)), }; - let signal_receiver = self.signal_receiver.clone(); - let chunk_process = crate::chunk_process::ChunkProcess::new( - service.clone(), - self.chunk_rx, - signal_receiver, - ); + let verify_mgr = Arc::clone(&self.verify_mgr); + self.handle + .spawn(async move { verify_mgr.write().await.run().await }); - self.handle.spawn(async move { chunk_process.run().await }); let mut receiver = self.receiver; let mut reorg_receiver = self.reorg_receiver; let handle_clone = self.handle.clone(); diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 541ccd0787..dffa79a883 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,61 +1,130 @@ -use rand::Rng; -use tokio::sync::{mpsc, oneshot}; +use ckb_stop_handler::CancellationToken; +use std::sync::Arc; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::sync::oneshot::Sender; +use tokio::sync::{mpsc, oneshot, watch, RwLock}; use tokio::task; -type Job = Box; +use crate::verify_queue::{Entry, VerifyQueue}; -struct Manager { - workers: Vec<( - task::JoinHandle<()>, - mpsc::UnboundedSender, - oneshot::Sender<()>, - mpsc::UnboundedReceiver<()>, +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum ChunkCommand { + Suspend, + Resume, +} +pub struct VerifyMgr { + pub workers: Vec<( + tokio::task::JoinHandle<()>, + UnboundedSender, + Sender<()>, + UnboundedReceiver<()>, )>, + pub chunk_rx: watch::Receiver, + pub current_state: ChunkCommand, + pub signal_exit: CancellationToken, + pub verify_queue: Arc>, } -impl Manager { - fn new() -> Self { - let mut workers = Vec::new(); - for _ in 0..num_cpus::get() { - let (job_sender, job_receiver) = mpsc::unbounded_channel::(); - let (exit_sender, exit_receiver) = oneshot::channel(); - let (res_sender, res_receiver) = mpsc::unbounded_channel::<()>(); - let handler = - task::spawn(async { worker(job_receiver, exit_receiver, res_sender).await }); - workers.push((handler, job_sender, exit_sender, res_receiver)); +impl VerifyMgr { + pub fn new( + chunk_rx: watch::Receiver, + signal_exit: CancellationToken, + verify_queue: Arc>, + ) -> Self { + Self { + workers: vec![], + chunk_rx, + current_state: ChunkCommand::Resume, + signal_exit, + verify_queue, + } + } + + pub async fn try_process(&mut self) -> bool { + if let Some(entry) = self.verify_queue.write().await.get_first() { + println!("entry: {:?}", entry); + if self.workers.len() <= 10 { + let (job_sender, job_receiver) = mpsc::unbounded_channel::(); + let (exit_sender, exit_receiver) = oneshot::channel(); + let (res_sender, res_receiver) = mpsc::unbounded_channel::<()>(); + let mut worker = Worker::new(job_receiver, exit_receiver, res_sender); + let handle = task::spawn(async move { worker.run().await }); + job_sender.send(entry).unwrap(); + self.workers + .push((handle, job_sender, exit_sender, res_receiver)); + } else { + return false; + } + return true; } - Self { workers } + false } - async fn send_job(&mut self, job: Job) { - // pick a random worker from workers and send job to it - let mut rng = rand::thread_rng(); - let idx = rng.gen_range(0..self.workers.len()); - let worker = self.workers.get_mut(idx).unwrap(); - worker.1.send(job).unwrap(); - let res = worker.3.recv().await; - println!("res: {:?}", res); + pub async fn run(&mut self) { + let mut interval = tokio::time::interval(std::time::Duration::from_micros(1500)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { + tokio::select! { + _ = self.chunk_rx.changed() => { + self.current_state = self.chunk_rx.borrow().to_owned(); + if matches!(self.current_state, ChunkCommand::Resume) { + let stop = self.try_process().await; + if stop { + break; + } + } + }, + _ = interval.tick() => { + if matches!(self.current_state, ChunkCommand::Resume) { + let stop = self.try_process().await; + if stop { + break; + } + } + }, + _ = self.signal_exit.cancelled() => { + break; + }, + else => break, + } + } } } -async fn worker( - mut job_receiver: mpsc::UnboundedReceiver, - mut exit_receiver: oneshot::Receiver<()>, +struct Worker { + job_receiver: mpsc::UnboundedReceiver, + exit_receiver: oneshot::Receiver<()>, res_sender: mpsc::UnboundedSender<()>, -) { - loop { - tokio::select! { - job = job_receiver.recv() => { - if let Some(job) = job { - job(); - let _ = res_sender.send(()); - } else { +} + +impl Worker { + pub fn new( + job_receiver: mpsc::UnboundedReceiver, + exit_receiver: oneshot::Receiver<()>, + res_sender: mpsc::UnboundedSender<()>, + ) -> Self { + Self { + job_receiver, + exit_receiver, + res_sender, + } + } + + pub async fn run(&mut self) { + loop { + tokio::select! { + entry = self.job_receiver.recv() => { + if let Some(entry) = entry { + eprintln!("entry: {:?}", entry); + let _ = self.res_sender.send(()); + } else { + break; + } + } + _ = &mut self.exit_receiver => { break; } } - _ = &mut exit_receiver => { - break; - } } } } diff --git a/tx-pool/src/component/verify_queue.rs b/tx-pool/src/verify_queue.rs similarity index 80% rename from tx-pool/src/component/verify_queue.rs rename to tx-pool/src/verify_queue.rs index 9b8f5dc21a..756edd806b 100644 --- a/tx-pool/src/component/verify_queue.rs +++ b/tx-pool/src/verify_queue.rs @@ -3,7 +3,7 @@ extern crate slab; use ckb_network::PeerIndex; use ckb_types::{ core::{Cycle, TransactionView}, - packed::ProposalShortId, + packed::{ProposalShortId, Time}, }; use ckb_util::shrink_to_fit; use multi_index_map::MultiIndexMap; @@ -12,7 +12,7 @@ const DEFAULT_MAX_VERIFY_TRANSACTIONS: usize = 100; const SHRINK_THRESHOLD: usize = 120; #[derive(Debug, Clone, Eq)] -pub(crate) struct Entry { +pub struct Entry { pub(crate) tx: TransactionView, pub(crate) remote: Option<(Cycle, PeerIndex)>, } @@ -36,12 +36,13 @@ pub struct VerifyEntry { pub id: ProposalShortId, #[multi_index(hashed_non_unique)] pub status: VerifyStatus, + #[multi_index(ordered_non_unique)] + pub added_time: u64, // other sort key pub inner: Entry, } -#[derive(Default)] -pub(crate) struct VerifyQueue { +pub struct VerifyQueue { inner: MultiIndexVerifyEntryMap, } @@ -86,17 +87,28 @@ impl VerifyQueue { self.shrink_to_fit(); } + pub fn get_first(&self) -> Option { + self.inner + .iter_by_added_time() + .filter(|e| e.status == VerifyStatus::Fresh) + .next() + .map(|entry| entry.inner.clone()) + } + /// If the queue did not have this tx present, true is returned. /// If the queue did have this tx present, false is returned. pub fn add_tx(&mut self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>) -> bool { if self.contains_key(&tx.proposal_short_id()) { return false; } - let entry = Entry { tx, remote }; self.inner.insert(VerifyEntry { id: tx.proposal_short_id(), status: VerifyStatus::Fresh, - inner: entry.clone(), + added_time: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("timestamp") + .as_millis() as u64, + inner: Entry { tx, remote }, }); true } From 6de0ad35863d0acda635ec817aa4e3223a88e4c0 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 28 Dec 2023 12:43:40 +0800 Subject: [PATCH 004/294] more on worker --- tx-pool/src/verify_mgr.rs | 204 ++++++++++++++++++++++++-------------- 1 file changed, 132 insertions(+), 72 deletions(-) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index dffa79a883..91c8d972ff 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,9 +1,9 @@ +use ckb_channel::Receiver; use ckb_stop_handler::CancellationToken; use std::sync::Arc; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; -use tokio::sync::oneshot::Sender; -use tokio::sync::{mpsc, oneshot, watch, RwLock}; -use tokio::task; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; +use tokio::sync::{watch, RwLock}; +use tokio::task::JoinHandle; use crate::verify_queue::{Entry, VerifyQueue}; @@ -11,14 +11,93 @@ use crate::verify_queue::{Entry, VerifyQueue}; pub enum ChunkCommand { Suspend, Resume, + Shutdown, } + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum VerifyNotify { + Start { short_id: String }, + Done { short_id: String }, +} + +struct Worker { + tasks: Arc>, + inbox: Receiver, + outbox: UnboundedSender, +} + +impl Clone for Worker { + fn clone(&self) -> Self { + Self { + tasks: Arc::clone(&self.tasks), + inbox: self.inbox.clone(), + outbox: self.outbox.clone(), + } + } +} + +impl Worker { + pub fn new( + tasks: Arc>, + inbox: Receiver, + outbox: UnboundedSender, + ) -> Self { + Worker { + tasks, + inbox, + outbox, + } + } + + /// start handle tasks + pub fn start(self) -> JoinHandle<()> { + tokio::spawn(async move { + let mut pause = false; + loop { + let msg = match self.inbox.try_recv() { + Ok(msg) => Some(msg), + Err(_err) => None, + }; + // check command + if Some(ChunkCommand::Shutdown) == msg { + return; + } + + if Some(ChunkCommand::Suspend) == msg { + pause = true; + continue; + } + + if Some(ChunkCommand::Resume) == msg { + pause = false; + } + // pick a entry to run verify + if !pause { + let entry = match self.tasks.write().await.get_first() { + Some(entry) => entry, + None => return, + }; + + self.run_verify_tx(&entry) + } + } + }) + } + + fn run_verify_tx(&self, entry: &Entry) { + let short_id = entry.tx.proposal_short_id(); + self.outbox + .send(VerifyNotify::Start { + short_id: short_id.to_string(), + }) + .unwrap(); + } +} + pub struct VerifyMgr { - pub workers: Vec<( - tokio::task::JoinHandle<()>, - UnboundedSender, - Sender<()>, - UnboundedReceiver<()>, - )>, + workers: Vec<(ckb_channel::Sender, Worker)>, + worker_notify: UnboundedReceiver, + join_handles: Option>>, pub chunk_rx: watch::Receiver, pub current_state: ChunkCommand, pub signal_exit: CancellationToken, @@ -31,8 +110,21 @@ impl VerifyMgr { signal_exit: CancellationToken, verify_queue: Arc>, ) -> Self { + let (notify_tx, notify_rx) = unbounded_channel::(); + let workers: Vec<_> = (0..4) + .map({ + let tasks = Arc::clone(&verify_queue); + move |_| { + let (command_tx, command_rx) = ckb_channel::unbounded(); + let worker = Worker::new(Arc::clone(&tasks), command_rx, notify_tx.clone()); + (command_tx, worker) + } + }) + .collect(); Self { - workers: vec![], + workers, + worker_notify: notify_rx, + join_handles: None, chunk_rx, current_state: ChunkCommand::Resume, signal_exit, @@ -40,27 +132,28 @@ impl VerifyMgr { } } - pub async fn try_process(&mut self) -> bool { - if let Some(entry) = self.verify_queue.write().await.get_first() { - println!("entry: {:?}", entry); - if self.workers.len() <= 10 { - let (job_sender, job_receiver) = mpsc::unbounded_channel::(); - let (exit_sender, exit_receiver) = oneshot::channel(); - let (res_sender, res_receiver) = mpsc::unbounded_channel::<()>(); - let mut worker = Worker::new(job_receiver, exit_receiver, res_sender); - let handle = task::spawn(async move { worker.run().await }); - job_sender.send(entry).unwrap(); - self.workers - .push((handle, job_sender, exit_sender, res_receiver)); - } else { - return false; - } - return true; + async fn resume(&mut self) { + for worker in self.workers.iter_mut() { + worker.0.send(ChunkCommand::Resume).unwrap(); } - false } - pub async fn run(&mut self) { + async fn suspend(&mut self) { + for worker in self.workers.iter_mut() { + worker.0.send(ChunkCommand::Suspend).unwrap(); + } + } + + fn start_workers(&mut self) { + let mut join_handles = Vec::new(); + for w in self.workers.iter_mut() { + let h = w.1.clone().start(); + join_handles.push(h); + } + self.join_handles.replace(join_handles); + } + + async fn start_loop(&mut self) { let mut interval = tokio::time::interval(std::time::Duration::from_micros(1500)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { @@ -68,20 +161,20 @@ impl VerifyMgr { _ = self.chunk_rx.changed() => { self.current_state = self.chunk_rx.borrow().to_owned(); if matches!(self.current_state, ChunkCommand::Resume) { - let stop = self.try_process().await; - if stop { - break; - } + self.resume().await; + } + if matches!(self.current_state, ChunkCommand::Suspend) { + self.suspend().await; } }, _ = interval.tick() => { if matches!(self.current_state, ChunkCommand::Resume) { - let stop = self.try_process().await; - if stop { - break; - } + self.resume().await; } }, + res = self.worker_notify.recv() => { + eprintln!("res: {:?}", res); + } _ = self.signal_exit.cancelled() => { break; }, @@ -89,42 +182,9 @@ impl VerifyMgr { } } } -} - -struct Worker { - job_receiver: mpsc::UnboundedReceiver, - exit_receiver: oneshot::Receiver<()>, - res_sender: mpsc::UnboundedSender<()>, -} - -impl Worker { - pub fn new( - job_receiver: mpsc::UnboundedReceiver, - exit_receiver: oneshot::Receiver<()>, - res_sender: mpsc::UnboundedSender<()>, - ) -> Self { - Self { - job_receiver, - exit_receiver, - res_sender, - } - } pub async fn run(&mut self) { - loop { - tokio::select! { - entry = self.job_receiver.recv() => { - if let Some(entry) = entry { - eprintln!("entry: {:?}", entry); - let _ = self.res_sender.send(()); - } else { - break; - } - } - _ = &mut self.exit_receiver => { - break; - } - } - } + self.start_workers(); + self.start_loop().await; } } From c13e6b50fb68280469e108b115f0e724dca7177c Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 28 Dec 2023 19:40:40 +0800 Subject: [PATCH 005/294] a draft version queue worker_mgr --- tx-pool/src/verify_mgr.rs | 84 +++++++++++++++++++------------------ tx-pool/src/verify_queue.rs | 11 ++++- 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 91c8d972ff..8a0be7002d 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,6 +1,7 @@ use ckb_channel::Receiver; use ckb_stop_handler::CancellationToken; use std::sync::Arc; +use std::time::Duration; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::{watch, RwLock}; use tokio::task::JoinHandle; @@ -54,31 +55,42 @@ impl Worker { tokio::spawn(async move { let mut pause = false; loop { - let msg = match self.inbox.try_recv() { - Ok(msg) => Some(msg), - Err(_err) => None, + match self.inbox.try_recv() { + Ok(msg) => match msg { + ChunkCommand::Shutdown => { + break; + } + ChunkCommand::Suspend => { + pause = true; + continue; + } + ChunkCommand::Resume => { + pause = false; + } + }, + Err(err) => { + if !err.is_empty() { + eprintln!("error: {:?}", err); + break; + } + } }; - // check command - if Some(ChunkCommand::Shutdown) == msg { - return; - } - if Some(ChunkCommand::Suspend) == msg { - pause = true; - continue; - } - - if Some(ChunkCommand::Resume) == msg { - pause = false; - } - // pick a entry to run verify if !pause { - let entry = match self.tasks.write().await.get_first() { + if self.tasks.read().await.get_first().is_none() { + // sleep for 100 ms + tokio::time::sleep(Duration::from_millis(100)).await; + continue; + } + // pick a entry to run verify + let entry = match self.tasks.write().await.pop_first() { Some(entry) => entry, - None => return, + None => continue, }; - self.run_verify_tx(&entry) + } else { + // sleep for 100 ms + tokio::time::sleep(Duration::from_millis(100)).await; } } }) @@ -132,15 +144,14 @@ impl VerifyMgr { } } - async fn resume(&mut self) { - for worker in self.workers.iter_mut() { - worker.0.send(ChunkCommand::Resume).unwrap(); - } - } - - async fn suspend(&mut self) { + async fn send_command(&mut self, command: ChunkCommand) { + eprintln!( + "send workers {:?} command: {:?}", + std::time::SystemTime::now(), + command + ); for worker in self.workers.iter_mut() { - worker.0.send(ChunkCommand::Suspend).unwrap(); + worker.0.send(command.clone()).unwrap(); } } @@ -154,31 +165,24 @@ impl VerifyMgr { } async fn start_loop(&mut self) { - let mut interval = tokio::time::interval(std::time::Duration::from_micros(1500)); + let mut interval = tokio::time::interval(Duration::from_micros(1500)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { tokio::select! { _ = self.chunk_rx.changed() => { self.current_state = self.chunk_rx.borrow().to_owned(); - if matches!(self.current_state, ChunkCommand::Resume) { - self.resume().await; - } - if matches!(self.current_state, ChunkCommand::Suspend) { - self.suspend().await; - } - }, - _ = interval.tick() => { - if matches!(self.current_state, ChunkCommand::Resume) { - self.resume().await; - } + self.send_command(self.current_state.clone()).await; }, res = self.worker_notify.recv() => { eprintln!("res: {:?}", res); } _ = self.signal_exit.cancelled() => { + self.send_command(ChunkCommand::Shutdown).await; break; }, - else => break, + _ = interval.tick() => { + tokio::time::sleep(Duration::from_millis(50)).await; + } } } } diff --git a/tx-pool/src/verify_queue.rs b/tx-pool/src/verify_queue.rs index 756edd806b..5ae0729916 100644 --- a/tx-pool/src/verify_queue.rs +++ b/tx-pool/src/verify_queue.rs @@ -3,7 +3,7 @@ extern crate slab; use ckb_network::PeerIndex; use ckb_types::{ core::{Cycle, TransactionView}, - packed::{ProposalShortId, Time}, + packed::ProposalShortId, }; use ckb_util::shrink_to_fit; use multi_index_map::MultiIndexMap; @@ -87,6 +87,15 @@ impl VerifyQueue { self.shrink_to_fit(); } + pub fn pop_first(&mut self) -> Option { + if let Some(entry) = self.get_first() { + self.remove_tx(&entry.tx.proposal_short_id()); + Some(entry) + } else { + None + } + } + pub fn get_first(&self) -> Option { self.inner .iter_by_added_time() From 53a4d3ca28e5c2c1345d295806271099d87e3691 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 3 Jan 2024 00:33:26 +0800 Subject: [PATCH 006/294] more on verify mgr --- tx-pool/src/service.rs | 17 ++- tx-pool/src/verify_mgr.rs | 234 +++++++++++++++++++++++++++++++++++--- 2 files changed, 225 insertions(+), 26 deletions(-) diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index bfd10ee9c3..401c08e017 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -380,10 +380,9 @@ pub struct TxPoolServiceBuilder { pub(crate) signal_receiver: CancellationToken, pub(crate) handle: Handle, pub(crate) tx_relay_sender: ckb_channel::Sender, - //pub(crate) chunk_rx: watch::Receiver, + pub(crate) chunk_rx: watch::Receiver, pub(crate) chunk: Arc>, pub(crate) verify_queue: Arc>, - pub(crate) verify_mgr: Arc>, pub(crate) started: Arc, pub(crate) block_assembler_channel: ( mpsc::Sender, @@ -408,11 +407,6 @@ impl TxPoolServiceBuilder { let (chunk_tx, chunk_rx) = watch::channel(ChunkCommand::Resume); let chunk = Arc::new(RwLock::new(ChunkQueue::new())); let verify_queue = Arc::new(RwLock::new(VerifyQueue::new())); - let verify_mgr = Arc::new(RwLock::new(VerifyMgr::new( - chunk_rx, - signal_receiver.clone(), - verify_queue.clone(), - ))); let started = Arc::new(AtomicBool::new(false)); let controller = TxPoolController { @@ -438,10 +432,9 @@ impl TxPoolServiceBuilder { handle: handle.clone(), tx_relay_sender, chunk, - //chunk_rx, + chunk_rx, started, verify_queue, - verify_mgr, block_assembler_channel, }; @@ -501,7 +494,11 @@ impl TxPoolServiceBuilder { after_delay: Arc::new(AtomicBool::new(after_delay_window)), }; - let verify_mgr = Arc::clone(&self.verify_mgr); + let verify_mgr = Arc::new(RwLock::new(VerifyMgr::new( + service.clone(), + self.chunk_rx, + self.signal_receiver.clone(), + ))); self.handle .spawn(async move { verify_mgr.write().await.run().await }); diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 8a0be7002d..a28fc64e80 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,3 +1,7 @@ +use crate::component::entry::TxEntry; +use crate::try_or_return_with_snapshot; + +use crate::{error::Reject, service::TxPoolService}; use ckb_channel::Receiver; use ckb_stop_handler::CancellationToken; use std::sync::Arc; @@ -6,7 +10,28 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::{watch, RwLock}; use tokio::task::JoinHandle; -use crate::verify_queue::{Entry, VerifyQueue}; +use crate::verify_queue::{self, Entry, VerifyQueue}; + +use ckb_chain_spec::consensus::Consensus; +use ckb_error::Error; +use ckb_logger::info; +use ckb_snapshot::Snapshot; +use ckb_store::data_loader_wrapper::AsDataLoader; +use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; +use ckb_types::{ + core::{cell::ResolvedTransaction, Cycle}, + packed::Byte32, +}; +use ckb_verification::cache::TxVerificationCache; +use ckb_verification::{ + cache::{CacheEntry, Completed}, + ContextualWithoutScriptTransactionVerifier, DaoScriptSizeVerifier, ScriptError, ScriptVerifier, + ScriptVerifyResult, ScriptVerifyState, TimeRelativeTransactionVerifier, TransactionSnapshot, + TxVerifyEnv, +}; +use tokio::task::block_in_place; + +type Stop = bool; #[derive(Eq, PartialEq, Clone, Debug)] pub enum ChunkCommand { @@ -15,16 +40,29 @@ pub enum ChunkCommand { Shutdown, } -#[derive(Eq, PartialEq, Clone, Debug)] +#[derive(Clone, Debug)] pub enum VerifyNotify { - Start { short_id: String }, - Done { short_id: String }, + Start { + short_id: String, + }, + Done { + short_id: String, + result: Option<(Result, Arc)>, + }, +} + +enum State { + Stopped, + //Suspended(Arc), + Completed(Cycle), } struct Worker { tasks: Arc>, inbox: Receiver, outbox: UnboundedSender, + service: TxPoolService, + exit_signal: CancellationToken, } impl Clone for Worker { @@ -33,20 +71,26 @@ impl Clone for Worker { tasks: Arc::clone(&self.tasks), inbox: self.inbox.clone(), outbox: self.outbox.clone(), + service: self.service.clone(), + exit_signal: self.exit_signal.clone(), } } } impl Worker { pub fn new( + service: TxPoolService, tasks: Arc>, inbox: Receiver, outbox: UnboundedSender, + exit_signal: CancellationToken, ) -> Self { Worker { + service, tasks, inbox, outbox, + exit_signal, } } @@ -87,7 +131,13 @@ impl Worker { Some(entry) => entry, None => continue, }; - self.run_verify_tx(&entry) + let res = self.run_verify_tx(entry).await; + self.outbox + .send(VerifyNotify::Done { + short_id: entry.tx.proposal_short_id().to_string(), + result: res, + }) + .unwrap(); } else { // sleep for 100 ms tokio::time::sleep(Duration::from_millis(100)).await; @@ -96,17 +146,159 @@ impl Worker { }) } - fn run_verify_tx(&self, entry: &Entry) { - let short_id = entry.tx.proposal_short_id(); - self.outbox - .send(VerifyNotify::Start { - short_id: short_id.to_string(), - }) - .unwrap(); + // fn run_verify_tx(&self, entry: &Entry) { + // let short_id = entry.tx.proposal_short_id(); + // self.outbox + // .send(VerifyNotify::Start { + // short_id: short_id.to_string(), + // }) + // .unwrap(); + // } + + async fn run_verify_tx( + &mut self, + entry: Entry, + ) -> Option<(Result, Arc)> { + let Entry { tx, remote } = entry; + let tx_hash = tx.hash(); + + let (ret, snapshot) = self.service.pre_check(&tx).await; + let (tip_hash, rtx, status, fee, tx_size) = try_or_return_with_snapshot!(ret, snapshot); + + let cached = self.service.fetch_tx_verify_cache(&tx_hash).await; + + let tip_header = snapshot.tip_header(); + let consensus = snapshot.cloned_consensus(); + + let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); + let mut init_snap = None; + + if let Some(ref cached) = cached { + match cached { + CacheEntry::Completed(completed) => { + let ret = TimeRelativeTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&consensus), + snapshot.as_data_loader(), + Arc::clone(&tx_env), + ) + .verify() + .map(|_| *completed) + .map_err(Reject::Verification); + let completed = try_or_return_with_snapshot!(ret, snapshot); + + let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); + let (ret, submit_snapshot) = + self.service.submit_entry(tip_hash, entry, status).await; + try_or_return_with_snapshot!(ret, submit_snapshot); + // self.service + // .after_process(tx, remote, &submit_snapshot, &Ok(completed)) + // .await; + // self.remove_front().await; + return Some((Ok(false), submit_snapshot)); + } + CacheEntry::Suspended(suspended) => { + init_snap = Some(Arc::clone(&suspended.snap)); + } + } + } + + let cloned_snapshot = Arc::clone(&snapshot); + let data_loader = cloned_snapshot.as_data_loader(); + let ret = ContextualWithoutScriptTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&consensus), + data_loader.clone(), + Arc::clone(&tx_env), + ) + .verify() + .and_then(|result| { + DaoScriptSizeVerifier::new( + Arc::clone(&rtx), + Arc::clone(&consensus), + data_loader.clone(), + ) + .verify()?; + Ok(result) + }) + .map_err(Reject::Verification); + let fee = try_or_return_with_snapshot!(ret, snapshot); + + let max_cycles = if let Some((declared_cycle, _peer)) = remote { + declared_cycle + } else { + consensus.max_block_cycles() + }; + + let ret = self.loop_resume( + Arc::clone(&rtx), + data_loader, + init_snap, + max_cycles, + Arc::clone(&consensus), + Arc::clone(&tx_env), + ); + let state = try_or_return_with_snapshot!(ret, snapshot); + + let completed: Completed = match state { + // verify failed + State::Stopped => return Some((Ok(true), snapshot)), + State::Completed(cycles) => Completed { cycles, fee }, + }; + if let Some((declared_cycle, _peer)) = remote { + if declared_cycle != completed.cycles { + return Some(( + Err(Reject::DeclaredWrongCycles( + declared_cycle, + completed.cycles, + )), + snapshot, + )); + } + } + // verify passed + return Some((Ok(false), snapshot)); + + //let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); + //let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; + //try_or_return_with_snapshot!(ret, snapshot); + + // self.service.notify_block_assembler(status).await; + + // self.service + // .after_process(tx, remote, &submit_snapshot, &Ok(completed)) + // .await; + + // self.remove_front().await; + + // update_cache( + // Arc::clone(&self.service.txs_verify_cache), + // tx_hash, + // CacheEntry::Completed(completed), + // ) + // .await; + + // Some((Ok(false), submit_snapshot)) + } + + fn loop_resume< + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, + >( + &mut self, + rtx: Arc, + data_loader: DL, + mut init_snap: Option>, + max_cycles: Cycle, + consensus: Arc, + tx_env: Arc, + ) -> Result { + let script_verifier = ScriptVerifier::new(rtx, data_loader, consensus, tx_env); + let CYCLE_LIMIT = 1000000000; + script_verifier.resumable_verify_with_signal(CYCLE_LIMIT)?; } } -pub struct VerifyMgr { +pub(crate) struct VerifyMgr { workers: Vec<(ckb_channel::Sender, Worker)>, worker_notify: UnboundedReceiver, join_handles: Option>>, @@ -114,26 +306,36 @@ pub struct VerifyMgr { pub current_state: ChunkCommand, pub signal_exit: CancellationToken, pub verify_queue: Arc>, + pub service: TxPoolService, } impl VerifyMgr { pub fn new( + service: TxPoolService, chunk_rx: watch::Receiver, signal_exit: CancellationToken, - verify_queue: Arc>, + //verify_queue: Arc>, ) -> Self { let (notify_tx, notify_rx) = unbounded_channel::(); + let verify_queue = Arc::new(RwLock::new(VerifyQueue::new())); let workers: Vec<_> = (0..4) .map({ let tasks = Arc::clone(&verify_queue); move |_| { let (command_tx, command_rx) = ckb_channel::unbounded(); - let worker = Worker::new(Arc::clone(&tasks), command_rx, notify_tx.clone()); + let worker = Worker::new( + service.clone(), + Arc::clone(&tasks), + command_rx, + notify_tx.clone(), + signal_exit.clone(), + ); (command_tx, worker) } }) .collect(); Self { + service, workers, worker_notify: notify_rx, join_handles: None, @@ -165,7 +367,7 @@ impl VerifyMgr { } async fn start_loop(&mut self) { - let mut interval = tokio::time::interval(Duration::from_micros(1500)); + let mut interval = tokio::time::interval(Duration::from_micros(1000)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { tokio::select! { From 3bc79cce606c79e6bc29bb8fc0c834b2cbace37d Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 3 Jan 2024 16:44:05 +0800 Subject: [PATCH 007/294] begin to fix dataloader issue --- Cargo.lock | 2 + script/src/lib.rs | 4 +- script/src/types.rs | 7 + script/src/verify.rs | 228 ++++++++++++++++++++++- tx-pool/Cargo.toml | 2 + tx-pool/src/chunk_process.rs | 7 +- tx-pool/src/service.rs | 2 +- tx-pool/src/verify_mgr.rs | 186 ++++++------------ verification/Cargo.toml | 1 + verification/src/transaction_verifier.rs | 6 + 10 files changed, 302 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 564e05afcc..ec3ce7f51d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,6 +1617,7 @@ dependencies = [ "ckb-metrics", "ckb-network", "ckb-reward-calculator", + "ckb-script", "ckb-snapshot", "ckb-stop-handler", "ckb-store", @@ -1693,6 +1694,7 @@ dependencies = [ "ckb-verification-traits", "derive_more", "lru", + "tokio", ] [[package]] diff --git a/script/src/lib.rs b/script/src/lib.rs index 7f73ca7ee6..b24f94db63 100644 --- a/script/src/lib.rs +++ b/script/src/lib.rs @@ -10,8 +10,8 @@ mod verify_env; pub use crate::error::{ScriptError, TransactionScriptError}; pub use crate::syscalls::spawn::update_caller_machine; pub use crate::types::{ - CoreMachine, MachineContext, ResumableMachine, ScriptGroup, ScriptGroupType, ScriptVersion, - TransactionSnapshot, TransactionState, VerifyResult, VmIsa, VmVersion, + ChunkCommand, CoreMachine, MachineContext, ResumableMachine, ScriptGroup, ScriptGroupType, + ScriptVersion, TransactionSnapshot, TransactionState, VerifyResult, VmIsa, VmVersion, }; pub use crate::verify::{TransactionScriptsSyscallsGenerator, TransactionScriptsVerifier}; pub use crate::verify_env::TxVerifyEnv; diff --git a/script/src/types.rs b/script/src/types.rs index ba393e1894..211ca64d6c 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -471,3 +471,10 @@ impl std::fmt::Debug for TransactionState { .finish() } } + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum ChunkCommand { + Suspend, + Resume, + Shutdown, +} diff --git a/script/src/verify.rs b/script/src/verify.rs index 207161e436..8721036365 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -16,6 +16,7 @@ use crate::{ TransactionState, VerifyResult, }, verify_env::TxVerifyEnv, + ChunkCommand, }; use ckb_chain_spec::consensus::{Consensus, TYPE_ID_CODE_HASH}; use ckb_error::Error; @@ -36,9 +37,10 @@ use ckb_vm::{ snapshot::{resume, Snapshot}, DefaultMachineBuilder, Error as VMInternalError, SupportMachine, Syscalls, }; -use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; use std::sync::{Arc, Mutex}; +use std::{cell::RefCell, sync::RwLock}; +use tokio::{select, sync::mpsc}; #[cfg(test)] use core::sync::atomic::{AtomicBool, Ordering}; @@ -665,6 +667,46 @@ impl, + ) -> Result { + let mut cycles = 0; + + let groups: Vec<_> = self.groups().collect(); + for (idx, (_hash, group)) in groups.iter().enumerate() { + // vm should early return invalid cycles + let remain_cycles = limit_cycles.checked_sub(cycles).ok_or_else(|| { + ScriptError::VMInternalError(format!( + "expect invalid cycles {limit_cycles} {cycles}" + )) + .source(group) + })?; + + match self + .verify_group_with_signal(group, remain_cycles, command_rx) + .await + { + Ok(ChunkState::Completed(used_cycles)) => { + cycles = wrapping_cycles_add(cycles, used_cycles, group)?; + } + Ok(ChunkState::Suspended(vms, context)) => { + let current = idx; + let state = TransactionState::new(vms, context, current, cycles, remain_cycles); + return Ok(VerifyResult::Suspended(state)); + } + Err(e) => { + #[cfg(feature = "logging")] + logging::on_script_error(_hash, &self.hash(), &e); + return Err(e.source(group).into()); + } + } + } + + Ok(VerifyResult::Completed(cycles)) + } + /// Resuming an suspended verify from snapshot /// /// ## Params @@ -970,6 +1012,31 @@ impl, + ) -> Result { + if group.script.code_hash() == TYPE_ID_CODE_HASH.pack() + && Into::::into(group.script.hash_type()) == Into::::into(ScriptHashType::Type) + { + let verifier = TypeIdSystemScript { + rtx: &self.rtx, + script_group: group, + max_cycles, + }; + match verifier.verify() { + Ok(cycles) => Ok(ChunkState::Completed(cycles)), + Err(ScriptError::ExceededMaximumCycles(_)) => Ok(ChunkState::suspended_type_id()), + Err(e) => Err(e), + } + } else { + self.chunk_run_with_signal(group, max_cycles, command_rx) + .await + } + } + /// Finds the script group from cell deps. pub fn find_script_group( &self, @@ -1050,6 +1117,66 @@ impl, + ) -> Result { + let context: Arc> = Default::default(); + + let map_vm_internal_error = |error: VMInternalError| match error { + VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), + _ => ScriptError::VMInternalError(format!("{error:?}")), + }; + + let machines = { + // No snapshots are available, create machine from scratch + let mut machine = self.build_machine(script_group, max_cycles, Arc::clone(&context))?; + let program = self.extract_script(&script_group.script)?; + let bytes = machine + .load_program(&program, &[]) + .map_err(map_vm_internal_error)?; + let program_bytes_cycles = transferred_byte_cycles(bytes); + // NOTE: previously, we made a distinction between machines + // that completes program loading without errors, and machines + // that fail program loading due to cycle limits. For the latter + // one, we won't generate any snapshots. Starting from this version, + // we will remove this distinction: when loading program exceeds + // maximum cycles, the error will be triggered when executing the + // first instruction. As a result, now all ResumableMachine will + // be transformed to snapshots. This is due to several considerations: + // + // * Let's do a little bit math: right now CKB has a block limit of + // ~570KB, a single transaction is further limited to 512KB in RPC, + // the biggest program one can load is either 512KB or ~570KB depending + // on which limit to use. The cycles consumed to load a program, is + // thus at most 131072 or ~145920, which is far less than the cycle + // limit for running a single transaction (70 million or more). In + // reality it might be extremely rare that loading a program would + // result in exceeding cycle limits. Removing the distinction here, + // would help simply the code. + // * If you pay attention to the code now, we already have this behavior + // in the code: most syscalls use +add_cycles_no_checking+ in the code, + // meaning an error would not be immediately generated when cycle limit + // is reached, the error would be raised when executing the first instruction + // after the syscall. What's more, when spawn is loading a program + // to its child machine, it also uses +add_cycles_no_checking+ so it + // won't generate errors immediately. This means that all spawned machines + // will be in a state that a program is loaded, regardless of the fact if + // loading a program in spawn reaches the cycle limit or not. As a + // result, we definitely want to pull the trigger, so we can have unified + // behavior everywhere. + machine + .machine + .add_cycles_no_checking(program_bytes_cycles) + .map_err(|e| ScriptError::VMInternalError(format!("{e:?}")))?; + vec![machine] + }; + + run_vms_with_signal(script_group, max_cycles, machines, &context, command_rx).await + } + fn chunk_run( &self, script_group: &ScriptGroup, @@ -1229,6 +1356,105 @@ fn run_vms( } } +// Run a series of VMs that are just freshly resumed +async fn run_vms_with_signal( + script_group: &ScriptGroup, + _max_cycles: Cycle, + mut machines: Vec, + context: &Arc>, + command_rx: &mut tokio::sync::watch::Receiver, +) -> Result { + let (mut exit_code, mut cycles) = (0, 0); + + if machines.is_empty() { + return Err(ScriptError::VMInternalError( + "To resume VMs, at least one VM must be available!".to_string(), + )); + } + + // let map_vm_internal_error = |error: VMInternalError| match error { + // VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), + // _ => ScriptError::VMInternalError(format!("{error:?}")), + // }; + + while let Some(machine) = machines.pop() { + match run_vm_with_signal(machine, command_rx).await { + (Ok(code), run_cycles) => { + exit_code = code; + cycles = run_cycles; + } + (Err(error), _) => match error { + VMInternalError::CyclesExceeded => { + return Ok(ChunkState::suspended(vec![], Arc::clone(context))); + } + _ => return Err(ScriptError::VMInternalError(format!("{error:?}"))), + }, + }; + } + + if exit_code == 0 { + Ok(ChunkState::Completed(cycles)) + } else { + Err(ScriptError::validation_failure( + &script_group.script, + exit_code, + )) + } +} + +async fn run_vm_with_signal( + mut vm: Machine, + signal: &mut tokio::sync::watch::Receiver, +) -> (Result, Cycle) { + let (finished_send, mut finished_recv) = mpsc::unbounded_channel(); + let pause = vm.machine.pause(); + let (child_sender, mut child_recv) = tokio::sync::watch::channel(ChunkCommand::Resume); + child_recv.mark_changed(); + let jh = tokio::spawn(async move { + loop { + select! { + _ = child_recv.changed() => { + let state = child_recv.borrow().to_owned(); + if state == ChunkCommand::Resume { + let result = vm.run(); + eprintln!("res: {:?}", result); + if matches!(result, Err(ckb_vm::Error::Pause)) { + continue; + } else { + finished_send.send((result, vm.machine.cycles())).unwrap(); + return; + } + } + } + else => { + eprintln!("now paused ..."); + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + } + } + }); + + loop { + tokio::select! { + _ = signal.changed() => { + let state = signal.borrow().to_owned(); + if state == ChunkCommand::Suspend { + eprintln!("received pause signal ..."); + pause.interrupt(); + } else if state == ChunkCommand::Resume { + eprintln!("received resume signal ..."); + child_sender.send(ChunkCommand::Resume).unwrap(); + } + } + res = finished_recv.recv() => { + eprintln!("finished ...: {:?}", res); + let _ = jh.await.unwrap(); + return res.unwrap(); + } + } + } +} + fn wrapping_cycles_add( lhs: Cycle, rhs: Cycle, diff --git a/tx-pool/Cargo.toml b/tx-pool/Cargo.toml index 5761c57a5c..e65e054591 100644 --- a/tx-pool/Cargo.toml +++ b/tx-pool/Cargo.toml @@ -35,6 +35,8 @@ ckb-network = { path = "../network", version = "= 0.114.0-pre" } ckb-channel = { path = "../util/channel", version = "= 0.114.0-pre" } ckb-traits = { path = "../traits", version = "= 0.114.0-pre" } ckb-db = { path = "../db", version = "= 0.114.0-pre" } +ckb-script = { path = "../script", version = "= 0.114.0-pre" } + sentry = { version = "0.26.0", optional = true } serde_json = "1.0" rand = "0.8.4" diff --git a/tx-pool/src/chunk_process.rs b/tx-pool/src/chunk_process.rs index 0d9b03f2f3..1bb4af1c35 100644 --- a/tx-pool/src/chunk_process.rs +++ b/tx-pool/src/chunk_process.rs @@ -5,6 +5,7 @@ use crate::{error::Reject, service::TxPoolService}; use ckb_chain_spec::consensus::Consensus; use ckb_error::Error; use ckb_logger::info; +use ckb_script::ChunkCommand; use ckb_snapshot::Snapshot; use ckb_store::data_loader_wrapper::AsDataLoader; use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; @@ -29,12 +30,6 @@ const MIN_STEP_CYCLE: Cycle = 10_000_000; type Stop = bool; -#[derive(Eq, PartialEq, Clone, Debug)] -pub(crate) enum ChunkCommand { - Suspend, - Resume, -} - enum State { Stopped, Suspended(Arc), diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index 401c08e017..dc853dc081 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -7,7 +7,6 @@ use crate::component::{chunk::ChunkQueue, orphan::OrphanPool}; use crate::error::{handle_recv_error, handle_send_cmd_error, handle_try_send_error}; use crate::pool::TxPool; use crate::util::after_delay_window; -use crate::verify_mgr::ChunkCommand; use crate::verify_mgr::VerifyMgr; use crate::verify_queue::VerifyQueue; use ckb_app_config::{BlockAssemblerConfig, TxPoolConfig}; @@ -19,6 +18,7 @@ use ckb_jsonrpc_types::BlockTemplate; use ckb_logger::error; use ckb_logger::info; use ckb_network::{NetworkController, PeerIndex}; +use ckb_script::ChunkCommand; use ckb_snapshot::Snapshot; use ckb_stop_handler::new_tokio_exit_rx; use ckb_types::core::tx_pool::{PoolTxDetailInfo, TransactionWithStatus, TxStatus}; diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index a28fc64e80..d31b45d994 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -2,7 +2,7 @@ use crate::component::entry::TxEntry; use crate::try_or_return_with_snapshot; use crate::{error::Reject, service::TxPoolService}; -use ckb_channel::Receiver; +use ckb_script::ChunkCommand; use ckb_stop_handler::CancellationToken; use std::sync::Arc; use std::time::Duration; @@ -10,11 +10,9 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::{watch, RwLock}; use tokio::task::JoinHandle; -use crate::verify_queue::{self, Entry, VerifyQueue}; +use crate::verify_queue::{Entry, VerifyQueue}; use ckb_chain_spec::consensus::Consensus; -use ckb_error::Error; -use ckb_logger::info; use ckb_snapshot::Snapshot; use ckb_store::data_loader_wrapper::AsDataLoader; use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; @@ -33,18 +31,8 @@ use tokio::task::block_in_place; type Stop = bool; -#[derive(Eq, PartialEq, Clone, Debug)] -pub enum ChunkCommand { - Suspend, - Resume, - Shutdown, -} - #[derive(Clone, Debug)] pub enum VerifyNotify { - Start { - short_id: String, - }, Done { short_id: String, result: Option<(Result, Arc)>, @@ -59,7 +47,7 @@ enum State { struct Worker { tasks: Arc>, - inbox: Receiver, + command_rx: tokio::sync::watch::Receiver, outbox: UnboundedSender, service: TxPoolService, exit_signal: CancellationToken, @@ -69,10 +57,10 @@ impl Clone for Worker { fn clone(&self) -> Self { Self { tasks: Arc::clone(&self.tasks), - inbox: self.inbox.clone(), + command_rx: self.command_rx.clone(), + exit_signal: self.exit_signal.clone(), outbox: self.outbox.clone(), service: self.service.clone(), - exit_signal: self.exit_signal.clone(), } } } @@ -81,80 +69,49 @@ impl Worker { pub fn new( service: TxPoolService, tasks: Arc>, - inbox: Receiver, + command_rx: tokio::sync::watch::Receiver, outbox: UnboundedSender, exit_signal: CancellationToken, ) -> Self { Worker { service, tasks, - inbox, + command_rx, outbox, exit_signal, } } /// start handle tasks - pub fn start(self) -> JoinHandle<()> { + pub fn start(mut self) -> JoinHandle<()> { tokio::spawn(async move { - let mut pause = false; loop { - match self.inbox.try_recv() { - Ok(msg) => match msg { - ChunkCommand::Shutdown => { - break; - } - ChunkCommand::Suspend => { - pause = true; - continue; - } - ChunkCommand::Resume => { - pause = false; - } - }, - Err(err) => { - if !err.is_empty() { - eprintln!("error: {:?}", err); - break; - } - } - }; + if self.exit_signal.is_cancelled() { + break; + } - if !pause { - if self.tasks.read().await.get_first().is_none() { - // sleep for 100 ms - tokio::time::sleep(Duration::from_millis(100)).await; - continue; - } - // pick a entry to run verify - let entry = match self.tasks.write().await.pop_first() { - Some(entry) => entry, - None => continue, - }; - let res = self.run_verify_tx(entry).await; - self.outbox - .send(VerifyNotify::Done { - short_id: entry.tx.proposal_short_id().to_string(), - result: res, - }) - .unwrap(); - } else { + if self.tasks.read().await.get_first().is_none() { // sleep for 100 ms - tokio::time::sleep(Duration::from_millis(100)).await; + tokio::time::sleep(Duration::from_millis(10)).await; + continue; } + // pick a entry to run verify + let entry = match self.tasks.write().await.pop_first() { + Some(entry) => entry, + None => continue, + }; + let short_id = entry.tx.proposal_short_id().to_string(); + let res = self.run_verify_tx(entry).await; + self.outbox + .send(VerifyNotify::Done { + short_id, + result: res, + }) + .unwrap(); } }) } - // fn run_verify_tx(&self, entry: &Entry) { - // let short_id = entry.tx.proposal_short_id(); - // self.outbox - // .send(VerifyNotify::Start { - // short_id: short_id.to_string(), - // }) - // .unwrap(); - // } - async fn run_verify_tx( &mut self, entry: Entry, @@ -171,7 +128,6 @@ impl Worker { let consensus = snapshot.cloned_consensus(); let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); - let mut init_snap = None; if let Some(ref cached) = cached { match cached { @@ -197,9 +153,7 @@ impl Worker { // self.remove_front().await; return Some((Ok(false), submit_snapshot)); } - CacheEntry::Suspended(suspended) => { - init_snap = Some(Arc::clone(&suspended.snap)); - } + CacheEntry::Suspended(_suspended) => {} } } @@ -230,14 +184,15 @@ impl Worker { consensus.max_block_cycles() }; - let ret = self.loop_resume( - Arc::clone(&rtx), - data_loader, - init_snap, - max_cycles, - Arc::clone(&consensus), - Arc::clone(&tx_env), - ); + let ret = self + .loop_resume( + Arc::clone(&rtx), + data_loader, + max_cycles, + Arc::clone(&consensus), + Arc::clone(&tx_env), + ) + .await; let state = try_or_return_with_snapshot!(ret, snapshot); let completed: Completed = match state { @@ -258,52 +213,32 @@ impl Worker { } // verify passed return Some((Ok(false), snapshot)); - - //let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - //let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; - //try_or_return_with_snapshot!(ret, snapshot); - - // self.service.notify_block_assembler(status).await; - - // self.service - // .after_process(tx, remote, &submit_snapshot, &Ok(completed)) - // .await; - - // self.remove_front().await; - - // update_cache( - // Arc::clone(&self.service.txs_verify_cache), - // tx_hash, - // CacheEntry::Completed(completed), - // ) - // .await; - - // Some((Ok(false), submit_snapshot)) } - fn loop_resume< + async fn loop_resume< DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, >( &mut self, rtx: Arc, data_loader: DL, - mut init_snap: Option>, max_cycles: Cycle, consensus: Arc, tx_env: Arc, ) -> Result { let script_verifier = ScriptVerifier::new(rtx, data_loader, consensus, tx_env); let CYCLE_LIMIT = 1000000000; - script_verifier.resumable_verify_with_signal(CYCLE_LIMIT)?; + let res = script_verifier + .resumable_verify_with_signal(CYCLE_LIMIT, &mut self.command_rx) + .await + .unwrap(); + return Ok(State::Completed(0)); } } pub(crate) struct VerifyMgr { - workers: Vec<(ckb_channel::Sender, Worker)>, + workers: Vec, worker_notify: UnboundedReceiver, join_handles: Option>>, - pub chunk_rx: watch::Receiver, - pub current_state: ChunkCommand, pub signal_exit: CancellationToken, pub verify_queue: Arc>, pub service: TxPoolService, @@ -321,16 +256,19 @@ impl VerifyMgr { let workers: Vec<_> = (0..4) .map({ let tasks = Arc::clone(&verify_queue); + let command_rx = chunk_rx.clone(); + let signal_exit = signal_exit.clone(); + let service = service.clone(); move |_| { - let (command_tx, command_rx) = ckb_channel::unbounded(); + //let (command_tx, command_rx) = ckb_channel::unbounded(); let worker = Worker::new( service.clone(), Arc::clone(&tasks), - command_rx, + command_rx.clone(), notify_tx.clone(), signal_exit.clone(), ); - (command_tx, worker) + worker } }) .collect(); @@ -339,28 +277,15 @@ impl VerifyMgr { workers, worker_notify: notify_rx, join_handles: None, - chunk_rx, - current_state: ChunkCommand::Resume, signal_exit, verify_queue, } } - async fn send_command(&mut self, command: ChunkCommand) { - eprintln!( - "send workers {:?} command: {:?}", - std::time::SystemTime::now(), - command - ); - for worker in self.workers.iter_mut() { - worker.0.send(command.clone()).unwrap(); - } - } - fn start_workers(&mut self) { let mut join_handles = Vec::new(); for w in self.workers.iter_mut() { - let h = w.1.clone().start(); + let h = w.clone().start(); join_handles.push(h); } self.join_handles.replace(join_handles); @@ -371,17 +296,12 @@ impl VerifyMgr { interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { tokio::select! { - _ = self.chunk_rx.changed() => { - self.current_state = self.chunk_rx.borrow().to_owned(); - self.send_command(self.current_state.clone()).await; + _ = self.signal_exit.cancelled() => { + break; }, res = self.worker_notify.recv() => { eprintln!("res: {:?}", res); } - _ = self.signal_exit.cancelled() => { - self.send_command(ChunkCommand::Shutdown).await; - break; - }, _ = interval.tick() => { tokio::time::sleep(Duration::from_millis(50)).await; } diff --git a/verification/Cargo.toml b/verification/Cargo.toml index 6e9e022bcd..f08eb31c81 100644 --- a/verification/Cargo.toml +++ b/verification/Cargo.toml @@ -21,6 +21,7 @@ ckb-dao-utils = { path = "../util/dao/utils", version = "= 0.114.0-pre" } ckb-error = { path = "../error", version = "= 0.114.0-pre" } derive_more = { version = "0.99.0", default-features=false, features = ["display"] } ckb-verification-traits = { path = "./traits", version = "= 0.114.0-pre" } +tokio = { version = "1", features = ["sync", "process"] } [dev-dependencies] ckb-test-chain-utils = { path = "../util/test-chain-utils", version = "= 0.114.0-pre" } diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index c22d74b359..4305729c7e 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -5,7 +5,13 @@ use ckb_chain_spec::consensus::Consensus; use ckb_dao::DaoCalculator; use ckb_dao_utils::DaoError; use ckb_error::Error; +<<<<<<< HEAD use ckb_script::{TransactionScriptsVerifier, TransactionSnapshot, VerifyResult}; +======= +use ckb_script::{ + ChunkCommand, TransactionScriptsVerifier, TransactionSnapshot, TransactionState, VerifyResult, +}; +>>>>>>> 6a1178ada (begin to fix dataloader issue) use ckb_traits::{ CellDataProvider, EpochProvider, ExtensionProvider, HeaderFieldsProvider, HeaderProvider, }; From f888a18c965894c77989dd046302733026d1b82e Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 3 Jan 2024 17:18:08 +0800 Subject: [PATCH 008/294] fix lazydata issue --- script/src/verify.rs | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 8721036365..d140c530aa 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -70,25 +70,25 @@ enum DataGuard { } /// LazyData wrapper make sure not-loaded data will be loaded only after one access -#[derive(Debug, PartialEq, Eq, Clone)] -struct LazyData(RefCell); +#[derive(Debug, Clone)] +struct LazyData(Arc>); impl LazyData { fn from_cell_meta(cell_meta: &CellMeta) -> LazyData { match &cell_meta.mem_cell_data { - Some(data) => LazyData(RefCell::new(DataGuard::Loaded(data.to_owned()))), - None => LazyData(RefCell::new(DataGuard::NotLoaded( + Some(data) => LazyData(Arc::new(RwLock::new(DataGuard::Loaded(data.to_owned())))), + None => LazyData(Arc::new(RwLock::new(DataGuard::NotLoaded( cell_meta.out_point.clone(), - ))), + )))), } } fn access(&self, data_loader: &DL) -> Bytes { - let guard = self.0.borrow().to_owned(); + let guard = self.0.read().unwrap().to_owned(); match guard { DataGuard::NotLoaded(out_point) => { let data = data_loader.get_cell_data(&out_point).expect("cell data"); - self.0.replace(DataGuard::Loaded(data.to_owned())); + *self.0.write().unwrap() = DataGuard::Loaded(data.to_owned()); data } DataGuard::Loaded(bytes) => bytes, @@ -96,6 +96,26 @@ impl LazyData { } } +impl PartialEq for LazyData { + fn eq(&self, other: &Self) -> bool { + let self_guard = self.0.read().unwrap(); + let other_guard = other.0.read().unwrap(); + + match (&*self_guard, &*other_guard) { + (DataGuard::Loaded(ref self_bytes), DataGuard::Loaded(ref other_bytes)) => { + self_bytes == other_bytes + } + ( + DataGuard::NotLoaded(ref self_out_point), + DataGuard::NotLoaded(ref other_out_point), + ) => self_out_point == other_out_point, + _ => false, + } + } +} + +impl Eq for LazyData {} + #[derive(Debug, PartialEq, Eq, Clone)] enum Binaries { Unique(Byte32, LazyData), From a60662fab83c97994752ccb6b6987b13a508a179 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 3 Jan 2024 17:32:57 +0800 Subject: [PATCH 009/294] cleanup chunk --- script/src/types.rs | 31 --- script/src/verify.rs | 2 +- tx-pool/src/chunk_process.rs | 363 --------------------------------- tx-pool/src/component/chunk.rs | 113 ---------- tx-pool/src/component/mod.rs | 1 - tx-pool/src/lib.rs | 1 - tx-pool/src/process.rs | 12 +- tx-pool/src/service.rs | 7 +- tx-pool/src/verify_mgr.rs | 22 +- 9 files changed, 16 insertions(+), 536 deletions(-) delete mode 100644 tx-pool/src/chunk_process.rs delete mode 100644 tx-pool/src/component/chunk.rs diff --git a/script/src/types.rs b/script/src/types.rs index 211ca64d6c..b822cbe32e 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -12,7 +12,6 @@ use ckb_vm::{ use serde::{Deserialize, Serialize}; use std::fmt; use std::sync::{Arc, Mutex}; -use tokio::sync::mpsc::{self, unbounded_channel}; #[cfg(has_asm)] use ckb_vm::machine::asm::{AsmCoreMachine, AsmMachine}; @@ -231,36 +230,6 @@ impl ResumableMachine { } } -async fn run_with_pause( - mut machine: Machine, - mut pause_signal: mpsc::UnboundedReceiver<()>, -) -> Result<(Result, Machine), Error> { - let pause = machine.machine.pause(); - let (res_tx, mut res_rx) = unbounded_channel(); - let jh = tokio::spawn(async move { - let result = machine.run(); - res_tx.send(result).unwrap(); - return machine; - }); - - loop { - tokio::select! { - _ = pause_signal.recv() => { - eprintln!("received pause signal ..."); - pause.interrupt(); - } - Some(res) = res_rx.recv() => { - eprintln!("finished ..."); - let machine = jh.await.unwrap(); - return Ok((res, machine)); - } - else => { - eprintln!("no signal ..."); - } - } - } -} - #[cfg(has_asm)] pub(crate) fn set_vm_max_cycles(vm: &mut Machine, cycles: Cycle) { vm.set_max_cycles(cycles) diff --git a/script/src/verify.rs b/script/src/verify.rs index d140c530aa..11cde9f857 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -38,8 +38,8 @@ use ckb_vm::{ DefaultMachineBuilder, Error as VMInternalError, SupportMachine, Syscalls, }; use std::collections::{BTreeMap, HashMap}; +use std::sync::RwLock; use std::sync::{Arc, Mutex}; -use std::{cell::RefCell, sync::RwLock}; use tokio::{select, sync::mpsc}; #[cfg(test)] diff --git a/tx-pool/src/chunk_process.rs b/tx-pool/src/chunk_process.rs deleted file mode 100644 index 1bb4af1c35..0000000000 --- a/tx-pool/src/chunk_process.rs +++ /dev/null @@ -1,363 +0,0 @@ -use crate::component::chunk::Entry; -use crate::component::entry::TxEntry; -use crate::try_or_return_with_snapshot; -use crate::{error::Reject, service::TxPoolService}; -use ckb_chain_spec::consensus::Consensus; -use ckb_error::Error; -use ckb_logger::info; -use ckb_script::ChunkCommand; -use ckb_snapshot::Snapshot; -use ckb_store::data_loader_wrapper::AsDataLoader; -use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; -use ckb_types::{ - core::{cell::ResolvedTransaction, Cycle}, - packed::Byte32, -}; -use ckb_verification::cache::TxVerificationCache; -use ckb_verification::{ - cache::{CacheEntry, Completed}, - ContextualWithoutScriptTransactionVerifier, DaoScriptSizeVerifier, ScriptError, ScriptVerifier, - ScriptVerifyResult, ScriptVerifyState, TimeRelativeTransactionVerifier, TransactionSnapshot, - TxVerifyEnv, -}; -use std::sync::Arc; -use tokio::sync::watch; -use tokio::sync::RwLock; -use tokio::task::block_in_place; -use tokio_util::sync::CancellationToken; - -const MIN_STEP_CYCLE: Cycle = 10_000_000; - -type Stop = bool; - -enum State { - Stopped, - Suspended(Arc), - Completed(Cycle), -} - -pub(crate) struct ChunkProcess { - service: TxPoolService, - recv: watch::Receiver, - current_state: ChunkCommand, - signal: CancellationToken, -} - -impl ChunkProcess { - pub fn new( - service: TxPoolService, - recv: watch::Receiver, - signal: CancellationToken, - ) -> Self { - ChunkProcess { - service, - recv, - signal, - current_state: ChunkCommand::Resume, - } - } - - pub async fn run(mut self) { - let mut interval = tokio::time::interval(std::time::Duration::from_micros(1500)); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - loop { - tokio::select! { - _ = self.recv.changed() => { - self.current_state = self.recv.borrow().to_owned(); - if matches!(self.current_state, ChunkCommand::Resume) { - let stop = self.try_process().await; - if stop { - break; - } - } - }, - _ = interval.tick() => { - if matches!(self.current_state, ChunkCommand::Resume) { - let stop = self.try_process().await; - if stop { - break; - } - } - }, - _ = self.signal.cancelled() => { - info!("TxPool chunk_command service received exit signal, exit now"); - break - }, - else => break, - } - } - } - - async fn try_process(&mut self) -> Stop { - match self.get_front().await { - Some(entry) => self.process(entry).await, - None => false, - } - } - - async fn get_front(&self) -> Option { - self.service.chunk.write().await.pop_front() - } - - async fn remove_front(&self) { - let mut guard = self.service.chunk.write().await; - guard.clean_front(); - } - - async fn process(&mut self, entry: Entry) -> Stop { - let (ret, snapshot) = self - .process_inner(entry.clone()) - .await - .expect("process_inner can not return None"); - - match ret { - Ok(stop) => stop, - Err(e) => { - self.service - .after_process(entry.tx, entry.remote, &snapshot, &Err(e)) - .await; - self.remove_front().await; - false - } - } - } - - fn loop_resume< - DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, - >( - &mut self, - rtx: Arc, - data_loader: DL, - mut init_snap: Option>, - max_cycles: Cycle, - consensus: Arc, - tx_env: Arc, - ) -> Result { - let script_verifier = ScriptVerifier::new(rtx, data_loader, consensus, tx_env); - let mut tmp_state: Option = None; - - let completed: Cycle = loop { - if self.signal.is_cancelled() { - return Ok(State::Stopped); - } - if self.recv.has_changed().unwrap_or(false) { - self.current_state = self.recv.borrow_and_update().to_owned(); - } - - if matches!(self.current_state, ChunkCommand::Suspend) { - let state = tmp_state.take(); - - if let Some(state) = state { - let snap = state.try_into().map_err(Reject::Verification)?; - return Ok(State::Suspended(Arc::new(snap))); - } - } - - let mut last_step = false; - let ret = if let Some(ref snap) = init_snap { - if snap.current_cycles > max_cycles { - let error = - exceeded_maximum_cycles_error(&script_verifier, max_cycles, snap.current); - return Err(Reject::Verification(error)); - } - - let (limit_cycles, last) = snap.next_limit_cycles(MIN_STEP_CYCLE, max_cycles); - last_step = last; - let ret = script_verifier.resume_from_snap(snap, limit_cycles); - init_snap = None; - ret - } else if let Some(state) = tmp_state { - // once we start loop from state, clean tmp snap. - init_snap = None; - if state.current_cycles > max_cycles { - let error = - exceeded_maximum_cycles_error(&script_verifier, max_cycles, state.current); - return Err(Reject::Verification(error)); - } - - let (limit_cycles, last) = state.next_limit_cycles(MIN_STEP_CYCLE, max_cycles); - last_step = last; - - block_in_place(|| script_verifier.resume_from_state(state, limit_cycles)) - } else { - block_in_place(|| script_verifier.resumable_verify(MIN_STEP_CYCLE)) - } - .map_err(Reject::Verification)?; - - match ret { - ScriptVerifyResult::Completed(cycles) => { - break cycles; - } - ScriptVerifyResult::Suspended(state) => { - if last_step { - let error = exceeded_maximum_cycles_error( - &script_verifier, - max_cycles, - state.current, - ); - return Err(Reject::Verification(error)); - } - tmp_state = Some(state); - } - } - }; - - Ok(State::Completed(completed)) - } - - async fn process_inner( - &mut self, - entry: Entry, - ) -> Option<(Result, Arc)> { - let Entry { tx, remote } = entry; - let tx_hash = tx.hash(); - - let (ret, snapshot) = self.service.pre_check(&tx).await; - let (tip_hash, rtx, status, fee, tx_size) = try_or_return_with_snapshot!(ret, snapshot); - - let cached = self.service.fetch_tx_verify_cache(&tx_hash).await; - - let tip_header = snapshot.tip_header(); - let consensus = snapshot.cloned_consensus(); - - let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); - let mut init_snap = None; - - if let Some(ref cached) = cached { - match cached { - CacheEntry::Completed(completed) => { - let ret = TimeRelativeTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - snapshot.as_data_loader(), - Arc::clone(&tx_env), - ) - .verify() - .map(|_| *completed) - .map_err(Reject::Verification); - let completed = try_or_return_with_snapshot!(ret, snapshot); - - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, submit_snapshot) = - self.service.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, submit_snapshot); - self.service - .after_process(tx, remote, &submit_snapshot, &Ok(completed)) - .await; - self.remove_front().await; - return Some((Ok(false), submit_snapshot)); - } - CacheEntry::Suspended(suspended) => { - init_snap = Some(Arc::clone(&suspended.snap)); - } - } - } - - let cloned_snapshot = Arc::clone(&snapshot); - let data_loader = cloned_snapshot.as_data_loader(); - let ret = ContextualWithoutScriptTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - data_loader.clone(), - Arc::clone(&tx_env), - ) - .verify() - .and_then(|result| { - DaoScriptSizeVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - data_loader.clone(), - ) - .verify()?; - Ok(result) - }) - .map_err(Reject::Verification); - let fee = try_or_return_with_snapshot!(ret, snapshot); - - let max_cycles = if let Some((declared_cycle, _peer)) = remote { - declared_cycle - } else { - consensus.max_block_cycles() - }; - - let ret = self.loop_resume( - Arc::clone(&rtx), - data_loader, - init_snap, - max_cycles, - Arc::clone(&consensus), - Arc::clone(&tx_env), - ); - let state = try_or_return_with_snapshot!(ret, snapshot); - - let completed: Completed = match state { - State::Stopped => return Some((Ok(true), snapshot)), - State::Suspended(snap) => { - update_cache( - Arc::clone(&self.service.txs_verify_cache), - tx_hash, - CacheEntry::suspended(snap, fee), - ) - .await; - return Some((Ok(false), snapshot)); - } - State::Completed(cycles) => Completed { cycles, fee }, - }; - - if let Some((declared_cycle, _peer)) = remote { - if declared_cycle != completed.cycles { - return Some(( - Err(Reject::DeclaredWrongCycles( - declared_cycle, - completed.cycles, - )), - snapshot, - )); - } - } - - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, snapshot); - - self.service.notify_block_assembler(status).await; - - self.service - .after_process(tx, remote, &submit_snapshot, &Ok(completed)) - .await; - - self.remove_front().await; - - update_cache( - Arc::clone(&self.service.txs_verify_cache), - tx_hash, - CacheEntry::Completed(completed), - ) - .await; - - Some((Ok(false), submit_snapshot)) - } -} - -fn exceeded_maximum_cycles_error< - DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, ->( - verifier: &ScriptVerifier
, - max_cycles: Cycle, - current: usize, -) -> Error { - verifier - .groups() - .nth(current) - .map(|(_hash, group)| ScriptError::ExceededMaximumCycles(max_cycles).source(group)) - .unwrap_or_else(|| { - ScriptError::Other(format!("suspended state group missing {current:?}")) - .unknown_source() - }) - .into() -} - -async fn update_cache(cache: Arc>, tx_hash: Byte32, entry: CacheEntry) { - let mut guard = cache.write().await; - guard.put(tx_hash, entry); -} diff --git a/tx-pool/src/component/chunk.rs b/tx-pool/src/component/chunk.rs deleted file mode 100644 index 225e0a4ea8..0000000000 --- a/tx-pool/src/component/chunk.rs +++ /dev/null @@ -1,113 +0,0 @@ -use ckb_network::PeerIndex; -use ckb_types::{ - core::{Cycle, TransactionView}, - packed::ProposalShortId, -}; -use ckb_util::{shrink_to_fit, LinkedHashMap}; - -const SHRINK_THRESHOLD: usize = 100; -pub(crate) const DEFAULT_MAX_CHUNK_TRANSACTIONS: usize = 100; - -#[derive(Debug, Clone, Eq)] -pub(crate) struct Entry { - pub(crate) tx: TransactionView, - pub(crate) remote: Option<(Cycle, PeerIndex)>, -} - -impl PartialEq for Entry { - fn eq(&self, other: &Entry) -> bool { - self.tx == other.tx - } -} - -#[derive(Default)] -pub(crate) struct ChunkQueue { - inner: LinkedHashMap, - // memory last pop value for atomic reset - front: Option, -} - -impl ChunkQueue { - pub(crate) fn new() -> Self { - ChunkQueue { - inner: LinkedHashMap::default(), - front: None, - } - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - #[allow(dead_code)] - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - pub fn is_full(&self) -> bool { - self.len() > DEFAULT_MAX_CHUNK_TRANSACTIONS - } - - pub fn contains_key(&self, id: &ProposalShortId) -> bool { - self.front - .as_ref() - .map(|e| e.tx.proposal_short_id()) - .as_ref() - == Some(id) - || self.inner.contains_key(id) - } - - pub fn shrink_to_fit(&mut self) { - shrink_to_fit!(self.inner, SHRINK_THRESHOLD); - } - - pub fn clean_front(&mut self) { - self.front = None; - } - - pub fn pop_front(&mut self) -> Option { - if let Some(entry) = &self.front { - Some(entry.clone()) - } else { - match self.inner.pop_front() { - Some((_id, entry)) => { - self.front = Some(entry.clone()); - Some(entry) - } - None => None, - } - } - } - - pub fn remove_chunk_tx(&mut self, id: &ProposalShortId) -> Option { - let ret = self.inner.remove(id); - self.shrink_to_fit(); - ret - } - - pub fn remove_chunk_txs(&mut self, ids: impl Iterator) { - for id in ids { - self.inner.remove(&id); - } - self.shrink_to_fit(); - } - - /// If the queue did not have this tx present, true is returned. - /// If the queue did have this tx present, false is returned. - pub fn add_tx(&mut self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>) -> bool { - if self.contains_key(&tx.proposal_short_id()) { - return false; - } - - self.inner - .insert(tx.proposal_short_id(), Entry { tx, remote }) - .is_none() - } - - /// Clears the map, removing all elements. - pub fn clear(&mut self) { - self.inner.clear(); - self.clean_front(); - self.shrink_to_fit() - } -} diff --git a/tx-pool/src/component/mod.rs b/tx-pool/src/component/mod.rs index e5b8ab3cfc..beac9064d8 100644 --- a/tx-pool/src/component/mod.rs +++ b/tx-pool/src/component/mod.rs @@ -1,7 +1,6 @@ pub mod commit_txs_scanner; pub mod entry; -pub(crate) mod chunk; pub(crate) mod edges; pub(crate) mod links; pub(crate) mod orphan; diff --git a/tx-pool/src/lib.rs b/tx-pool/src/lib.rs index 043c10a511..a117325c87 100644 --- a/tx-pool/src/lib.rs +++ b/tx-pool/src/lib.rs @@ -3,7 +3,6 @@ pub mod block_assembler; mod callback; -mod chunk_process; mod component; pub mod error; mod persisted; diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index ed284cf5d7..2ba4943ce0 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -361,8 +361,8 @@ impl TxPoolService { pub(crate) async fn remove_tx(&self, tx_hash: Byte32) -> bool { let id = ProposalShortId::from_tx_hash(&tx_hash); { - let mut chunk = self.chunk.write().await; - if chunk.remove_chunk_tx(&id).is_some() { + let mut chunk = self.verify_queue.write().await; + if chunk.remove_tx(&id).is_some() { return true; } } @@ -528,7 +528,7 @@ impl TxPoolService { tx.hash(), ); self.remove_orphan_tx(&orphan.tx.proposal_short_id()).await; - self.chunk + self.verify_queue .write() .await .add_tx(orphan.tx, Some((orphan.cycle, orphan.peer))); @@ -994,7 +994,7 @@ impl TxPoolService { let txs_opt = if is_in_delay_window { { - self.chunk.write().await.clear(); + self.verify_queue.write().await.clear(); } Some(tx_pool.drain_all_transactions()) } else { @@ -1065,8 +1065,8 @@ impl TxPoolService { self.remove_orphan_txs_by_attach(&attached).await; { - let mut chunk = self.chunk.write().await; - chunk.remove_chunk_txs(attached.iter().map(|tx| tx.proposal_short_id())); + let mut chunk = self.verify_queue.write().await; + chunk.remove_txs(attached.iter().map(|tx| tx.proposal_short_id())); } } diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index dc853dc081..8f01026320 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -2,8 +2,8 @@ use crate::block_assembler::{self, BlockAssembler}; use crate::callback::{Callbacks, PendingCallback, ProposedCallback, RejectCallback}; +use crate::component::orphan::OrphanPool; use crate::component::pool_map::{PoolEntry, Status}; -use crate::component::{chunk::ChunkQueue, orphan::OrphanPool}; use crate::error::{handle_recv_error, handle_send_cmd_error, handle_try_send_error}; use crate::pool::TxPool; use crate::util::after_delay_window; @@ -381,7 +381,6 @@ pub struct TxPoolServiceBuilder { pub(crate) handle: Handle, pub(crate) tx_relay_sender: ckb_channel::Sender, pub(crate) chunk_rx: watch::Receiver, - pub(crate) chunk: Arc>, pub(crate) verify_queue: Arc>, pub(crate) started: Arc, pub(crate) block_assembler_channel: ( @@ -405,7 +404,6 @@ impl TxPoolServiceBuilder { let (reorg_sender, reorg_receiver) = mpsc::channel(DEFAULT_CHANNEL_SIZE); let signal_receiver: CancellationToken = new_tokio_exit_rx(); let (chunk_tx, chunk_rx) = watch::channel(ChunkCommand::Resume); - let chunk = Arc::new(RwLock::new(ChunkQueue::new())); let verify_queue = Arc::new(RwLock::new(VerifyQueue::new())); let started = Arc::new(AtomicBool::new(false)); @@ -431,7 +429,6 @@ impl TxPoolServiceBuilder { signal_receiver, handle: handle.clone(), tx_relay_sender, - chunk, chunk_rx, started, verify_queue, @@ -486,7 +483,6 @@ impl TxPoolServiceBuilder { callbacks: Arc::new(self.callbacks), tx_relay_sender: self.tx_relay_sender, block_assembler_sender, - chunk: self.chunk, verify_queue: self.verify_queue, network, consensus, @@ -643,7 +639,6 @@ pub(crate) struct TxPoolService { pub(crate) callbacks: Arc, pub(crate) network: NetworkController, pub(crate) tx_relay_sender: ckb_channel::Sender, - pub(crate) chunk: Arc>, pub(crate) verify_queue: Arc>, pub(crate) block_assembler_sender: mpsc::Sender, pub(crate) delay: Arc>>, diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index d31b45d994..8e9c5d72fc 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -16,18 +16,13 @@ use ckb_chain_spec::consensus::Consensus; use ckb_snapshot::Snapshot; use ckb_store::data_loader_wrapper::AsDataLoader; use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; -use ckb_types::{ - core::{cell::ResolvedTransaction, Cycle}, - packed::Byte32, -}; -use ckb_verification::cache::TxVerificationCache; +use ckb_types::core::{cell::ResolvedTransaction, Cycle}; +//use ckb_verification::cache::TxVerificationCache; use ckb_verification::{ cache::{CacheEntry, Completed}, - ContextualWithoutScriptTransactionVerifier, DaoScriptSizeVerifier, ScriptError, ScriptVerifier, - ScriptVerifyResult, ScriptVerifyState, TimeRelativeTransactionVerifier, TransactionSnapshot, - TxVerifyEnv, + ContextualWithoutScriptTransactionVerifier, DaoScriptSizeVerifier, ScriptVerifier, + TimeRelativeTransactionVerifier, TxVerifyEnv, }; -use tokio::task::block_in_place; type Stop = bool; @@ -221,14 +216,14 @@ impl Worker { &mut self, rtx: Arc, data_loader: DL, - max_cycles: Cycle, + _max_cycles: Cycle, consensus: Arc, tx_env: Arc, ) -> Result { let script_verifier = ScriptVerifier::new(rtx, data_loader, consensus, tx_env); - let CYCLE_LIMIT = 1000000000; - let res = script_verifier - .resumable_verify_with_signal(CYCLE_LIMIT, &mut self.command_rx) + let cycle_limit = 1000000000; + let _res = script_verifier + .resumable_verify_with_signal(cycle_limit, &mut self.command_rx) .await .unwrap(); return Ok(State::Completed(0)); @@ -260,7 +255,6 @@ impl VerifyMgr { let signal_exit = signal_exit.clone(); let service = service.clone(); move |_| { - //let (command_tx, command_rx) = ckb_channel::unbounded(); let worker = Worker::new( service.clone(), Arc::clone(&tasks), From aace281644cb8b22b0bf9ec4f17319c14124a56d Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 3 Jan 2024 23:29:06 +0800 Subject: [PATCH 010/294] more cleanups --- script/src/verify.rs | 14 ++-- tx-pool/src/service.rs | 16 ++--- tx-pool/src/verify_mgr.rs | 128 ++++++++++++++++++++++++------------ tx-pool/src/verify_queue.rs | 6 +- 4 files changed, 102 insertions(+), 62 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 11cde9f857..b05ef8d22d 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1194,7 +1194,7 @@ impl, - context: &Arc>, command_rx: &mut tokio::sync::watch::Receiver, ) -> Result { let (mut exit_code, mut cycles) = (0, 0); if machines.is_empty() { return Err(ScriptError::VMInternalError( - "To resume VMs, at least one VM must be available!".to_string(), + "At least one VM must be available!".to_string(), )); } - // let map_vm_internal_error = |error: VMInternalError| match error { - // VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), - // _ => ScriptError::VMInternalError(format!("{error:?}")), - // }; - while let Some(machine) = machines.pop() { match run_vm_with_signal(machine, command_rx).await { (Ok(code), run_cycles) => { @@ -1405,7 +1399,7 @@ async fn run_vms_with_signal( } (Err(error), _) => match error { VMInternalError::CyclesExceeded => { - return Ok(ChunkState::suspended(vec![], Arc::clone(context))); + return Err(ScriptError::ExceededMaximumCycles(max_cycles)) } _ => return Err(ScriptError::VMInternalError(format!("{error:?}"))), }, diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index 8f01026320..2e87a0ae2d 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -381,7 +381,6 @@ pub struct TxPoolServiceBuilder { pub(crate) handle: Handle, pub(crate) tx_relay_sender: ckb_channel::Sender, pub(crate) chunk_rx: watch::Receiver, - pub(crate) verify_queue: Arc>, pub(crate) started: Arc, pub(crate) block_assembler_channel: ( mpsc::Sender, @@ -404,7 +403,6 @@ impl TxPoolServiceBuilder { let (reorg_sender, reorg_receiver) = mpsc::channel(DEFAULT_CHANNEL_SIZE); let signal_receiver: CancellationToken = new_tokio_exit_rx(); let (chunk_tx, chunk_rx) = watch::channel(ChunkCommand::Resume); - let verify_queue = Arc::new(RwLock::new(VerifyQueue::new())); let started = Arc::new(AtomicBool::new(false)); let controller = TxPoolController { @@ -431,7 +429,6 @@ impl TxPoolServiceBuilder { tx_relay_sender, chunk_rx, started, - verify_queue, block_assembler_channel, }; @@ -473,6 +470,8 @@ impl TxPoolServiceBuilder { } }; + let (queue_tx, queue_rx) = watch::channel(0 as usize); + let verify_queue = Arc::new(RwLock::new(VerifyQueue::new(queue_tx))); let (block_assembler_sender, mut block_assembler_receiver) = self.block_assembler_channel; let service = TxPoolService { tx_pool_config: Arc::new(tx_pool.config.clone()), @@ -483,20 +482,21 @@ impl TxPoolServiceBuilder { callbacks: Arc::new(self.callbacks), tx_relay_sender: self.tx_relay_sender, block_assembler_sender, - verify_queue: self.verify_queue, + verify_queue: verify_queue.clone(), network, consensus, delay: Arc::new(RwLock::new(LinkedHashMap::new())), after_delay: Arc::new(AtomicBool::new(after_delay_window)), }; - let verify_mgr = Arc::new(RwLock::new(VerifyMgr::new( + let mut verify_mgr = VerifyMgr::new( service.clone(), self.chunk_rx, self.signal_receiver.clone(), - ))); - self.handle - .spawn(async move { verify_mgr.write().await.run().await }); + verify_queue, + queue_rx, + ); + self.handle.spawn(async move { verify_mgr.run().await }); let mut receiver = self.receiver; let mut reorg_receiver = self.reorg_receiver; diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 8e9c5d72fc..df0506d1ef 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -2,8 +2,10 @@ use crate::component::entry::TxEntry; use crate::try_or_return_with_snapshot; use crate::{error::Reject, service::TxPoolService}; -use ckb_script::ChunkCommand; +use ckb_script::{ChunkCommand, VerifyResult}; use ckb_stop_handler::CancellationToken; +use ckb_types::packed::Byte32; +use ckb_verification::cache::TxVerificationCache; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; @@ -42,7 +44,8 @@ enum State { struct Worker { tasks: Arc>, - command_rx: tokio::sync::watch::Receiver, + command_rx: watch::Receiver, + queue_rx: watch::Receiver, outbox: UnboundedSender, service: TxPoolService, exit_signal: CancellationToken, @@ -53,6 +56,7 @@ impl Clone for Worker { Self { tasks: Arc::clone(&self.tasks), command_rx: self.command_rx.clone(), + queue_rx: self.queue_rx.clone(), exit_signal: self.exit_signal.clone(), outbox: self.outbox.clone(), service: self.service.clone(), @@ -64,7 +68,8 @@ impl Worker { pub fn new( service: TxPoolService, tasks: Arc>, - command_rx: tokio::sync::watch::Receiver, + command_rx: watch::Receiver, + queue_rx: watch::Receiver, outbox: UnboundedSender, exit_signal: CancellationToken, ) -> Self { @@ -72,6 +77,7 @@ impl Worker { service, tasks, command_rx, + queue_rx, outbox, exit_signal, } @@ -80,33 +86,43 @@ impl Worker { /// start handle tasks pub fn start(mut self) -> JoinHandle<()> { tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_millis(100)); loop { - if self.exit_signal.is_cancelled() { - break; - } - - if self.tasks.read().await.get_first().is_none() { - // sleep for 100 ms - tokio::time::sleep(Duration::from_millis(10)).await; - continue; + tokio::select! { + _ = self.exit_signal.cancelled() => { + break; + } + _ = interval.tick() => { + self.process_inner().await; + } + _ = self.queue_rx.changed() => { + self.process_inner().await; + } } - // pick a entry to run verify - let entry = match self.tasks.write().await.pop_first() { - Some(entry) => entry, - None => continue, - }; - let short_id = entry.tx.proposal_short_id().to_string(); - let res = self.run_verify_tx(entry).await; - self.outbox - .send(VerifyNotify::Done { - short_id, - result: res, - }) - .unwrap(); } }) } + async fn process_inner(&mut self) -> bool { + if self.tasks.read().await.get_first().is_none() { + return false; + } + // pick a entry to run verify + let entry = match self.tasks.write().await.pop_first() { + Some(entry) => entry, + None => return false, + }; + let short_id = entry.tx.proposal_short_id().to_string(); + let res = self.run_verify_tx(entry).await; + self.outbox + .send(VerifyNotify::Done { + short_id, + result: res, + }) + .unwrap(); + true + } + async fn run_verify_tx( &mut self, entry: Entry, @@ -142,13 +158,14 @@ impl Worker { let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; try_or_return_with_snapshot!(ret, submit_snapshot); - // self.service - // .after_process(tx, remote, &submit_snapshot, &Ok(completed)) - // .await; - // self.remove_front().await; + self.service + .after_process(tx, remote, &submit_snapshot, &Ok(completed)) + .await; return Some((Ok(false), submit_snapshot)); } - CacheEntry::Suspended(_suspended) => {} + CacheEntry::Suspended(_suspended) => { + panic!("not expected"); + } } } @@ -207,6 +224,24 @@ impl Worker { } } // verify passed + + let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); + let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; + try_or_return_with_snapshot!(ret, snapshot); + + self.service.notify_block_assembler(status).await; + + self.service + .after_process(tx, remote, &submit_snapshot, &Ok(completed)) + .await; + + update_cache( + Arc::clone(&self.service.txs_verify_cache), + tx_hash, + CacheEntry::Completed(completed), + ) + .await; + return Some((Ok(false), snapshot)); } @@ -216,27 +251,36 @@ impl Worker { &mut self, rtx: Arc, data_loader: DL, - _max_cycles: Cycle, + max_cycles: Cycle, consensus: Arc, tx_env: Arc, ) -> Result { let script_verifier = ScriptVerifier::new(rtx, data_loader, consensus, tx_env); - let cycle_limit = 1000000000; - let _res = script_verifier - .resumable_verify_with_signal(cycle_limit, &mut self.command_rx) + let res = script_verifier + .resumable_verify_with_signal(max_cycles, &mut self.command_rx) .await - .unwrap(); - return Ok(State::Completed(0)); + .map_err(Reject::Verification)?; + match res { + VerifyResult::Completed(cycles) => { + return Ok(State::Completed(cycles)); + } + VerifyResult::Suspended(_) => { + panic!("not expected"); + } + } } } +async fn update_cache(cache: Arc>, tx_hash: Byte32, entry: CacheEntry) { + let mut guard = cache.write().await; + guard.put(tx_hash, entry); +} + pub(crate) struct VerifyMgr { workers: Vec, worker_notify: UnboundedReceiver, join_handles: Option>>, - pub signal_exit: CancellationToken, - pub verify_queue: Arc>, - pub service: TxPoolService, + signal_exit: CancellationToken, } impl VerifyMgr { @@ -244,21 +288,21 @@ impl VerifyMgr { service: TxPoolService, chunk_rx: watch::Receiver, signal_exit: CancellationToken, - //verify_queue: Arc>, + verify_queue: Arc>, + queue_rx: watch::Receiver, ) -> Self { let (notify_tx, notify_rx) = unbounded_channel::(); - let verify_queue = Arc::new(RwLock::new(VerifyQueue::new())); let workers: Vec<_> = (0..4) .map({ let tasks = Arc::clone(&verify_queue); let command_rx = chunk_rx.clone(); let signal_exit = signal_exit.clone(); - let service = service.clone(); move |_| { let worker = Worker::new( service.clone(), Arc::clone(&tasks), command_rx.clone(), + queue_rx.clone(), notify_tx.clone(), signal_exit.clone(), ); @@ -267,12 +311,10 @@ impl VerifyMgr { }) .collect(); Self { - service, workers, worker_notify: notify_rx, join_handles: None, signal_exit, - verify_queue, } } diff --git a/tx-pool/src/verify_queue.rs b/tx-pool/src/verify_queue.rs index 5ae0729916..f25879191f 100644 --- a/tx-pool/src/verify_queue.rs +++ b/tx-pool/src/verify_queue.rs @@ -7,6 +7,7 @@ use ckb_types::{ }; use ckb_util::shrink_to_fit; use multi_index_map::MultiIndexMap; +use tokio::sync::watch; const DEFAULT_MAX_VERIFY_TRANSACTIONS: usize = 100; const SHRINK_THRESHOLD: usize = 120; @@ -44,12 +45,14 @@ pub struct VerifyEntry { pub struct VerifyQueue { inner: MultiIndexVerifyEntryMap, + queue_tx: watch::Sender, } impl VerifyQueue { - pub(crate) fn new() -> Self { + pub(crate) fn new(queue_tx: watch::Sender) -> Self { VerifyQueue { inner: MultiIndexVerifyEntryMap::default(), + queue_tx, } } @@ -119,6 +122,7 @@ impl VerifyQueue { .as_millis() as u64, inner: Entry { tx, remote }, }); + self.queue_tx.send(self.len()).unwrap(); true } From 14c9e3def17aa76462657f5741643b826ccc5895 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 4 Jan 2024 18:11:38 +0800 Subject: [PATCH 011/294] cleanup on logs --- script/src/verify.rs | 18 +++++++----------- tx-pool/src/verify_mgr.rs | 1 + 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index b05ef8d22d..af1982bfc1 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -40,7 +40,10 @@ use ckb_vm::{ use std::collections::{BTreeMap, HashMap}; use std::sync::RwLock; use std::sync::{Arc, Mutex}; -use tokio::{select, sync::mpsc}; +use tokio::{ + select, + sync::{mpsc, oneshot, watch}, +}; #[cfg(test)] use core::sync::atomic::{AtomicBool, Ordering}; @@ -1418,11 +1421,11 @@ async fn run_vms_with_signal( async fn run_vm_with_signal( mut vm: Machine, - signal: &mut tokio::sync::watch::Receiver, + signal: &mut watch::Receiver, ) -> (Result, Cycle) { let (finished_send, mut finished_recv) = mpsc::unbounded_channel(); let pause = vm.machine.pause(); - let (child_sender, mut child_recv) = tokio::sync::watch::channel(ChunkCommand::Resume); + let (child_sender, mut child_recv) = watch::channel(ChunkCommand::Resume); child_recv.mark_changed(); let jh = tokio::spawn(async move { loop { @@ -1431,7 +1434,6 @@ async fn run_vm_with_signal( let state = child_recv.borrow().to_owned(); if state == ChunkCommand::Resume { let result = vm.run(); - eprintln!("res: {:?}", result); if matches!(result, Err(ckb_vm::Error::Pause)) { continue; } else { @@ -1440,10 +1442,6 @@ async fn run_vm_with_signal( } } } - else => { - eprintln!("now paused ..."); - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } } } }); @@ -1453,18 +1451,16 @@ async fn run_vm_with_signal( _ = signal.changed() => { let state = signal.borrow().to_owned(); if state == ChunkCommand::Suspend { - eprintln!("received pause signal ..."); pause.interrupt(); } else if state == ChunkCommand::Resume { - eprintln!("received resume signal ..."); child_sender.send(ChunkCommand::Resume).unwrap(); } } res = finished_recv.recv() => { - eprintln!("finished ...: {:?}", res); let _ = jh.await.unwrap(); return res.unwrap(); } + else => { break (Err(ckb_vm::Error::Unexpected("channel closed".into())), 0) } } } } diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index df0506d1ef..85f79aacb6 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -87,6 +87,7 @@ impl Worker { pub fn start(mut self) -> JoinHandle<()> { tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_millis(100)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { tokio::select! { _ = self.exit_signal.cancelled() => { From 0240ddf51327e1f8fd81e2a9e93de8d494120f35 Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 9 Jan 2024 03:43:20 +0800 Subject: [PATCH 012/294] add pause for child --- script/src/syscalls/spawn.rs | 20 ++++++++++---------- script/src/types.rs | 13 +++++++++++-- script/src/verify.rs | 7 ++++++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/script/src/syscalls/spawn.rs b/script/src/syscalls/spawn.rs index cf6bc8dd80..b58c0e847b 100644 --- a/script/src/syscalls/spawn.rs +++ b/script/src/syscalls/spawn.rs @@ -234,17 +234,17 @@ where update_caller_machine(machine, data, machine_child.machine.cycles(), &spawn_data)?; Ok(true) } - Err(VMError::CyclesExceeded) => { - let mut context = self - .context - .lock() - .map_err(|e| VMError::Unexpected(format!("Failed to acquire lock: {}", e)))?; - context - .suspended_machines - .push(ResumableMachine::spawn(machine_child, spawn_data)); - Err(VMError::CyclesExceeded) + Err(err) => { + if matches!(err, VMError::CyclesExceeded | VMError::Pause) { + let mut context = self.context.lock().map_err(|e| { + VMError::Unexpected(format!("Failed to acquire lock: {}", e)) + })?; + context + .suspended_machines + .push(ResumableMachine::spawn(machine_child, spawn_data)); + } + Err(err) } - Err(err) => Err(err), } } } diff --git a/script/src/types.rs b/script/src/types.rs index b822cbe32e..cf75a076a9 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -5,13 +5,13 @@ use ckb_types::{ packed::{Byte32, Script}, }; use ckb_vm::{ - machine::{VERSION0, VERSION1, VERSION2}, + machine::{Pause, VERSION0, VERSION1, VERSION2}, snapshot::{make_snapshot, Snapshot}, Error as VMInternalError, SupportMachine, ISA_A, ISA_B, ISA_IMC, ISA_MOP, }; use serde::{Deserialize, Serialize}; -use std::fmt; use std::sync::{Arc, Mutex}; +use std::{fmt, sync::atomic::AtomicU8}; #[cfg(has_asm)] use ckb_vm::machine::asm::{AsmCoreMachine, AsmMachine}; @@ -119,6 +119,14 @@ pub(crate) type Machine = TraceMachine; pub struct MachineContext { /// A stack of ResumableMachines. pub suspended_machines: Vec, + pub pause: Pause, +} + +impl MachineContext { + /// Creates a new MachineContext struct + pub fn set_pause(&mut self, pause: Pause) { + self.pause = pause; + } } /// Data structure captured all environment data for a suspended machine @@ -163,6 +171,7 @@ impl TryFrom<&SpawnData> for ResumePoint { caller_content_addr, caller_content_length_addr, cycles_base, + .. } = value; Ok(ResumePoint::Spawn { callee_peak_memory: *callee_peak_memory, diff --git a/script/src/verify.rs b/script/src/verify.rs index af1982bfc1..d868a3eef9 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -37,9 +37,12 @@ use ckb_vm::{ snapshot::{resume, Snapshot}, DefaultMachineBuilder, Error as VMInternalError, SupportMachine, Syscalls, }; -use std::collections::{BTreeMap, HashMap}; use std::sync::RwLock; use std::sync::{Arc, Mutex}; +use std::{ + collections::{BTreeMap, HashMap}, + sync::atomic::AtomicU8, +}; use tokio::{ select, sync::{mpsc, oneshot, watch}, @@ -1194,6 +1197,8 @@ impl Date: Tue, 9 Jan 2024 23:21:57 +0800 Subject: [PATCH 013/294] more on pause for spwan --- Cargo.lock | 10 +- docs/ckb_rpc_openrpc | 2 +- script/Cargo.toml | 2 +- script/src/syscalls/spawn.rs | 10 +- script/src/types.rs | 7 +- script/src/verify.rs | 172 ++++++++++++++++++++++++----------- tx-pool/src/process.rs | 2 +- tx-pool/src/verify_mgr.rs | 29 ++++-- 8 files changed, 161 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec3ce7f51d..4a5e6af785 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1733,9 +1733,8 @@ dependencies = [ [[package]] name = "ckb-vm" -version = "0.24.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8332997ee3beacb0c1b9e2489e17b33af855a0ec28d7c08a81170fae6b204340" +version = "0.24.7" +source = "git+https://github.com/chenyukang/ckb-vm.git?branch=yukang-local-changes#f799b2a4416197a4e6c15372983feac649f6e5bd" dependencies = [ "byteorder", "bytes", @@ -1751,9 +1750,8 @@ dependencies = [ [[package]] name = "ckb-vm-definitions" -version = "0.24.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f6fa54fd079938807cce5b11b4fbb9b21984568b887204ea96a02dbd907c2f" +version = "0.24.7" +source = "git+https://github.com/chenyukang/ckb-vm.git?branch=yukang-local-changes#f799b2a4416197a4e6c15372983feac649f6e5bd" dependencies = [ "paste", ] diff --git a/docs/ckb_rpc_openrpc b/docs/ckb_rpc_openrpc index 5d696307ed..7fe6f991ea 160000 --- a/docs/ckb_rpc_openrpc +++ b/docs/ckb_rpc_openrpc @@ -1 +1 @@ -Subproject commit 5d696307edb59dfa198fb78800ae14588d4bafd8 +Subproject commit 7fe6f991ea616616e6b695e37ee19d35d3d89ca5 diff --git a/script/Cargo.toml b/script/Cargo.toml index 60c989b930..fdeb61fb66 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -22,7 +22,7 @@ ckb-traits = { path = "../traits", version = "= 0.114.0-pre" } byteorder = "1.3.1" ckb-types = { path = "../util/types", version = "= 0.114.0-pre" } ckb-hash = { path = "../util/hash", version = "= 0.114.0-pre" } -ckb-vm = { version = "=0.24.8", default-features = false } +ckb-vm = { git = "https://github.com/chenyukang/ckb-vm.git", version = "=0.24.7", default-features = false, branch = "yukang-local-changes"} faster-hex = "0.6" ckb-logger = { path = "../util/logger", version = "= 0.114.0-pre", optional = true } serde = { version = "1.0", features = ["derive"] } diff --git a/script/src/syscalls/spawn.rs b/script/src/syscalls/spawn.rs index b58c0e847b..c5a8eb7548 100644 --- a/script/src/syscalls/spawn.rs +++ b/script/src/syscalls/spawn.rs @@ -276,8 +276,14 @@ pub fn build_child_machine< cycles_limit, (callee_memory_limit * SPAWN_MEMORY_PAGE_SIZE) as usize, ); - let machine_builder = - DefaultMachineBuilder::new(machine_core).instruction_cycle_func(Box::new(estimate_cycles)); + let pause = context + .lock() + .map_err(|e| VMError::Unexpected(format!("Failed to acquire lock: {}", e)))? + .pause + .clone(); + let machine_builder = DefaultMachineBuilder::new(machine_core) + .instruction_cycle_func(Box::new(estimate_cycles)) + .pause(pause); let machine_syscalls = syscalls_generator.generate_same_syscalls(script_version, script_group); let machine_builder = machine_syscalls .into_iter() diff --git a/script/src/types.rs b/script/src/types.rs index cf75a076a9..d737c6a815 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -10,8 +10,8 @@ use ckb_vm::{ Error as VMInternalError, SupportMachine, ISA_A, ISA_B, ISA_IMC, ISA_MOP, }; use serde::{Deserialize, Serialize}; +use std::fmt; use std::sync::{Arc, Mutex}; -use std::{fmt, sync::atomic::AtomicU8}; #[cfg(has_asm)] use ckb_vm::machine::asm::{AsmCoreMachine, AsmMachine}; @@ -224,6 +224,10 @@ impl ResumableMachine { self.machine().machine.cycles() } + pub(crate) fn pause(&self) -> Pause { + self.machine().machine.pause() + } + pub(crate) fn set_max_cycles(&mut self, cycles: Cycle) { set_vm_max_cycles(self.machine_mut(), cycles) } @@ -454,5 +458,4 @@ impl std::fmt::Debug for TransactionState { pub enum ChunkCommand { Suspend, Resume, - Shutdown, } diff --git a/script/src/verify.rs b/script/src/verify.rs index d868a3eef9..1e0fdde26e 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -37,15 +37,12 @@ use ckb_vm::{ snapshot::{resume, Snapshot}, DefaultMachineBuilder, Error as VMInternalError, SupportMachine, Syscalls, }; +use std::collections::{BTreeMap, HashMap}; use std::sync::RwLock; use std::sync::{Arc, Mutex}; -use std::{ - collections::{BTreeMap, HashMap}, - sync::atomic::AtomicU8, -}; use tokio::{ select, - sync::{mpsc, oneshot, watch}, + sync::{mpsc, watch}, }; #[cfg(test)] @@ -1199,10 +1196,10 @@ impl, - command_rx: &mut tokio::sync::watch::Receiver, + _max_cycles: Cycle, + mut machines: Vec, + context: &Arc>, + signal: &mut tokio::sync::watch::Receiver, ) -> Result { - let (mut exit_code, mut cycles) = (0, 0); - if machines.is_empty() { return Err(ScriptError::VMInternalError( "At least one VM must be available!".to_string(), )); } - while let Some(machine) = machines.pop() { - match run_vm_with_signal(machine, command_rx).await { - (Ok(code), run_cycles) => { - exit_code = code; - cycles = run_cycles; - } - (Err(error), _) => match error { - VMInternalError::CyclesExceeded => { - return Err(ScriptError::ExceededMaximumCycles(max_cycles)) - } - _ => return Err(ScriptError::VMInternalError(format!("{error:?}"))), - }, - }; - } - - if exit_code == 0 { - Ok(ChunkState::Completed(cycles)) - } else { - Err(ScriptError::validation_failure( - &script_group.script, - exit_code, - )) - } -} - -async fn run_vm_with_signal( - mut vm: Machine, - signal: &mut watch::Receiver, -) -> (Result, Cycle) { - let (finished_send, mut finished_recv) = mpsc::unbounded_channel(); - let pause = vm.machine.pause(); + let pause = machines[0].pause(); + let (finished_send, mut finished_recv) = + mpsc::unbounded_channel::<(Result, u64)>(); let (child_sender, mut child_recv) = watch::channel(ChunkCommand::Resume); child_recv.mark_changed(); + let context_clone = context.clone(); let jh = tokio::spawn(async move { + let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); loop { select! { _ = child_recv.changed() => { let state = child_recv.borrow().to_owned(); if state == ChunkCommand::Resume { - let result = vm.run(); - if matches!(result, Err(ckb_vm::Error::Pause)) { - continue; - } else { - finished_send.send((result, vm.machine.cycles())).unwrap(); + if machines.len() == 0 { + finished_send.send((Ok(exit_code), cycles)).unwrap(); return; } + + while let Some(mut machine) = machines.pop() { + if let Some(callee_spawn_data) = &spawn_data { + update_caller_machine( + &mut machine.machine_mut().machine, + exit_code, + cycles, + callee_spawn_data, + ) + .unwrap(); + } + + let res = machine.run(); + match res { + Ok(code) => { + exit_code = code; + cycles = machine.cycles(); + if let ResumableMachine::Spawn(_, data) = machine { + spawn_data = Some(data); + } else { + spawn_data = None; + } + } + Err(VMInternalError::Pause) => { + let mut new_suspended_machines: Vec<_> = { + let mut context = context_clone.lock().map_err(|e| { + ScriptError::VMInternalError(format!( + "Failed to acquire lock: {}", + e + )) + }).unwrap(); + context.suspended_machines.drain(..).collect() + }; + // The inner most machine lives at the top of the vector, + // reverse the list for natural order. + new_suspended_machines.reverse(); + machines.push(machine); + machines.append(&mut new_suspended_machines); + } + _ => { + finished_send.send((res, machine.cycles())).unwrap(); + } + }; + } } } } @@ -1462,14 +1473,73 @@ async fn run_vm_with_signal( } } res = finished_recv.recv() => { - let _ = jh.await.unwrap(); - return res.unwrap(); + let _ = jh.await; + match res.unwrap() { + (Ok(0), cycles) => { + return Ok(ChunkState::Completed(cycles)); + } + (Ok(exit_code), _) => { + return Err(ScriptError::validation_failure( + &script_group.script, + exit_code + ))}, + (Err(e), _) => { + return Err(ScriptError::VMInternalError(format!("{e:?}"))); + } + } + } - else => { break (Err(ckb_vm::Error::Unexpected("channel closed".into())), 0) } + else => { break Err(ScriptError::validation_failure(&script_group.script, 0)) } } } } +// async fn run_vm_with_signal( +// mut vm: Machine, +// signal: &mut watch::Receiver, +// ) -> (Result, Cycle) { +// let (finished_send, mut finished_recv) = mpsc::unbounded_channel(); +// let pause = vm.machine.pause(); +// let (child_sender, mut child_recv) = watch::channel(ChunkCommand::Resume); +// child_recv.mark_changed(); +// let jh = tokio::spawn(async move { +// loop { +// select! { +// _ = child_recv.changed() => { +// let state = child_recv.borrow().to_owned(); +// if state == ChunkCommand::Resume { +// let result = vm.run(); +// if matches!(result, Err(ckb_vm::Error::Pause)) { +// continue; +// } else { +// finished_send.send((result, vm.machine.cycles())).unwrap(); +// return; +// } +// } +// } +// } +// } +// }); + +// loop { +// tokio::select! { +// _ = signal.changed() => { +// let state = signal.borrow().to_owned(); +// if state == ChunkCommand::Suspend { +// pause.interrupt(); +// } else if state == ChunkCommand::Resume { +// child_sender.send(ChunkCommand::Resume).unwrap(); +// } +// } +// res = finished_recv.recv() => { +// let _ = jh.await.unwrap(); +// return res.unwrap(); +// } +// else => { break (Err(ckb_vm::Error::Unexpected("channel closed".into())), 0) } +// } +// } +// } + fn wrapping_cycles_add( lhs: Cycle, rhs: Cycle, diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 2ba4943ce0..df4bf93d3a 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -704,7 +704,7 @@ impl TxPoolService { } ScriptVerifyResult::Suspended(state) => { if is_chunk_full { - Err(Reject::Full("chunk".to_owned())) + Err(Reject::Full("chunk is full".to_owned())) } else { let snap = Arc::new(state.try_into().map_err(Reject::Verification)?); Ok(CacheEntry::suspended(snap, fee)) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 85f79aacb6..30354cdf3f 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -32,12 +32,12 @@ type Stop = bool; pub enum VerifyNotify { Done { short_id: String, - result: Option<(Result, Arc)>, + result: (Result, Arc), }, } enum State { - Stopped, + //Stopped, //Suspended(Arc), Completed(Cycle), } @@ -104,24 +104,35 @@ impl Worker { }) } - async fn process_inner(&mut self) -> bool { + async fn process_inner(&mut self) { if self.tasks.read().await.get_first().is_none() { - return false; + return; } // pick a entry to run verify let entry = match self.tasks.write().await.pop_first() { Some(entry) => entry, - None => return false, + None => return, }; let short_id = entry.tx.proposal_short_id().to_string(); - let res = self.run_verify_tx(entry).await; + let (res, snapshot) = self + .run_verify_tx(entry.clone()) + .await + .expect("run_verify_tx failed"); self.outbox .send(VerifyNotify::Done { short_id, - result: res, + result: (res.clone(), snapshot.clone()), }) .unwrap(); - true + + match res { + Err(e) => { + self.service + .after_process(entry.tx, entry.remote, &snapshot, &Err(e)) + .await; + } + _ => {} + } } async fn run_verify_tx( @@ -210,7 +221,7 @@ impl Worker { let completed: Completed = match state { // verify failed - State::Stopped => return Some((Ok(true), snapshot)), + // State::Stopped => return Some((Ok(true), snapshot)), State::Completed(cycles) => Completed { cycles, fee }, }; if let Some((declared_cycle, _peer)) = remote { From 731730fe3134d8f899b3b3cb56147024ab74f0df Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 13 Jan 2024 20:04:27 +0800 Subject: [PATCH 014/294] fix remove_tx in verify_queue --- tx-pool/src/verify_queue.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tx-pool/src/verify_queue.rs b/tx-pool/src/verify_queue.rs index f25879191f..7cbaf76517 100644 --- a/tx-pool/src/verify_queue.rs +++ b/tx-pool/src/verify_queue.rs @@ -78,9 +78,10 @@ impl VerifyQueue { } pub fn remove_tx(&mut self, id: &ProposalShortId) -> Option { - let ret = self.inner.remove_by_id(id); - self.shrink_to_fit(); - Some(ret.unwrap().inner) + self.inner.remove_by_id(id).map(|e| { + self.shrink_to_fit(); + e.inner + }) } pub fn remove_txs(&mut self, ids: impl Iterator) { From 81ebc61ce3f53270807aee1bc9622cb76528823a Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 14 Jan 2024 00:40:55 +0800 Subject: [PATCH 015/294] fix logic issue in rum_vms with signal pass large cycles txs --- script/src/verify.rs | 106 +++++++++++++++++++++--------------- tx-pool/src/process.rs | 3 + tx-pool/src/verify_mgr.rs | 11 +++- tx-pool/src/verify_queue.rs | 1 + 4 files changed, 75 insertions(+), 46 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 1e0fdde26e..710e21d552 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1400,62 +1400,78 @@ async fn run_vms_with_signal( mpsc::unbounded_channel::<(Result, u64)>(); let (child_sender, mut child_recv) = watch::channel(ChunkCommand::Resume); child_recv.mark_changed(); + eprintln!("begin to run vms with signal: vms len {}", machines.len()); let context_clone = context.clone(); let jh = tokio::spawn(async move { let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); loop { + eprintln!("begin to loop ........."); select! { _ = child_recv.changed() => { let state = child_recv.borrow().to_owned(); - if state == ChunkCommand::Resume { - if machines.len() == 0 { - finished_send.send((Ok(exit_code), cycles)).unwrap(); - return; - } + if state != ChunkCommand::Resume { + continue; + } + eprintln!("first resume here: {:?}", machines.len()); + if machines.len() == 0 { + finished_send.send((Ok(exit_code), cycles)).unwrap(); + return; + } - while let Some(mut machine) = machines.pop() { - if let Some(callee_spawn_data) = &spawn_data { - update_caller_machine( - &mut machine.machine_mut().machine, - exit_code, - cycles, - callee_spawn_data, - ) - .unwrap(); - } + while let Some(mut machine) = machines.pop() { + eprintln!("first run vm now {}", machine.cycles()); + if let Some(callee_spawn_data) = &spawn_data { + update_caller_machine( + &mut machine.machine_mut().machine, + exit_code, + cycles, + callee_spawn_data, + ) + .unwrap(); + } - let res = machine.run(); - match res { - Ok(code) => { - exit_code = code; - cycles = machine.cycles(); - if let ResumableMachine::Spawn(_, data) = machine { - spawn_data = Some(data); - } else { - spawn_data = None; - } - } - Err(VMInternalError::Pause) => { - let mut new_suspended_machines: Vec<_> = { - let mut context = context_clone.lock().map_err(|e| { - ScriptError::VMInternalError(format!( - "Failed to acquire lock: {}", - e - )) - }).unwrap(); - context.suspended_machines.drain(..).collect() - }; - // The inner most machine lives at the top of the vector, - // reverse the list for natural order. - new_suspended_machines.reverse(); - machines.push(machine); - machines.append(&mut new_suspended_machines); + let res = machine.run(); + eprintln!("first run vm now {} res: {:?}", machine.cycles(), res); + match res { + Ok(code) => { + exit_code = code; + cycles = machine.cycles(); + if let ResumableMachine::Spawn(_, data) = machine { + spawn_data = Some(data); + } else { + spawn_data = None; } - _ => { - finished_send.send((res, machine.cycles())).unwrap(); + eprintln!("finished run vm: {}", machines.len()); + if machines.len() == 0 { + finished_send.send((Ok(exit_code), cycles)).unwrap(); + return; } - }; - } + } + Err(VMInternalError::Pause) => { + let mut new_suspended_machines: Vec<_> = { + let mut context = context_clone.lock().map_err(|e| { + ScriptError::VMInternalError(format!( + "Failed to acquire lock: {}", + e + )) + }).unwrap(); + context.suspended_machines.drain(..).collect() + }; + // The inner most machine lives at the top of the vector, + // reverse the list for natural order. + new_suspended_machines.reverse(); + machines.push(machine); + machines.append(&mut new_suspended_machines); + // wait for Resume command to begin next loop iteration + break; + } + _ => { + // other error happened here, for example CyclesExceeded, + // we need to return as verification failed + finished_send.send((res, machine.cycles())).unwrap(); + return; + } + }; } } } diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index df4bf93d3a..fc15cdedbf 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -303,6 +303,7 @@ impl TxPoolService { } if let Some((ret, snapshot)) = self.verify_or_add_to_queue(tx.clone(), remote).await { + eprintln!("resumeble_process_tx: ret = {:?}", ret); match ret { Ok(processed) => { if let ProcessResult::Completed(completed) = processed { @@ -684,6 +685,7 @@ impl TxPoolService { .resumable_verify(self.tx_pool_config.max_tx_verify_cycles) .map_err(Reject::Verification)?; + eprintln!("first verify: {:?}", ret); match ret { ScriptVerifyResult::Completed(cycles) => { if let Err(e) = DaoScriptSizeVerifier::new( @@ -720,6 +722,7 @@ impl TxPoolService { .enqueue_suspended_tx(rtx.transaction.clone(), cached, remote) .await; try_or_return_with_snapshot!(ret, snapshot); + eprintln!("added to queue here: {:?}", tx.proposal_short_id()); return Some((Ok(ProcessResult::Suspended), snapshot)); } CacheEntry::Completed(completed) => completed, diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 30354cdf3f..8da13401dd 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -36,6 +36,7 @@ pub enum VerifyNotify { }, } +#[derive(Clone, Debug)] enum State { //Stopped, //Suspended(Arc), @@ -113,11 +114,13 @@ impl Worker { Some(entry) => entry, None => return, }; + eprintln!("begin to process: {:?}", entry); let short_id = entry.tx.proposal_short_id().to_string(); let (res, snapshot) = self .run_verify_tx(entry.clone()) .await .expect("run_verify_tx failed"); + eprintln!("process done: {:?}", res); self.outbox .send(VerifyNotify::Done { short_id, @@ -152,6 +155,7 @@ impl Worker { let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); + eprintln!("run_verify_tx cached: {:?}", cached); if let Some(ref cached) = cached { match cached { CacheEntry::Completed(completed) => { @@ -176,7 +180,8 @@ impl Worker { return Some((Ok(false), submit_snapshot)); } CacheEntry::Suspended(_suspended) => { - panic!("not expected"); + eprintln!("not expected suspended: {:?}", cached); + //panic!("not expected"); } } } @@ -208,6 +213,7 @@ impl Worker { consensus.max_block_cycles() }; + eprintln!("begin to loop: {:?}", rtx); let ret = self .loop_resume( Arc::clone(&rtx), @@ -217,6 +223,7 @@ impl Worker { Arc::clone(&tx_env), ) .await; + eprintln!("loop done: {:?}", ret); let state = try_or_return_with_snapshot!(ret, snapshot); let completed: Completed = match state { @@ -224,6 +231,8 @@ impl Worker { // State::Stopped => return Some((Ok(true), snapshot)), State::Completed(cycles) => Completed { cycles, fee }, }; + eprintln!("completed: {:?}", completed); + eprintln!("remote: {:?}", remote); if let Some((declared_cycle, _peer)) = remote { if declared_cycle != completed.cycles { return Some(( diff --git a/tx-pool/src/verify_queue.rs b/tx-pool/src/verify_queue.rs index 7cbaf76517..b31eedcbb7 100644 --- a/tx-pool/src/verify_queue.rs +++ b/tx-pool/src/verify_queue.rs @@ -123,6 +123,7 @@ impl VerifyQueue { .as_millis() as u64, inner: Entry { tx, remote }, }); + eprintln!("added to queue len: {:?}", self.len()); self.queue_tx.send(self.len()).unwrap(); true } From 22614c1145cae3c1b1ac1912a90ab2d451f5a1b3 Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 14 Jan 2024 01:07:53 +0800 Subject: [PATCH 016/294] clean up and fix tests --- script/src/verify.rs | 46 ---------------------------- tx-pool/src/component/tests/chunk.rs | 14 +++++---- 2 files changed, 8 insertions(+), 52 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 710e21d552..2d4df719a5 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1510,52 +1510,6 @@ async fn run_vms_with_signal( } } -// async fn run_vm_with_signal( -// mut vm: Machine, -// signal: &mut watch::Receiver, -// ) -> (Result, Cycle) { -// let (finished_send, mut finished_recv) = mpsc::unbounded_channel(); -// let pause = vm.machine.pause(); -// let (child_sender, mut child_recv) = watch::channel(ChunkCommand::Resume); -// child_recv.mark_changed(); -// let jh = tokio::spawn(async move { -// loop { -// select! { -// _ = child_recv.changed() => { -// let state = child_recv.borrow().to_owned(); -// if state == ChunkCommand::Resume { -// let result = vm.run(); -// if matches!(result, Err(ckb_vm::Error::Pause)) { -// continue; -// } else { -// finished_send.send((result, vm.machine.cycles())).unwrap(); -// return; -// } -// } -// } -// } -// } -// }); - -// loop { -// tokio::select! { -// _ = signal.changed() => { -// let state = signal.borrow().to_owned(); -// if state == ChunkCommand::Suspend { -// pause.interrupt(); -// } else if state == ChunkCommand::Resume { -// child_sender.send(ChunkCommand::Resume).unwrap(); -// } -// } -// res = finished_recv.recv() => { -// let _ = jh.await.unwrap(); -// return res.unwrap(); -// } -// else => { break (Err(ckb_vm::Error::Unexpected("channel closed".into())), 0) } -// } -// } -// } - fn wrapping_cycles_add( lhs: Cycle, rhs: Cycle, diff --git a/tx-pool/src/component/tests/chunk.rs b/tx-pool/src/component/tests/chunk.rs index a811133584..4ec300efcc 100644 --- a/tx-pool/src/component/tests/chunk.rs +++ b/tx-pool/src/component/tests/chunk.rs @@ -1,6 +1,7 @@ use ckb_types::core::TransactionBuilder; +use tokio::sync::watch; -use crate::component::chunk::{ChunkQueue, Entry}; +use crate::verify_queue::{Entry, VerifyQueue}; #[test] fn basic() { @@ -10,13 +11,14 @@ fn basic() { remote: None, }; let id = tx.proposal_short_id(); - let mut queue = ChunkQueue::new(); + let (queue_tx, _queue_rx) = watch::channel(0 as usize); + let mut queue = VerifyQueue::new(queue_tx); assert!(queue.add_tx(tx.clone(), None)); - assert_eq!(queue.pop_front().as_ref(), Some(&entry)); - assert!(queue.contains_key(&id)); - assert!(!queue.add_tx(tx, None)); + assert_eq!(queue.pop_first().as_ref(), Some(&entry)); + assert!(!queue.contains_key(&id)); + assert!(queue.add_tx(tx, None)); - queue.clean_front(); + queue.clear(); assert!(!queue.contains_key(&id)); } From 57a185fa333b01b26fe0055eb65681e52312ee2e Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 14 Jan 2024 01:33:18 +0800 Subject: [PATCH 017/294] fix merge conflicts --- script/src/verify.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 2d4df719a5..a52b68b39d 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -701,10 +701,8 @@ impl ScriptError::ExceededMaximumCycles(max_cycles), - _ => ScriptError::VMInternalError(format!("{error:?}")), + _ => ScriptError::VMInternalError(error), }; let machines = { @@ -1193,7 +1191,7 @@ impl, ) -> Result { if machines.is_empty() { - return Err(ScriptError::VMInternalError( - "At least one VM must be available!".to_string(), + return Err(ScriptError::Other( + "To resume VMs, at least one VM must be available!".to_string(), )); } @@ -1450,10 +1448,7 @@ async fn run_vms_with_signal( Err(VMInternalError::Pause) => { let mut new_suspended_machines: Vec<_> = { let mut context = context_clone.lock().map_err(|e| { - ScriptError::VMInternalError(format!( - "Failed to acquire lock: {}", - e - )) + ScriptError::Other(format!("Failed to acquire lock: {}", e)) }).unwrap(); context.suspended_machines.drain(..).collect() }; @@ -1499,8 +1494,8 @@ async fn run_vms_with_signal( &script_group.script, exit_code ))}, - (Err(e), _) => { - return Err(ScriptError::VMInternalError(format!("{e:?}"))); + (Err(err), _) => { + return Err(ScriptError::VMInternalError(err)); } } From 91d346c783821be8fa2ff6c37207c13608ccf054 Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 14 Jan 2024 02:07:48 +0800 Subject: [PATCH 018/294] debug command tx --- chain/src/chain.rs | 1 + script/src/verify.rs | 1 + tx-pool/src/verify_mgr.rs | 12 ++++++------ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/chain/src/chain.rs b/chain/src/chain.rs index a42aa788b0..bdc47a2483 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -240,6 +240,7 @@ impl ChainService { let instant = Instant::now(); let _ = tx_control.suspend_chunk_process(); + eprintln!("suspend chunk process"); let _ = responder.send(self.process_block(block, verify)); let _ = tx_control.continue_chunk_process(); diff --git a/script/src/verify.rs b/script/src/verify.rs index a52b68b39d..e43e2c836e 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1370,6 +1370,7 @@ fn run_vms( } if exit_code == 0 { + eprintln!("run_vms finished: {} cycles: {}", exit_code, cycles); Ok(ChunkState::Completed(cycles)) } else { Err(ScriptError::validation_failure( diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 8da13401dd..b5f9b5f5df 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -19,7 +19,7 @@ use ckb_snapshot::Snapshot; use ckb_store::data_loader_wrapper::AsDataLoader; use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; use ckb_types::core::{cell::ResolvedTransaction, Cycle}; -//use ckb_verification::cache::TxVerificationCache; + use ckb_verification::{ cache::{CacheEntry, Completed}, ContextualWithoutScriptTransactionVerifier, DaoScriptSizeVerifier, ScriptVerifier, @@ -302,6 +302,7 @@ pub(crate) struct VerifyMgr { worker_notify: UnboundedReceiver, join_handles: Option>>, signal_exit: CancellationToken, + command_rx: watch::Receiver, } impl VerifyMgr { @@ -336,6 +337,7 @@ impl VerifyMgr { worker_notify: notify_rx, join_handles: None, signal_exit, + command_rx: chunk_rx, } } @@ -349,19 +351,17 @@ impl VerifyMgr { } async fn start_loop(&mut self) { - let mut interval = tokio::time::interval(Duration::from_micros(1000)); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { tokio::select! { _ = self.signal_exit.cancelled() => { break; }, + _ = self.command_rx.changed() => { + eprintln!("command: {:?}", self.command_rx.borrow()); + } res = self.worker_notify.recv() => { eprintln!("res: {:?}", res); } - _ = interval.tick() => { - tokio::time::sleep(Duration::from_millis(50)).await; - } } } } From 3a50eaa7437f80dcf396e9b7a552a6393dc3b30a Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 14 Jan 2024 11:04:06 +0800 Subject: [PATCH 019/294] make clippy happy --- script/src/types.rs | 6 +++++ script/src/verify.rs | 10 +++++---- tx-pool/src/component/tests/chunk.rs | 2 +- tx-pool/src/service.rs | 4 ++-- tx-pool/src/verify_mgr.rs | 24 ++++++++------------ tx-pool/src/verify_queue.rs | 33 ++++++++++++++++++---------- tx-pool/src/verify_worker.rs | 0 7 files changed, 45 insertions(+), 34 deletions(-) delete mode 100644 tx-pool/src/verify_worker.rs diff --git a/script/src/types.rs b/script/src/types.rs index d737c6a815..9b4e02de0f 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -119,6 +119,9 @@ pub(crate) type Machine = TraceMachine; pub struct MachineContext { /// A stack of ResumableMachines. pub suspended_machines: Vec, + /// A pause will be set for suspend machines. + /// The child machine will reuse parent machine's pause, + /// so that when parent is paused, all its children will be paused. pub pause: Pause, } @@ -454,8 +457,11 @@ impl std::fmt::Debug for TransactionState { } } +/// ChunkCommand is used to control the verification process to suspend or resume #[derive(Eq, PartialEq, Clone, Debug)] pub enum ChunkCommand { + /// Suspend the verification process Suspend, + /// Resume the verification process Resume, } diff --git a/script/src/verify.rs b/script/src/verify.rs index e43e2c836e..188007ea31 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -690,6 +690,8 @@ impl { - self.service - .after_process(entry.tx, entry.remote, &snapshot, &Err(e)) - .await; - } - _ => {} + if let Err(e) = res { + self.service + .after_process(entry.tx, entry.remote, &snapshot, &Err(e)) + .await; } } @@ -263,7 +260,7 @@ impl Worker { ) .await; - return Some((Ok(false), snapshot)); + Some((Ok(false), snapshot)) } async fn loop_resume< @@ -282,9 +279,7 @@ impl Worker { .await .map_err(Reject::Verification)?; match res { - VerifyResult::Completed(cycles) => { - return Ok(State::Completed(cycles)); - } + VerifyResult::Completed(cycles) => Ok(State::Completed(cycles)), VerifyResult::Suspended(_) => { panic!("not expected"); } @@ -320,15 +315,14 @@ impl VerifyMgr { let command_rx = chunk_rx.clone(); let signal_exit = signal_exit.clone(); move |_| { - let worker = Worker::new( + Worker::new( service.clone(), Arc::clone(&tasks), command_rx.clone(), queue_rx.clone(), notify_tx.clone(), signal_exit.clone(), - ); - worker + ) } }) .collect(); diff --git a/tx-pool/src/verify_queue.rs b/tx-pool/src/verify_queue.rs index b31eedcbb7..3e3dc05135 100644 --- a/tx-pool/src/verify_queue.rs +++ b/tx-pool/src/verify_queue.rs @@ -1,3 +1,5 @@ +//! Top-level VerifyQueue structure. +#![allow(missing_docs)] extern crate rustc_hash; extern crate slab; use ckb_network::PeerIndex; @@ -12,6 +14,7 @@ use tokio::sync::watch; const DEFAULT_MAX_VERIFY_TRANSACTIONS: usize = 100; const SHRINK_THRESHOLD: usize = 120; +/// The verify queue is a priority queue of transactions to verify. #[derive(Debug, Clone, Eq)] pub struct Entry { pub(crate) tx: TransactionView, @@ -24,31 +27,30 @@ impl PartialEq for Entry { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum VerifyStatus { - Fresh, - Verifying, - Completed, -} - #[derive(MultiIndexMap, Clone)] pub struct VerifyEntry { + /// The transaction id #[multi_index(hashed_unique)] pub id: ProposalShortId, - #[multi_index(hashed_non_unique)] - pub status: VerifyStatus, + /// The unix timestamp when entering the Txpool, unit: Millisecond + /// This field is used to sort the txs in the queue + /// We may add more other sort keys in the future #[multi_index(ordered_non_unique)] pub added_time: u64, - // other sort key + /// other sort key pub inner: Entry, } +/// The verify queue is a priority queue of transactions to verify. pub struct VerifyQueue { + /// inner tx entry inner: MultiIndexVerifyEntryMap, + /// used to notify the tx-pool to update the txs count queue_tx: watch::Sender, } impl VerifyQueue { + /// Create a new VerifyQueue pub(crate) fn new(queue_tx: watch::Sender) -> Self { VerifyQueue { inner: MultiIndexVerifyEntryMap::default(), @@ -56,27 +58,33 @@ impl VerifyQueue { } } + /// Returns the number of txs in the queue. pub fn len(&self) -> usize { self.inner.len() } + /// Returns true if the queue contains no txs. #[allow(dead_code)] pub fn is_empty(&self) -> bool { self.inner.is_empty() } + /// Returns true if the queue is full. pub fn is_full(&self) -> bool { self.len() > DEFAULT_MAX_VERIFY_TRANSACTIONS } + /// Returns true if the queue contains a tx with the specified id. pub fn contains_key(&self, id: &ProposalShortId) -> bool { self.inner.get_by_id(id).is_some() } + /// Shrink the capacity of the queue as much as possible. pub fn shrink_to_fit(&mut self) { shrink_to_fit!(self.inner, SHRINK_THRESHOLD); } + /// Remove a tx from the queue pub fn remove_tx(&mut self, id: &ProposalShortId) -> Option { self.inner.remove_by_id(id).map(|e| { self.shrink_to_fit(); @@ -84,6 +92,7 @@ impl VerifyQueue { }) } + /// Remove multiple txs from the queue pub fn remove_txs(&mut self, ids: impl Iterator) { for id in ids { self.inner.remove_by_id(&id); @@ -91,6 +100,7 @@ impl VerifyQueue { self.shrink_to_fit(); } + /// Returns the first entry in the queue and remove it pub fn pop_first(&mut self) -> Option { if let Some(entry) = self.get_first() { self.remove_tx(&entry.tx.proposal_short_id()); @@ -100,10 +110,10 @@ impl VerifyQueue { } } + /// Returns the first entry in the queue pub fn get_first(&self) -> Option { self.inner .iter_by_added_time() - .filter(|e| e.status == VerifyStatus::Fresh) .next() .map(|entry| entry.inner.clone()) } @@ -116,7 +126,6 @@ impl VerifyQueue { } self.inner.insert(VerifyEntry { id: tx.proposal_short_id(), - status: VerifyStatus::Fresh, added_time: std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .expect("timestamp") diff --git a/tx-pool/src/verify_worker.rs b/tx-pool/src/verify_worker.rs deleted file mode 100644 index e69de29bb2..0000000000 From 0e922331cda9b71a4ad1495d86d773e9be1cf28b Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 14 Jan 2024 11:19:44 +0800 Subject: [PATCH 020/294] make ci happy --- Cargo.lock | 10 ++++++++++ tx-pool/src/verify_mgr.rs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a5e6af785..3d7e210a44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1733,8 +1733,13 @@ dependencies = [ [[package]] name = "ckb-vm" +<<<<<<< HEAD version = "0.24.7" source = "git+https://github.com/chenyukang/ckb-vm.git?branch=yukang-local-changes#f799b2a4416197a4e6c15372983feac649f6e5bd" +======= +version = "0.24.6" +source = "git+https://github.com/chenyukang/ckb-vm.git?branch=yukang-local-changes#cf45b0efac1b6c2a1b26b3d741a82778bdd50647" +>>>>>>> 4efe2834d (make ci happy) dependencies = [ "byteorder", "bytes", @@ -1750,8 +1755,13 @@ dependencies = [ [[package]] name = "ckb-vm-definitions" +<<<<<<< HEAD version = "0.24.7" source = "git+https://github.com/chenyukang/ckb-vm.git?branch=yukang-local-changes#f799b2a4416197a4e6c15372983feac649f6e5bd" +======= +version = "0.24.6" +source = "git+https://github.com/chenyukang/ckb-vm.git?branch=yukang-local-changes#cf45b0efac1b6c2a1b26b3d741a82778bdd50647" +>>>>>>> 4efe2834d (make ci happy) dependencies = [ "paste", ] diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 9ec556f272..59726105fb 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,6 +1,6 @@ +extern crate num_cpus; use crate::component::entry::TxEntry; use crate::try_or_return_with_snapshot; - use crate::{error::Reject, service::TxPoolService}; use ckb_script::{ChunkCommand, VerifyResult}; use ckb_stop_handler::CancellationToken; @@ -309,7 +309,7 @@ impl VerifyMgr { queue_rx: watch::Receiver, ) -> Self { let (notify_tx, notify_rx) = unbounded_channel::(); - let workers: Vec<_> = (0..4) + let workers: Vec<_> = (0..num_cpus::get()) .map({ let tasks = Arc::clone(&verify_queue); let command_rx = chunk_rx.clone(); From b4a25fa9338e199044047a1b33797f2edc054bdb Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 14 Jan 2024 11:53:53 +0800 Subject: [PATCH 021/294] fix graceful shutdown test --- chain/src/chain.rs | 1 - tx-pool/src/verify_mgr.rs | 4 +++- util/app-config/src/tests/graceful_shutdown.bats | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/chain/src/chain.rs b/chain/src/chain.rs index bdc47a2483..a42aa788b0 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -240,7 +240,6 @@ impl ChainService { let instant = Instant::now(); let _ = tx_control.suspend_chunk_process(); - eprintln!("suspend chunk process"); let _ = responder.send(self.process_block(block, verify)); let _ = tx_control.continue_chunk_process(); diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 59726105fb..bba228f18e 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -2,6 +2,7 @@ extern crate num_cpus; use crate::component::entry::TxEntry; use crate::try_or_return_with_snapshot; use crate::{error::Reject, service::TxPoolService}; +use ckb_logger::info; use ckb_script::{ChunkCommand, VerifyResult}; use ckb_stop_handler::CancellationToken; use ckb_types::packed::Byte32; @@ -348,10 +349,11 @@ impl VerifyMgr { loop { tokio::select! { _ = self.signal_exit.cancelled() => { + info!("TxPool chunk_command service received exit signal, exit now"); break; }, _ = self.command_rx.changed() => { - eprintln!("command: {:?}", self.command_rx.borrow()); + //eprintln!("command: {:?}", self.command_rx.borrow()); } res = self.worker_notify.recv() => { eprintln!("res: {:?}", res); diff --git a/util/app-config/src/tests/graceful_shutdown.bats b/util/app-config/src/tests/graceful_shutdown.bats index 17ac2661b3..7f9c0c895b 100644 --- a/util/app-config/src/tests/graceful_shutdown.bats +++ b/util/app-config/src/tests/graceful_shutdown.bats @@ -23,7 +23,7 @@ function ckb_graceful_shutdown { #@test assert_output --regexp "INFO ckb_bin::subcommand::run Trapped exit signal, exiting..." assert_output --regexp "INFO ckb_chain::chain ChainService received exit signal, exit now" assert_output --regexp "INFO ckb_sync::synchronizer BlockDownload received exit signal, exit now" - assert_output --regexp "INFO ckb_tx_pool::chunk_process TxPool chunk_command service received exit signal, exit now" + assert_output --regexp "INFO ckb_tx_pool::verify_mgr TxPool chunk_command service received exit signal, exit now" assert_output --regexp "INFO ckb_tx_pool::service TxPool is saving, please wait..." assert_output --regexp "INFO ckb_tx_pool::service TxPool reorg process service received exit signal, exit now" assert_output --regexp "INFO ckb_indexer::service Indexer received exit signal, exit now" From b5e89120d7a11118fc37766af418c0035bf94eef Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 15 Jan 2024 10:18:07 +0800 Subject: [PATCH 022/294] code refactor on run_vms_with_signal --- script/src/verify.rs | 166 +++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 78 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 188007ea31..8fe683cb8e 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1199,7 +1199,7 @@ impl, - context: &Arc>, + machines: Vec, + context: Arc>, signal: &mut tokio::sync::watch::Receiver, ) -> Result { if machines.is_empty() { @@ -1399,82 +1399,12 @@ async fn run_vms_with_signal( let pause = machines[0].pause(); let (finished_send, mut finished_recv) = mpsc::unbounded_channel::<(Result, u64)>(); - let (child_sender, mut child_recv) = watch::channel(ChunkCommand::Resume); - child_recv.mark_changed(); + let (child_sender, child_recv) = watch::channel(ChunkCommand::Resume); eprintln!("begin to run vms with signal: vms len {}", machines.len()); - let context_clone = Arc::clone(context); - let jh = tokio::spawn(async move { - let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); - loop { - eprintln!("begin to loop ........."); - select! { - _ = child_recv.changed() => { - let state = child_recv.borrow().to_owned(); - if state != ChunkCommand::Resume { - continue; - } - eprintln!("first resume here: {:?}", machines.len()); - if machines.is_empty() { - finished_send.send((Ok(exit_code), cycles)).unwrap(); - return; - } - - while let Some(mut machine) = machines.pop() { - eprintln!("first run vm now {}", machine.cycles()); - if let Some(callee_spawn_data) = &spawn_data { - update_caller_machine( - &mut machine.machine_mut().machine, - exit_code, - cycles, - callee_spawn_data, - ) - .unwrap(); - } - - let res = machine.run(); - eprintln!("first run vm now {} res: {:?}", machine.cycles(), res); - match res { - Ok(code) => { - exit_code = code; - cycles = machine.cycles(); - if let ResumableMachine::Spawn(_, data) = machine { - spawn_data = Some(data); - } else { - spawn_data = None; - } - eprintln!("finished run vm: {}", machines.len()); - if machines.is_empty() { - finished_send.send((Ok(exit_code), cycles)).unwrap(); - return; - } - } - Err(VMInternalError::Pause) => { - let mut new_suspended_machines: Vec<_> = { - let mut context = context_clone.lock().map_err(|e| { - ScriptError::Other(format!("Failed to acquire lock: {}", e)) - }).unwrap(); - context.suspended_machines.drain(..).collect() - }; - // The inner most machine lives at the top of the vector, - // reverse the list for natural order. - new_suspended_machines.reverse(); - machines.push(machine); - machines.append(&mut new_suspended_machines); - // wait for Resume command to begin next loop iteration - break; - } - _ => { - // other error happened here, for example CyclesExceeded, - // we need to return as verification failed - finished_send.send((res, machine.cycles())).unwrap(); - return; - } - }; - } - } - } - } - }); + let jh = + tokio::spawn( + async move { run_vms_child(machines, child_recv, finished_send, context).await }, + ); loop { tokio::select! { @@ -1508,6 +1438,86 @@ async fn run_vms_with_signal( } } +async fn run_vms_child( + mut machines: Vec, + mut child_recv: watch::Receiver, + finished_send: mpsc::UnboundedSender<(Result, u64)>, + context: Arc>, +) { + let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); + child_recv.mark_changed(); + loop { + eprintln!("begin to loop ........."); + select! { + _ = child_recv.changed() => { + let state = child_recv.borrow().to_owned(); + if state != ChunkCommand::Resume { + continue; + } + eprintln!("first resume here: {:?}", machines.len()); + if machines.is_empty() { + finished_send.send((Ok(exit_code), cycles)).unwrap(); + return; + } + + while let Some(mut machine) = machines.pop() { + eprintln!("first run vm now {}", machine.cycles()); + if let Some(callee_spawn_data) = &spawn_data { + update_caller_machine( + &mut machine.machine_mut().machine, + exit_code, + cycles, + callee_spawn_data, + ) + .unwrap(); + } + + let res = machine.run(); + eprintln!("first run vm now {} res: {:?}", machine.cycles(), res); + match res { + Ok(code) => { + exit_code = code; + cycles = machine.cycles(); + if let ResumableMachine::Spawn(_, data) = machine { + spawn_data = Some(data); + } else { + spawn_data = None; + } + eprintln!("finished run vm: {}", machines.len()); + if machines.is_empty() { + finished_send.send((Ok(exit_code), cycles)).unwrap(); + return; + } + } + Err(VMInternalError::Pause) => { + let mut new_suspended_machines: Vec<_> = { + let mut context = context.lock().map_err(|e| { + ScriptError::Other(format!("Failed to acquire lock: {}", e)) + }).unwrap(); + context.suspended_machines.drain(..).collect() + }; + // The inner most machine lives at the top of the vector, + // reverse the list for natural order. + new_suspended_machines.reverse(); + machines.push(machine); + machines.append(&mut new_suspended_machines); + // wait for Resume command to begin next loop iteration + eprintln!("suspend here: {:?}", machines.len()); + break; + } + _ => { + // other error happened here, for example CyclesExceeded, + // we need to return as verification failed + finished_send.send((res, machine.cycles())).unwrap(); + return; + } + }; + } + } + } + } +} + fn wrapping_cycles_add( lhs: Cycle, rhs: Cycle, From 80d8208cc53af985c446a020e869f8cbb4aab06f Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 15 Jan 2024 10:47:28 +0800 Subject: [PATCH 023/294] fix cycle exceed error --- script/src/verify.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 8fe683cb8e..24b5832bf7 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1385,7 +1385,7 @@ fn run_vms( // Run a series of VMs that are just freshly resumed async fn run_vms_with_signal( script_group: &ScriptGroup, - _max_cycles: Cycle, + max_cycles: Cycle, machines: Vec, context: Arc>, signal: &mut tokio::sync::watch::Receiver, @@ -1428,7 +1428,11 @@ async fn run_vms_with_signal( exit_code ))}, (Err(err), _) => { - return Err(ScriptError::VMInternalError(err)); + let map_vm_internal_error = |error: VMInternalError| match error { + VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), + _ => ScriptError::VMInternalError(error), + }; + return Err(map_vm_internal_error(err)); } } From 3becd0fa57d6a9ae02f0489eaaceaa22ed238a5e Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 15 Jan 2024 18:15:11 +0800 Subject: [PATCH 024/294] add comments and cleanup for suspend --- script/src/verify.rs | 19 ++--- tx-pool/src/process.rs | 143 ++++---------------------------------- tx-pool/src/verify_mgr.rs | 1 + 3 files changed, 25 insertions(+), 138 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 24b5832bf7..bd4816d6cd 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -700,7 +700,7 @@ impl = self.groups().collect(); - for (idx, (_hash, group)) in groups.iter().enumerate() { + for (_idx, (_hash, group)) in groups.iter().enumerate() { // vm should early return invalid cycles let remain_cycles = limit_cycles.checked_sub(cycles).ok_or_else(|| { ScriptError::Other(format!("expect invalid cycles {limit_cycles} {cycles}")) @@ -714,10 +714,12 @@ impl { cycles = wrapping_cycles_add(cycles, used_cycles, group)?; } - Ok(ChunkState::Suspended(vms, context)) => { - let current = idx; - let state = TransactionState::new(vms, context, current, cycles, remain_cycles); - return Ok(VerifyResult::Suspended(state)); + Ok(ChunkState::Suspended(_vms, _context)) => { + // FIXME: we need to cleanup this later, state will not contain snapshot + //let current = idx; + //let state = TransactionState::new(vms, context, current, cycles, remain_cycles); + //return Ok(VerifyResult::Suspended(state)); + panic!("unexpect suspend in resumable_verify_with_signal"); } Err(e) => { #[cfg(feature = "logging")] @@ -1051,7 +1053,8 @@ impl Ok(ChunkState::Completed(cycles)), - Err(ScriptError::ExceededMaximumCycles(_)) => Ok(ChunkState::suspended_type_id()), + // FIXME(yukang) we need to clean this up later + // Err(ScriptError::ExceededMaximumCycles(_)) => Ok(ChunkState::suspended_type_id()), Err(e) => Err(e), } } else { @@ -1429,8 +1432,8 @@ async fn run_vms_with_signal( ))}, (Err(err), _) => { let map_vm_internal_error = |error: VMInternalError| match error { - VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), - _ => ScriptError::VMInternalError(error), + VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), + _ => ScriptError::VMInternalError(error), }; return Err(map_vm_internal_error(err)); } diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index fc15cdedbf..8c3c045bf4 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -302,7 +302,7 @@ impl TxPoolService { return Err(Reject::Duplicated(tx.hash())); } - if let Some((ret, snapshot)) = self.verify_or_add_to_queue(tx.clone(), remote).await { + if let Some((ret, snapshot)) = self._resumeble_process_tx(tx.clone(), remote).await { eprintln!("resumeble_process_tx: ret = {:?}", ret); match ret { Ok(processed) => { @@ -529,10 +529,9 @@ impl TxPoolService { tx.hash(), ); self.remove_orphan_tx(&orphan.tx.proposal_short_id()).await; - self.verify_queue - .write() + self.enqueue_suspended_tx(orphan.tx, Some((orphan.cycle, orphan.peer))) .await - .add_tx(orphan.tx, Some((orphan.cycle, orphan.peer))); + .expect("enqueue suspended tx"); } else if let Some((ret, snapshot)) = self ._process_tx(orphan.tx.clone(), Some(orphan.cycle)) .await @@ -628,7 +627,7 @@ impl TxPoolService { self.network.ban_peer(peer, DEFAULT_BAN_TIME, reason); } - async fn verify_or_add_to_queue( + async fn _resumeble_process_tx( &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, @@ -681,6 +680,8 @@ impl TxPoolService { tx_env, ); + // FIXME(yukang): here we're still using old `resumable_verify` interface + // ideally we should unify the interface with `resumable_verify_with_signal` let (ret, fee) = verifier .resumable_verify(self.tx_pool_config.max_tx_verify_cycles) .map_err(Reject::Verification)?; @@ -717,130 +718,12 @@ impl TxPoolService { let entry = try_or_return_with_snapshot!(ret, snapshot); match entry { - cached @ CacheEntry::Suspended(_) => { - let ret = self - .enqueue_suspended_tx(rtx.transaction.clone(), cached, remote) - .await; - try_or_return_with_snapshot!(ret, snapshot); - eprintln!("added to queue here: {:?}", tx.proposal_short_id()); - return Some((Ok(ProcessResult::Suspended), snapshot)); - } - CacheEntry::Completed(completed) => completed, - } - }; - - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - - let (ret, submit_snapshot) = self.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, submit_snapshot); - - self.notify_block_assembler(status).await; - if cached.is_none() { - // update cache - let txs_verify_cache = Arc::clone(&self.txs_verify_cache); - tokio::spawn(async move { - let mut guard = txs_verify_cache.write().await; - guard.put(tx_hash, CacheEntry::Completed(completed)); - }); - } - - Some((Ok(ProcessResult::Completed(completed)), submit_snapshot)) - } - - async fn _resumeble_process_tx( - &self, - tx: TransactionView, - remote: Option<(Cycle, PeerIndex)>, - ) -> Option<(Result, Arc)> { - let limit_cycles = self.tx_pool_config.max_tx_verify_cycles; - let tx_hash = tx.hash(); - - let (ret, snapshot) = self.pre_check(&tx).await; - let (tip_hash, rtx, status, fee, tx_size) = try_or_return_with_snapshot!(ret, snapshot); - - if self.is_in_delay_window(&snapshot) { - let mut delay = self.delay.write().await; - if delay.len() < DELAY_LIMIT { - delay.insert(tx.proposal_short_id(), tx); - } - return None; - } - - let cached = self.fetch_tx_verify_cache(&tx_hash).await; - let tip_header = snapshot.tip_header(); - let tx_env = Arc::new(status.with_env(tip_header)); - - let data_loader = snapshot.as_data_loader(); - - let completed = if let Some(ref entry) = cached { - match entry { - CacheEntry::Completed(completed) => { - let ret = TimeRelativeTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&self.consensus), - data_loader, - tx_env, - ) - .verify() - .map_err(Reject::Verification); - try_or_return_with_snapshot!(ret, snapshot); - *completed - } CacheEntry::Suspended(_) => { - return Some((Ok(ProcessResult::Suspended), snapshot)); - } - } - } else { - let is_chunk_full = self.is_chunk_full().await; - - let ret = block_in_place(|| { - let verifier = ContextualTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&self.consensus), - data_loader, - tx_env, - ); - - let (ret, fee) = verifier - .resumable_verify(limit_cycles) - .map_err(Reject::Verification)?; - - match ret { - ScriptVerifyResult::Completed(cycles) => { - if let Err(e) = DaoScriptSizeVerifier::new( - Arc::clone(&rtx), - Arc::clone(&self.consensus), - snapshot.as_data_loader(), - ) - .verify() - { - return Err(Reject::Verification(e)); - } - if let Some((declared, _)) = remote { - if declared != cycles { - return Err(Reject::DeclaredWrongCycles(declared, cycles)); - } - } - Ok(CacheEntry::completed(cycles, fee)) - } - ScriptVerifyResult::Suspended(state) => { - if is_chunk_full { - Err(Reject::Full("chunk".to_owned())) - } else { - let snap = Arc::new(state.try_into().map_err(Reject::Verification)?); - Ok(CacheEntry::suspended(snap, fee)) - } - } - } - }); - - let entry = try_or_return_with_snapshot!(ret, snapshot); - match entry { - cached @ CacheEntry::Suspended(_) => { let ret = self - .enqueue_suspended_tx(rtx.transaction.clone(), cached, remote) + .enqueue_suspended_tx(rtx.transaction.clone(), remote) .await; try_or_return_with_snapshot!(ret, snapshot); + eprintln!("added to queue here: {:?}", tx.proposal_short_id()); return Some((Ok(ProcessResult::Suspended), snapshot)); } CacheEntry::Completed(completed) => completed, @@ -872,16 +755,16 @@ impl TxPoolService { pub(crate) async fn enqueue_suspended_tx( &self, tx: TransactionView, - cached: CacheEntry, remote: Option<(Cycle, PeerIndex)>, ) -> Result<(), Reject> { let tx_hash = tx.hash(); let mut chunk = self.verify_queue.write().await; - if chunk.add_tx(tx, remote) { - let mut guard = self.txs_verify_cache.write().await; - guard.put(tx_hash, cached); + if !chunk.add_tx(tx, remote) { + return Err(Reject::Full(format!( + "chunk is full, tx_hash: {:#x}", + tx_hash + ))); } - Ok(()) } diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index bba228f18e..4a081a9166 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -282,6 +282,7 @@ impl Worker { match res { VerifyResult::Completed(cycles) => Ok(State::Completed(cycles)), VerifyResult::Suspended(_) => { + // `resumable_verify_with_signal` should not return `Suspended` panic!("not expected"); } } From 676ab743eeda08b483e915e9b726a4eac4053c24 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 15 Jan 2024 21:53:51 +0800 Subject: [PATCH 025/294] prepare to remove CacheEntry::Suspended --- tx-pool/src/process.rs | 44 +++++++------------ tx-pool/src/util.rs | 13 +----- .../src/contextual_block_verifier.rs | 22 ++-------- verification/src/transaction_verifier.rs | 1 + 4 files changed, 22 insertions(+), 58 deletions(-) diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 8c3c045bf4..8b6c069e67 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -666,12 +666,19 @@ impl TxPoolService { *completed } CacheEntry::Suspended(_) => { - return Some((Ok(ProcessResult::Suspended), snapshot)); + panic!("Unexpected suspended entry in cache"); } } + } else if remote.is_some() { + // for remote transaction with large decleard cycles, we enqueue it to verify queue + let ret = self + .enqueue_suspended_tx(rtx.transaction.clone(), remote) + .await; + try_or_return_with_snapshot!(ret, snapshot); + eprintln!("added to queue here: {:?}", tx.proposal_short_id()); + return Some((Ok(ProcessResult::Suspended), snapshot)); } else { - let is_chunk_full = self.is_chunk_full().await; - + // for local transaction, we verify it directly with a max cycles limit let ret = block_in_place(|| { let verifier = ContextualTransactionVerifier::new( Arc::clone(&rtx), @@ -680,10 +687,11 @@ impl TxPoolService { tx_env, ); + let cycle_limit = snapshot.cloned_consensus().max_block_cycles(); // FIXME(yukang): here we're still using old `resumable_verify` interface // ideally we should unify the interface with `resumable_verify_with_signal` let (ret, fee) = verifier - .resumable_verify(self.tx_pool_config.max_tx_verify_cycles) + .resumable_verify(cycle_limit) .map_err(Reject::Verification)?; eprintln!("first verify: {:?}", ret); @@ -698,46 +706,28 @@ impl TxPoolService { { return Err(Reject::Verification(e)); } - if let Some((declared, _)) = remote { - if declared != cycles { - return Err(Reject::DeclaredWrongCycles(declared, cycles)); - } - } Ok(CacheEntry::completed(cycles, fee)) } - ScriptVerifyResult::Suspended(state) => { - if is_chunk_full { - Err(Reject::Full("chunk is full".to_owned())) - } else { - let snap = Arc::new(state.try_into().map_err(Reject::Verification)?); - Ok(CacheEntry::suspended(snap, fee)) - } + ScriptVerifyResult::Suspended(_state) => { + panic!("unexpect suspend"); } } }); let entry = try_or_return_with_snapshot!(ret, snapshot); match entry { - CacheEntry::Suspended(_) => { - let ret = self - .enqueue_suspended_tx(rtx.transaction.clone(), remote) - .await; - try_or_return_with_snapshot!(ret, snapshot); - eprintln!("added to queue here: {:?}", tx.proposal_short_id()); - return Some((Ok(ProcessResult::Suspended), snapshot)); - } CacheEntry::Completed(completed) => completed, + _ => panic!("unexpect suspend"), } }; let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, submit_snapshot) = self.submit_entry(tip_hash, entry, status).await; try_or_return_with_snapshot!(ret, submit_snapshot); self.notify_block_assembler(status).await; if cached.is_none() { - // update cache + // update cache in background let txs_verify_cache = Arc::clone(&self.txs_verify_cache); tokio::spawn(async move { let mut guard = txs_verify_cache.write().await; @@ -748,7 +738,7 @@ impl TxPoolService { Some((Ok(ProcessResult::Completed(completed)), submit_snapshot)) } - pub(crate) async fn is_chunk_full(&self) -> bool { + pub(crate) async fn _is_chunk_full(&self) -> bool { self.verify_queue.read().await.is_full() } diff --git a/tx-pool/src/util.rs b/tx-pool/src/util.rs index c94eab5b25..1c02df3d67 100644 --- a/tx-pool/src/util.rs +++ b/tx-pool/src/util.rs @@ -101,18 +101,7 @@ pub(crate) fn verify_rtx( .map_err(Reject::Verification) } CacheEntry::Suspended(suspended) => { - ContextualTransactionVerifier::new(Arc::clone(&rtx), consensus, data_loader, tx_env) - .complete(max_tx_verify_cycles, false, &suspended.snap) - .and_then(|result| { - DaoScriptSizeVerifier::new( - rtx, - snapshot.cloned_consensus(), - snapshot.as_data_loader(), - ) - .verify()?; - Ok(result) - }) - .map_err(Reject::Verification) + panic!("Unexpected suspended entry in verify_rtx: {:?}", suspended); } } } else { diff --git a/verification/contextual/src/contextual_block_verifier.rs b/verification/contextual/src/contextual_block_verifier.rs index 18e848e69e..ae040e2e59 100644 --- a/verification/contextual/src/contextual_block_verifier.rs +++ b/verification/contextual/src/contextual_block_verifier.rs @@ -429,25 +429,9 @@ impl<'a, 'b, CS: ChainStore + VersionbitsIndexer + 'static> BlockTxsVerifier<'a, .into() }) .map(|_| (tx_hash, *completed)), - CacheEntry::Suspended(suspended) => ContextualTransactionVerifier::new( - Arc::clone(tx), - Arc::clone(&self.context.consensus), - self.context.store.as_data_loader(), - Arc::clone(&tx_env), - ) - .complete( - self.context.consensus.max_block_cycles(), - skip_script_verify, - &suspended.snap, - ) - .map_err(|error| { - BlockTransactionsError { - index: index as u32, - error, - } - .into() - }) - .map(|completed| (tx_hash, completed)), + CacheEntry::Suspended(_suspended) => { + panic!("unexpected Suspended in verify"); + }, } } else { ContextualTransactionVerifier::new( diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 4305729c7e..7bd24db090 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -166,6 +166,7 @@ where self.time_relative.verify()?; self.capacity.verify()?; let fee = self.fee_calculator.transaction_fee()?; + //let (_command_tx, mut command_rx) = tokio::sync::watch::channel(ChunkCommand::Resume); let ret = self.script.resumable_verify(limit_cycles)?; Ok((ret, fee)) } From 85dcd33a39851a11b172b5ce1f816ed5108b6122 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 15 Jan 2024 23:57:09 +0800 Subject: [PATCH 026/294] remove EntryCache suspend status --- tx-pool/src/process.rs | 41 ++++++---------- tx-pool/src/util.rs | 17 ++----- tx-pool/src/verify_mgr.rs | 49 ++++++++----------- .../src/contextual_block_verifier.rs | 13 ++--- verification/src/cache.rs | 20 +------- 5 files changed, 45 insertions(+), 95 deletions(-) diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 8b6c069e67..74ba9d456c 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -651,24 +651,17 @@ impl TxPoolService { let data_loader = snapshot.as_data_loader(); - let completed = if let Some(ref entry) = cached { - match entry { - CacheEntry::Completed(completed) => { - let ret = TimeRelativeTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&self.consensus), - data_loader, - tx_env, - ) - .verify() - .map_err(Reject::Verification); - try_or_return_with_snapshot!(ret, snapshot); - *completed - } - CacheEntry::Suspended(_) => { - panic!("Unexpected suspended entry in cache"); - } - } + let completed = if let Some(ref completed) = cached { + let ret = TimeRelativeTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&self.consensus), + data_loader, + tx_env, + ) + .verify() + .map_err(Reject::Verification); + try_or_return_with_snapshot!(ret, snapshot); + *completed } else if remote.is_some() { // for remote transaction with large decleard cycles, we enqueue it to verify queue let ret = self @@ -706,7 +699,7 @@ impl TxPoolService { { return Err(Reject::Verification(e)); } - Ok(CacheEntry::completed(cycles, fee)) + Ok(Completed { cycles, fee }) } ScriptVerifyResult::Suspended(_state) => { panic!("unexpect suspend"); @@ -714,11 +707,7 @@ impl TxPoolService { } }); - let entry = try_or_return_with_snapshot!(ret, snapshot); - match entry { - CacheEntry::Completed(completed) => completed, - _ => panic!("unexpect suspend"), - } + try_or_return_with_snapshot!(ret, snapshot) }; let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); @@ -731,7 +720,7 @@ impl TxPoolService { let txs_verify_cache = Arc::clone(&self.txs_verify_cache); tokio::spawn(async move { let mut guard = txs_verify_cache.write().await; - guard.put(tx_hash, CacheEntry::Completed(completed)); + guard.put(tx_hash, completed); }); } @@ -813,7 +802,7 @@ impl TxPoolService { let txs_verify_cache = Arc::clone(&self.txs_verify_cache); tokio::spawn(async move { let mut guard = txs_verify_cache.write().await; - guard.put(tx_hash, CacheEntry::Completed(verified)); + guard.put(tx_hash, verified); }); } diff --git a/tx-pool/src/util.rs b/tx-pool/src/util.rs index 1c02df3d67..937af7d841 100644 --- a/tx-pool/src/util.rs +++ b/tx-pool/src/util.rs @@ -92,18 +92,11 @@ pub(crate) fn verify_rtx( let consensus = snapshot.cloned_consensus(); let data_loader = snapshot.as_data_loader(); - if let Some(ref cached) = cache_entry { - match cached { - CacheEntry::Completed(completed) => { - TimeRelativeTransactionVerifier::new(rtx, consensus, data_loader, tx_env) - .verify() - .map(|_| *completed) - .map_err(Reject::Verification) - } - CacheEntry::Suspended(suspended) => { - panic!("Unexpected suspended entry in verify_rtx: {:?}", suspended); - } - } + if let Some(ref completed) = cache_entry { + TimeRelativeTransactionVerifier::new(rtx, consensus, data_loader, tx_env) + .verify() + .map(|_| *completed) + .map_err(Reject::Verification) } else { block_in_place(|| { ContextualTransactionVerifier::new(Arc::clone(&rtx), consensus, data_loader, tx_env) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 4a081a9166..80ea5387d7 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -154,34 +154,25 @@ impl Worker { let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); eprintln!("run_verify_tx cached: {:?}", cached); - if let Some(ref cached) = cached { - match cached { - CacheEntry::Completed(completed) => { - let ret = TimeRelativeTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - snapshot.as_data_loader(), - Arc::clone(&tx_env), - ) - .verify() - .map(|_| *completed) - .map_err(Reject::Verification); - let completed = try_or_return_with_snapshot!(ret, snapshot); - - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, submit_snapshot) = - self.service.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, submit_snapshot); - self.service - .after_process(tx, remote, &submit_snapshot, &Ok(completed)) - .await; - return Some((Ok(false), submit_snapshot)); - } - CacheEntry::Suspended(_suspended) => { - eprintln!("not expected suspended: {:?}", cached); - //panic!("not expected"); - } - } + if let Some(ref completed) = cached { + let ret = TimeRelativeTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&consensus), + snapshot.as_data_loader(), + Arc::clone(&tx_env), + ) + .verify() + .map(|_| *completed) + .map_err(Reject::Verification); + let completed = try_or_return_with_snapshot!(ret, snapshot); + + let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); + let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; + try_or_return_with_snapshot!(ret, submit_snapshot); + self.service + .after_process(tx, remote, &submit_snapshot, &Ok(completed)) + .await; + return Some((Ok(false), submit_snapshot)); } let cloned_snapshot = Arc::clone(&snapshot); @@ -257,7 +248,7 @@ impl Worker { update_cache( Arc::clone(&self.service.txs_verify_cache), tx_hash, - CacheEntry::Completed(completed), + completed, ) .await; diff --git a/verification/contextual/src/contextual_block_verifier.rs b/verification/contextual/src/contextual_block_verifier.rs index ae040e2e59..132b4e3ca0 100644 --- a/verification/contextual/src/contextual_block_verifier.rs +++ b/verification/contextual/src/contextual_block_verifier.rs @@ -379,7 +379,7 @@ impl<'a, 'b, CS: ChainStore + VersionbitsIndexer + 'static> BlockTxsVerifier<'a, self.handle.spawn(async move { let mut guard = txs_verify_cache.write().await; for (k, v) in ret { - guard.put(k, CacheEntry::Completed(v)); + guard.put(k, v); } }); } @@ -412,9 +412,8 @@ impl<'a, 'b, CS: ChainStore + VersionbitsIndexer + 'static> BlockTxsVerifier<'a, .map(|(index, tx)| { let tx_hash = tx.transaction.hash(); - if let Some(cache_entry) = fetched_cache.get(&tx_hash) { - match cache_entry { - CacheEntry::Completed(completed) => TimeRelativeTransactionVerifier::new( + if let Some(completed) = fetched_cache.get(&tx_hash) { + TimeRelativeTransactionVerifier::new( Arc::clone(tx), Arc::clone(&self.context.consensus), self.context.store.as_data_loader(), @@ -428,11 +427,7 @@ impl<'a, 'b, CS: ChainStore + VersionbitsIndexer + 'static> BlockTxsVerifier<'a, } .into() }) - .map(|_| (tx_hash, *completed)), - CacheEntry::Suspended(_suspended) => { - panic!("unexpected Suspended in verify"); - }, - } + .map(|_| (tx_hash, *completed)) } else { ContextualTransactionVerifier::new( Arc::clone(tx), diff --git a/verification/src/cache.rs b/verification/src/cache.rs index b7ceab66ab..1b01d2d1d4 100644 --- a/verification/src/cache.rs +++ b/verification/src/cache.rs @@ -17,14 +17,8 @@ pub fn init_cache() -> TxVerificationCache { lru::LruCache::new(CACHE_SIZE) } -#[derive(Clone, Debug)] /// TX verification lru entry -pub enum CacheEntry { - /// Completed - Completed(Completed), - /// Suspended - Suspended(Suspended), -} +pub type CacheEntry = Completed; /// Suspended state #[derive(Clone, Debug)] @@ -43,15 +37,3 @@ pub struct Completed { /// Cached tx fee pub fee: Capacity, } - -impl CacheEntry { - /// Constructs a completed CacheEntry - pub fn completed(cycles: Cycle, fee: Capacity) -> Self { - CacheEntry::Completed(Completed { cycles, fee }) - } - - /// Constructs a Suspended CacheEntry - pub fn suspended(snap: Arc, fee: Capacity) -> Self { - CacheEntry::Suspended(Suspended { snap, fee }) - } -} From f77319f50332f464065670a384edb02b7c34b96e Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 16 Jan 2024 01:22:09 +0800 Subject: [PATCH 027/294] add verify_until_completed replace resumable_verify --- script/Cargo.toml | 2 +- script/src/verify.rs | 22 ++++++------------ tx-pool/src/process.rs | 24 ++++++++------------ tx-pool/src/verify_mgr.rs | 29 ++++++------------------ verification/Cargo.toml | 2 +- verification/src/transaction_verifier.rs | 17 ++++++++++---- 6 files changed, 39 insertions(+), 57 deletions(-) diff --git a/script/Cargo.toml b/script/Cargo.toml index fdeb61fb66..b537c5b993 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -28,7 +28,7 @@ ckb-logger = { path = "../util/logger", version = "= 0.114.0-pre", optional = tr serde = { version = "1.0", features = ["derive"] } ckb-error = { path = "../error", version = "= 0.114.0-pre" } ckb-chain-spec = { path = "../spec", version = "= 0.114.0-pre" } -tokio = { version = "1.35.0", features = ["full"] } +tokio = { version = "1.35.0", features = ["sync"] } [dev-dependencies] proptest = "1.0" diff --git a/script/src/verify.rs b/script/src/verify.rs index bd4816d6cd..7a4b4f6bae 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -42,7 +42,10 @@ use std::sync::RwLock; use std::sync::{Arc, Mutex}; use tokio::{ select, - sync::{mpsc, watch}, + sync::{ + mpsc, + watch::{self, Receiver}, + }, }; #[cfg(test)] @@ -716,9 +719,6 @@ impl { // FIXME: we need to cleanup this later, state will not contain snapshot - //let current = idx; - //let state = TransactionState::new(vms, context, current, cycles, remain_cycles); - //return Ok(VerifyResult::Suspended(state)); panic!("unexpect suspend in resumable_verify_with_signal"); } Err(e) => { @@ -1053,8 +1053,6 @@ impl Ok(ChunkState::Completed(cycles)), - // FIXME(yukang) we need to clean this up later - // Err(ScriptError::ExceededMaximumCycles(_)) => Ok(ChunkState::suspended_type_id()), Err(e) => Err(e), } } else { @@ -1375,7 +1373,6 @@ fn run_vms( } if exit_code == 0 { - eprintln!("run_vms finished: {} cycles: {}", exit_code, cycles); Ok(ChunkState::Completed(cycles)) } else { Err(ScriptError::validation_failure( @@ -1391,7 +1388,7 @@ async fn run_vms_with_signal( max_cycles: Cycle, machines: Vec, context: Arc>, - signal: &mut tokio::sync::watch::Receiver, + signal: &mut Receiver, ) -> Result { if machines.is_empty() { return Err(ScriptError::Other( @@ -1419,9 +1416,9 @@ async fn run_vms_with_signal( child_sender.send(ChunkCommand::Resume).unwrap(); } } - res = finished_recv.recv() => { + Some(res) = finished_recv.recv() => { let _ = jh.await; - match res.unwrap() { + match res { (Ok(0), cycles) => { return Ok(ChunkState::Completed(cycles)); } @@ -1454,21 +1451,18 @@ async fn run_vms_child( let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); child_recv.mark_changed(); loop { - eprintln!("begin to loop ........."); select! { _ = child_recv.changed() => { let state = child_recv.borrow().to_owned(); if state != ChunkCommand::Resume { continue; } - eprintln!("first resume here: {:?}", machines.len()); if machines.is_empty() { finished_send.send((Ok(exit_code), cycles)).unwrap(); return; } while let Some(mut machine) = machines.pop() { - eprintln!("first run vm now {}", machine.cycles()); if let Some(callee_spawn_data) = &spawn_data { update_caller_machine( &mut machine.machine_mut().machine, @@ -1480,7 +1474,6 @@ async fn run_vms_child( } let res = machine.run(); - eprintln!("first run vm now {} res: {:?}", machine.cycles(), res); match res { Ok(code) => { exit_code = code; @@ -1490,7 +1483,6 @@ async fn run_vms_child( } else { spawn_data = None; } - eprintln!("finished run vm: {}", machines.len()); if machines.is_empty() { finished_send.send((Ok(exit_code), cycles)).unwrap(); return; diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 74ba9d456c..ab08695a42 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -34,7 +34,6 @@ use std::collections::HashSet; use std::collections::{HashMap, VecDeque}; use std::sync::Arc; use std::time::Duration; -use tokio::task::block_in_place; const DELAY_LIMIT: usize = 1_500 * 21; // 1_500 per block, 21 blocks @@ -672,7 +671,7 @@ impl TxPoolService { return Some((Ok(ProcessResult::Suspended), snapshot)); } else { // for local transaction, we verify it directly with a max cycles limit - let ret = block_in_place(|| { + let ret = { let verifier = ContextualTransactionVerifier::new( Arc::clone(&rtx), Arc::clone(&self.consensus), @@ -681,31 +680,28 @@ impl TxPoolService { ); let cycle_limit = snapshot.cloned_consensus().max_block_cycles(); - // FIXME(yukang): here we're still using old `resumable_verify` interface - // ideally we should unify the interface with `resumable_verify_with_signal` - let (ret, fee) = verifier - .resumable_verify(cycle_limit) - .map_err(Reject::Verification)?; - - eprintln!("first verify: {:?}", ret); + let ret = verifier + .verify_until_completed(cycle_limit) + .await + .map_err(Reject::Verification); + let (ret, fee) = try_or_return_with_snapshot!(ret, snapshot); match ret { ScriptVerifyResult::Completed(cycles) => { - if let Err(e) = DaoScriptSizeVerifier::new( + let res = DaoScriptSizeVerifier::new( Arc::clone(&rtx), Arc::clone(&self.consensus), snapshot.as_data_loader(), ) .verify() - { - return Err(Reject::Verification(e)); - } + .map_err(Reject::Verification); + try_or_return_with_snapshot!(res, snapshot); Ok(Completed { cycles, fee }) } ScriptVerifyResult::Suspended(_state) => { panic!("unexpect suspend"); } } - }); + }; try_or_return_with_snapshot!(ret, snapshot) }; diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 80ea5387d7..72b32aefed 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -37,13 +37,6 @@ pub enum VerifyNotify { }, } -#[derive(Clone, Debug)] -enum State { - //Stopped, - //Suspended(Arc), - Completed(Cycle), -} - struct Worker { tasks: Arc>, command_rx: watch::Receiver, @@ -85,10 +78,9 @@ impl Worker { } } - /// start handle tasks pub fn start(mut self) -> JoinHandle<()> { tokio::spawn(async move { - let mut interval = tokio::time::interval(Duration::from_millis(100)); + let mut interval = tokio::time::interval(Duration::from_millis(500)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { tokio::select! { @@ -216,9 +208,10 @@ impl Worker { let state = try_or_return_with_snapshot!(ret, snapshot); let completed: Completed = match state { - // verify failed - // State::Stopped => return Some((Ok(true), snapshot)), - State::Completed(cycles) => Completed { cycles, fee }, + VerifyResult::Completed(cycles) => Completed { cycles, fee }, + _ => { + panic!("not expected"); + } }; eprintln!("completed: {:?}", completed); eprintln!("remote: {:?}", remote); @@ -234,13 +227,11 @@ impl Worker { } } // verify passed - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; try_or_return_with_snapshot!(ret, snapshot); self.service.notify_block_assembler(status).await; - self.service .after_process(tx, remote, &submit_snapshot, &Ok(completed)) .await; @@ -264,19 +255,13 @@ impl Worker { max_cycles: Cycle, consensus: Arc, tx_env: Arc, - ) -> Result { + ) -> Result { let script_verifier = ScriptVerifier::new(rtx, data_loader, consensus, tx_env); let res = script_verifier .resumable_verify_with_signal(max_cycles, &mut self.command_rx) .await .map_err(Reject::Verification)?; - match res { - VerifyResult::Completed(cycles) => Ok(State::Completed(cycles)), - VerifyResult::Suspended(_) => { - // `resumable_verify_with_signal` should not return `Suspended` - panic!("not expected"); - } - } + Ok(res) } } diff --git a/verification/Cargo.toml b/verification/Cargo.toml index f08eb31c81..fc5b79f7be 100644 --- a/verification/Cargo.toml +++ b/verification/Cargo.toml @@ -21,7 +21,7 @@ ckb-dao-utils = { path = "../util/dao/utils", version = "= 0.114.0-pre" } ckb-error = { path = "../error", version = "= 0.114.0-pre" } derive_more = { version = "0.99.0", default-features=false, features = ["display"] } ckb-verification-traits = { path = "./traits", version = "= 0.114.0-pre" } -tokio = { version = "1", features = ["sync", "process"] } +tokio = { version = "1", features = ["sync"] } [dev-dependencies] ckb-test-chain-utils = { path = "../util/test-chain-utils", version = "= 0.114.0-pre" } diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 7bd24db090..424485a474 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -160,14 +160,23 @@ where } } - /// Perform resumable context-dependent verification, return a `Result` to `CacheEntry` - pub fn resumable_verify(&self, limit_cycles: Cycle) -> Result<(VerifyResult, Capacity), Error> { + /// Perform context-dependent verification, return a `Result` to `CacheEntry` + pub async fn verify_until_completed( + &self, + limit_cycles: Cycle, + ) -> Result<(VerifyResult, Capacity), Error> { self.compatible.verify()?; self.time_relative.verify()?; self.capacity.verify()?; let fee = self.fee_calculator.transaction_fee()?; - //let (_command_tx, mut command_rx) = tokio::sync::watch::channel(ChunkCommand::Resume); - let ret = self.script.resumable_verify(limit_cycles)?; + // here we use a dummy command_rx, which will never receive a Suspend command + // to make sure the verification will not be interrupted and ExceededMaximumCycles will be treated as an error + let (_command_tx, mut command_rx) = tokio::sync::watch::channel(ChunkCommand::Resume); + let ret = self + .script + .resumable_verify_with_signal(limit_cycles, &mut command_rx) + .await?; + eprintln!("resumable_verify_with_signal ret: {:?}", ret); Ok((ret, fee)) } From e09ef844b586092b7a7f83fcf237fc2f5348428c Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 16 Jan 2024 10:39:58 +0800 Subject: [PATCH 028/294] cleanup verify_mgr --- tx-pool/src/verify_mgr.rs | 117 +++++++++++--------------------------- 1 file changed, 32 insertions(+), 85 deletions(-) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 72b32aefed..b9ef1bcae3 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,47 +1,29 @@ extern crate num_cpus; use crate::component::entry::TxEntry; use crate::try_or_return_with_snapshot; +use crate::verify_queue::{Entry, VerifyQueue}; use crate::{error::Reject, service::TxPoolService}; +use ckb_chain_spec::consensus::Consensus; use ckb_logger::info; use ckb_script::{ChunkCommand, VerifyResult}; -use ckb_stop_handler::CancellationToken; -use ckb_types::packed::Byte32; -use ckb_verification::cache::TxVerificationCache; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; -use tokio::sync::{watch, RwLock}; -use tokio::task::JoinHandle; - -use crate::verify_queue::{Entry, VerifyQueue}; - -use ckb_chain_spec::consensus::Consensus; use ckb_snapshot::Snapshot; +use ckb_stop_handler::CancellationToken; use ckb_store::data_loader_wrapper::AsDataLoader; use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; use ckb_types::core::{cell::ResolvedTransaction, Cycle}; - use ckb_verification::{ - cache::{CacheEntry, Completed}, - ContextualWithoutScriptTransactionVerifier, DaoScriptSizeVerifier, ScriptVerifier, - TimeRelativeTransactionVerifier, TxVerifyEnv, + cache::Completed, ContextualWithoutScriptTransactionVerifier, DaoScriptSizeVerifier, + ScriptVerifier, TimeRelativeTransactionVerifier, TxVerifyEnv, }; - -type Stop = bool; - -#[derive(Clone, Debug)] -pub enum VerifyNotify { - Done { - short_id: String, - result: (Result, Arc), - }, -} +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::{watch, RwLock}; +use tokio::task::JoinHandle; struct Worker { tasks: Arc>, command_rx: watch::Receiver, queue_rx: watch::Receiver, - outbox: UnboundedSender, service: TxPoolService, exit_signal: CancellationToken, } @@ -53,7 +35,6 @@ impl Clone for Worker { command_rx: self.command_rx.clone(), queue_rx: self.queue_rx.clone(), exit_signal: self.exit_signal.clone(), - outbox: self.outbox.clone(), service: self.service.clone(), } } @@ -65,7 +46,6 @@ impl Worker { tasks: Arc>, command_rx: watch::Receiver, queue_rx: watch::Receiver, - outbox: UnboundedSender, exit_signal: CancellationToken, ) -> Self { Worker { @@ -73,7 +53,6 @@ impl Worker { tasks, command_rx, queue_rx, - outbox, exit_signal, } } @@ -87,10 +66,10 @@ impl Worker { _ = self.exit_signal.cancelled() => { break; } - _ = interval.tick() => { + _ = self.queue_rx.changed() => { self.process_inner().await; } - _ = self.queue_rx.changed() => { + _ = interval.tick() => { self.process_inner().await; } } @@ -107,31 +86,30 @@ impl Worker { Some(entry) => entry, None => return, }; - eprintln!("begin to process: {:?}", entry); - let short_id = entry.tx.proposal_short_id().to_string(); + let (res, snapshot) = self .run_verify_tx(entry.clone()) .await .expect("run_verify_tx failed"); - eprintln!("process done: {:?}", res); - self.outbox - .send(VerifyNotify::Done { - short_id, - result: (res.clone(), Arc::clone(&snapshot)), - }) - .unwrap(); - if let Err(e) = res { - self.service - .after_process(entry.tx, entry.remote, &snapshot, &Err(e)) - .await; + match res { + Ok(completed) => { + self.service + .after_process(entry.tx, entry.remote, &snapshot, &Ok(completed)) + .await; + } + Err(e) => { + self.service + .after_process(entry.tx, entry.remote, &snapshot, &Err(e)) + .await; + } } } async fn run_verify_tx( &mut self, entry: Entry, - ) -> Option<(Result, Arc)> { + ) -> Option<(Result, Arc)> { let Entry { tx, remote } = entry; let tx_hash = tx.hash(); @@ -145,7 +123,6 @@ impl Worker { let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); - eprintln!("run_verify_tx cached: {:?}", cached); if let Some(ref completed) = cached { let ret = TimeRelativeTransactionVerifier::new( Arc::clone(&rtx), @@ -159,12 +136,9 @@ impl Worker { let completed = try_or_return_with_snapshot!(ret, snapshot); let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, submit_snapshot); - self.service - .after_process(tx, remote, &submit_snapshot, &Ok(completed)) - .await; - return Some((Ok(false), submit_snapshot)); + let (ret, snapshot) = self.service.submit_entry(tip_hash, entry, status).await; + try_or_return_with_snapshot!(ret, snapshot); + return Some((Ok(completed), snapshot)); } let cloned_snapshot = Arc::clone(&snapshot); @@ -194,7 +168,6 @@ impl Worker { consensus.max_block_cycles() }; - eprintln!("begin to loop: {:?}", rtx); let ret = self .loop_resume( Arc::clone(&rtx), @@ -204,7 +177,6 @@ impl Worker { Arc::clone(&tx_env), ) .await; - eprintln!("loop done: {:?}", ret); let state = try_or_return_with_snapshot!(ret, snapshot); let completed: Completed = match state { @@ -213,8 +185,6 @@ impl Worker { panic!("not expected"); } }; - eprintln!("completed: {:?}", completed); - eprintln!("remote: {:?}", remote); if let Some((declared_cycle, _peer)) = remote { if declared_cycle != completed.cycles { return Some(( @@ -228,22 +198,15 @@ impl Worker { } // verify passed let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, submit_snapshot) = self.service.submit_entry(tip_hash, entry, status).await; + let (ret, snapshot) = self.service.submit_entry(tip_hash, entry, status).await; try_or_return_with_snapshot!(ret, snapshot); self.service.notify_block_assembler(status).await; - self.service - .after_process(tx, remote, &submit_snapshot, &Ok(completed)) - .await; - update_cache( - Arc::clone(&self.service.txs_verify_cache), - tx_hash, - completed, - ) - .await; + let mut guard = self.service.txs_verify_cache.write().await; + guard.put(tx_hash, completed); - Some((Ok(false), snapshot)) + Some((Ok(completed), snapshot)) } async fn loop_resume< @@ -265,14 +228,8 @@ impl Worker { } } -async fn update_cache(cache: Arc>, tx_hash: Byte32, entry: CacheEntry) { - let mut guard = cache.write().await; - guard.put(tx_hash, entry); -} - pub(crate) struct VerifyMgr { workers: Vec, - worker_notify: UnboundedReceiver, join_handles: Option>>, signal_exit: CancellationToken, command_rx: watch::Receiver, @@ -286,7 +243,6 @@ impl VerifyMgr { verify_queue: Arc>, queue_rx: watch::Receiver, ) -> Self { - let (notify_tx, notify_rx) = unbounded_channel::(); let workers: Vec<_> = (0..num_cpus::get()) .map({ let tasks = Arc::clone(&verify_queue); @@ -298,7 +254,6 @@ impl VerifyMgr { Arc::clone(&tasks), command_rx.clone(), queue_rx.clone(), - notify_tx.clone(), signal_exit.clone(), ) } @@ -306,23 +261,19 @@ impl VerifyMgr { .collect(); Self { workers, - worker_notify: notify_rx, join_handles: None, signal_exit, command_rx: chunk_rx, } } - fn start_workers(&mut self) { + async fn start_loop(&mut self) { let mut join_handles = Vec::new(); for w in self.workers.iter_mut() { let h = w.clone().start(); join_handles.push(h); } self.join_handles.replace(join_handles); - } - - async fn start_loop(&mut self) { loop { tokio::select! { _ = self.signal_exit.cancelled() => { @@ -332,15 +283,11 @@ impl VerifyMgr { _ = self.command_rx.changed() => { //eprintln!("command: {:?}", self.command_rx.borrow()); } - res = self.worker_notify.recv() => { - eprintln!("res: {:?}", res); - } } } } pub async fn run(&mut self) { - self.start_workers(); self.start_loop().await; } } From 669d10d067891b9051abce5211a081248d241b75 Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 16 Jan 2024 10:43:57 +0800 Subject: [PATCH 029/294] clean up verify vms --- script/src/verify.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 7a4b4f6bae..caad36b4bd 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -698,7 +698,7 @@ impl, + command_rx: &mut Receiver, ) -> Result { let mut cycles = 0; @@ -1041,7 +1041,7 @@ impl, + command_rx: &mut Receiver, ) -> Result { if group.script.code_hash() == TYPE_ID_CODE_HASH.pack() && Into::::into(group.script.hash_type()) == Into::::into(ScriptHashType::Type) @@ -1145,7 +1145,7 @@ impl, + command_rx: &mut Receiver, ) -> Result { let context: Arc> = Default::default(); @@ -1382,7 +1382,7 @@ fn run_vms( } } -// Run a series of VMs that are just freshly resumed +// Run a series of VMs with control signal, will only return when verification finished async fn run_vms_with_signal( script_group: &ScriptGroup, max_cycles: Cycle, @@ -1409,11 +1409,13 @@ async fn run_vms_with_signal( loop { tokio::select! { _ = signal.changed() => { - let state = signal.borrow().to_owned(); - if state == ChunkCommand::Suspend { - pause.interrupt(); - } else if state == ChunkCommand::Resume { - child_sender.send(ChunkCommand::Resume).unwrap(); + match signal.borrow().to_owned() { + ChunkCommand::Suspend => { + pause.interrupt(); + } + ChunkCommand::Resume => { + child_sender.send(ChunkCommand::Resume).unwrap(); + } } } Some(res) = finished_recv.recv() => { @@ -1500,8 +1502,8 @@ async fn run_vms_child( new_suspended_machines.reverse(); machines.push(machine); machines.append(&mut new_suspended_machines); - // wait for Resume command to begin next loop iteration eprintln!("suspend here: {:?}", machines.len()); + // break to wait for Resume command to begin next loop iteration break; } _ => { From d79dd94540135551b3e3e8471b3d48ae48801811 Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 16 Jan 2024 12:46:23 +0800 Subject: [PATCH 030/294] verify mgr will not pick entry when suspend --- script/src/verify.rs | 1 - tx-pool/src/verify_mgr.rs | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index caad36b4bd..880a40b53a 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1400,7 +1400,6 @@ async fn run_vms_with_signal( let (finished_send, mut finished_recv) = mpsc::unbounded_channel::<(Result, u64)>(); let (child_sender, child_recv) = watch::channel(ChunkCommand::Resume); - eprintln!("begin to run vms with signal: vms len {}", machines.len()); let jh = tokio::spawn( async move { run_vms_child(machines, child_recv, finished_send, context).await }, diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index b9ef1bcae3..1e91718d0e 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -62,22 +62,32 @@ impl Worker { let mut interval = tokio::time::interval(Duration::from_millis(500)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { - tokio::select! { + let try_pick = tokio::select! { _ = self.exit_signal.cancelled() => { break; } _ = self.queue_rx.changed() => { - self.process_inner().await; + true } _ = interval.tick() => { - self.process_inner().await; + true } + _ = self.command_rx.changed() => { + true + } + }; + if try_pick { + self.process_inner().await; } } }) } async fn process_inner(&mut self) { + if self.command_rx.borrow().to_owned() == ChunkCommand::Suspend { + return; + } + if self.tasks.read().await.get_first().is_none() { return; } From 1aa8e9b6ae96b9a8f971d34238b5579f2895065a Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 16 Jan 2024 17:02:36 +0800 Subject: [PATCH 031/294] refactor and add tests for verify_queue --- tx-pool/src/component/mod.rs | 2 +- tx-pool/src/component/tests/chunk.rs | 52 ++++++++++++++++++--- tx-pool/src/{ => component}/verify_queue.rs | 21 ++++++--- tx-pool/src/lib.rs | 1 - tx-pool/src/process.rs | 11 +---- tx-pool/src/service.rs | 2 +- tx-pool/src/verify_mgr.rs | 6 +-- 7 files changed, 67 insertions(+), 28 deletions(-) rename tx-pool/src/{ => component}/verify_queue.rs (89%) diff --git a/tx-pool/src/component/mod.rs b/tx-pool/src/component/mod.rs index beac9064d8..99937a484c 100644 --- a/tx-pool/src/component/mod.rs +++ b/tx-pool/src/component/mod.rs @@ -7,8 +7,8 @@ pub(crate) mod orphan; pub(crate) mod pool_map; pub(crate) mod recent_reject; pub(crate) mod sort_key; - #[cfg(test)] mod tests; +pub(crate) mod verify_queue; pub use self::entry::TxEntry; diff --git a/tx-pool/src/component/tests/chunk.rs b/tx-pool/src/component/tests/chunk.rs index 71d2c70ffe..c2ea1a0f1c 100644 --- a/tx-pool/src/component/tests/chunk.rs +++ b/tx-pool/src/component/tests/chunk.rs @@ -1,23 +1,61 @@ +use crate::component::tests::util::build_tx; +use crate::component::verify_queue::{Entry, VerifyQueue}; use ckb_types::core::TransactionBuilder; +use tokio::select; use tokio::sync::watch; +use tokio::time::sleep; -use crate::verify_queue::{Entry, VerifyQueue}; - -#[test] -fn basic() { +#[tokio::test] +async fn verify_queue_basic() { let tx = TransactionBuilder::default().build(); let entry = Entry { tx: tx.clone(), remote: None, }; + let tx2 = build_tx(vec![(&tx.hash(), 0)], 1); + let id = tx.proposal_short_id(); - let (queue_tx, _queue_rx) = watch::channel(0_usize); + let (queue_tx, mut queue_rx) = watch::channel(0_usize); + let (exit_tx, mut exit_rx) = watch::channel(()); let mut queue = VerifyQueue::new(queue_tx); + let count = tokio::spawn(async move { + let mut counts = vec![]; + loop { + select! { + _ = queue_rx.changed() => { + let value = queue_rx.borrow().to_owned(); + counts.push(value); + } + _ = exit_rx.changed() => { + break; + } + } + } + counts + }); + + assert!(queue.add_tx(tx.clone(), None).unwrap()); + sleep(std::time::Duration::from_millis(100)).await; + + assert!(!queue.add_tx(tx.clone(), None).unwrap()); - assert!(queue.add_tx(tx.clone(), None)); assert_eq!(queue.pop_first().as_ref(), Some(&entry)); assert!(!queue.contains_key(&id)); - assert!(queue.add_tx(tx, None)); + + assert!(queue.add_tx(tx.clone(), None).unwrap()); + sleep(std::time::Duration::from_millis(100)).await; + + assert_eq!(queue.pop_first().as_ref(), Some(&entry)); + + assert!(queue.add_tx(tx.clone(), None).unwrap()); + sleep(std::time::Duration::from_millis(100)).await; + + assert!(queue.add_tx(tx2.clone(), None).unwrap()); + sleep(std::time::Duration::from_millis(100)).await; + + exit_tx.send(()).unwrap(); + let counts = count.await.unwrap(); + assert_eq!(counts, vec![1, 1, 1, 2]); queue.clear(); assert!(!queue.contains_key(&id)); diff --git a/tx-pool/src/verify_queue.rs b/tx-pool/src/component/verify_queue.rs similarity index 89% rename from tx-pool/src/verify_queue.rs rename to tx-pool/src/component/verify_queue.rs index 3e3dc05135..04eb0e782b 100644 --- a/tx-pool/src/verify_queue.rs +++ b/tx-pool/src/component/verify_queue.rs @@ -4,7 +4,7 @@ extern crate rustc_hash; extern crate slab; use ckb_network::PeerIndex; use ckb_types::{ - core::{Cycle, TransactionView}, + core::{tx_pool::Reject, Cycle, TransactionView}, packed::ProposalShortId, }; use ckb_util::shrink_to_fit; @@ -12,7 +12,7 @@ use multi_index_map::MultiIndexMap; use tokio::sync::watch; const DEFAULT_MAX_VERIFY_TRANSACTIONS: usize = 100; -const SHRINK_THRESHOLD: usize = 120; +const SHRINK_THRESHOLD: usize = 100; /// The verify queue is a priority queue of transactions to verify. #[derive(Debug, Clone, Eq)] @@ -120,9 +120,19 @@ impl VerifyQueue { /// If the queue did not have this tx present, true is returned. /// If the queue did have this tx present, false is returned. - pub fn add_tx(&mut self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>) -> bool { + pub fn add_tx( + &mut self, + tx: TransactionView, + remote: Option<(Cycle, PeerIndex)>, + ) -> Result { if self.contains_key(&tx.proposal_short_id()) { - return false; + return Ok(false); + } + if self.is_full() { + return Err(Reject::Full(format!( + "chunk is full, tx_hash: {:#x}", + tx.hash() + ))); } self.inner.insert(VerifyEntry { id: tx.proposal_short_id(), @@ -132,9 +142,8 @@ impl VerifyQueue { .as_millis() as u64, inner: Entry { tx, remote }, }); - eprintln!("added to queue len: {:?}", self.len()); self.queue_tx.send(self.len()).unwrap(); - true + Ok(true) } /// Clears the map, removing all elements. diff --git a/tx-pool/src/lib.rs b/tx-pool/src/lib.rs index a117325c87..b22c3248b5 100644 --- a/tx-pool/src/lib.rs +++ b/tx-pool/src/lib.rs @@ -12,7 +12,6 @@ mod process; pub mod service; mod util; mod verify_mgr; -pub mod verify_queue; pub use ckb_jsonrpc_types::BlockTemplate; pub use component::entry::TxEntry; diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index ab08695a42..65ea409fd3 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -731,16 +731,9 @@ impl TxPoolService { &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, - ) -> Result<(), Reject> { - let tx_hash = tx.hash(); + ) -> Result { let mut chunk = self.verify_queue.write().await; - if !chunk.add_tx(tx, remote) { - return Err(Reject::Full(format!( - "chunk is full, tx_hash: {:#x}", - tx_hash - ))); - } - Ok(()) + chunk.add_tx(tx, remote) } pub(crate) async fn _process_tx( diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index 43bc55d8d2..7b87ff9e7a 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -4,11 +4,11 @@ use crate::block_assembler::{self, BlockAssembler}; use crate::callback::{Callbacks, PendingCallback, ProposedCallback, RejectCallback}; use crate::component::orphan::OrphanPool; use crate::component::pool_map::{PoolEntry, Status}; +use crate::component::verify_queue::VerifyQueue; use crate::error::{handle_recv_error, handle_send_cmd_error, handle_try_send_error}; use crate::pool::TxPool; use crate::util::after_delay_window; use crate::verify_mgr::VerifyMgr; -use crate::verify_queue::VerifyQueue; use ckb_app_config::{BlockAssemblerConfig, TxPoolConfig}; use ckb_async_runtime::Handle; use ckb_chain_spec::consensus::Consensus; diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 1e91718d0e..a93bb81ab4 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,7 +1,7 @@ extern crate num_cpus; use crate::component::entry::TxEntry; +use crate::component::verify_queue::{Entry, VerifyQueue}; use crate::try_or_return_with_snapshot; -use crate::verify_queue::{Entry, VerifyQueue}; use crate::{error::Reject, service::TxPoolService}; use ckb_chain_spec::consensus::Consensus; use ckb_logger::info; @@ -69,10 +69,10 @@ impl Worker { _ = self.queue_rx.changed() => { true } - _ = interval.tick() => { + _ = self.command_rx.changed() => { true } - _ = self.command_rx.changed() => { + _ = interval.tick() => { true } }; From 253e94c3602ca7b16d4fae19f5e99a83e5bb84a0 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 17 Jan 2024 06:00:20 +0800 Subject: [PATCH 032/294] use verify directly in _resumable_process_tx --- tx-pool/src/process.rs | 48 ++++++++++-------------- verification/src/transaction_verifier.rs | 1 + 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 65ea409fd3..3bb06a6e6b 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -27,13 +27,14 @@ use ckb_types::{ use ckb_util::LinkedHashSet; use ckb_verification::{ cache::{CacheEntry, Completed}, - ContextualTransactionVerifier, DaoScriptSizeVerifier, ScriptVerifyResult, - TimeRelativeTransactionVerifier, TxVerifyEnv, + ContextualTransactionVerifier, DaoScriptSizeVerifier, TimeRelativeTransactionVerifier, + TxVerifyEnv, }; use std::collections::HashSet; use std::collections::{HashMap, VecDeque}; use std::sync::Arc; use std::time::Duration; +use tokio::task::block_in_place; const DELAY_LIMIT: usize = 1_500 * 21; // 1_500 per block, 21 blocks @@ -672,35 +673,26 @@ impl TxPoolService { } else { // for local transaction, we verify it directly with a max cycles limit let ret = { - let verifier = ContextualTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&self.consensus), - data_loader, - tx_env, - ); - - let cycle_limit = snapshot.cloned_consensus().max_block_cycles(); - let ret = verifier - .verify_until_completed(cycle_limit) - .await - .map_err(Reject::Verification); - let (ret, fee) = try_or_return_with_snapshot!(ret, snapshot); - match ret { - ScriptVerifyResult::Completed(cycles) => { - let res = DaoScriptSizeVerifier::new( + block_in_place(|| { + let cycle_limit = snapshot.cloned_consensus().max_block_cycles(); + ContextualTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&self.consensus), + data_loader, + tx_env, + ) + .verify(cycle_limit, false) + .and_then(|result| { + DaoScriptSizeVerifier::new( Arc::clone(&rtx), - Arc::clone(&self.consensus), + snapshot.cloned_consensus(), snapshot.as_data_loader(), ) - .verify() - .map_err(Reject::Verification); - try_or_return_with_snapshot!(res, snapshot); - Ok(Completed { cycles, fee }) - } - ScriptVerifyResult::Suspended(_state) => { - panic!("unexpect suspend"); - } - } + .verify()?; + Ok(result) + }) + .map_err(Reject::Verification) + }) }; try_or_return_with_snapshot!(ret, snapshot) diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 424485a474..0fd8547f8d 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -161,6 +161,7 @@ where } /// Perform context-dependent verification, return a `Result` to `CacheEntry` + /// FIXME(yukang): only used in tests pub async fn verify_until_completed( &self, limit_cycles: Cycle, From b20b15bdc2c8bd305750c52001b5f87e02e4bc2d Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 17 Jan 2024 06:10:16 +0800 Subject: [PATCH 033/294] Refactor transaction verification with pattern match, which is more friendly in format --- tx-pool/src/process.rs | 90 +++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 3bb06a6e6b..d307e8445b 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -651,51 +651,55 @@ impl TxPoolService { let data_loader = snapshot.as_data_loader(); - let completed = if let Some(ref completed) = cached { - let ret = TimeRelativeTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&self.consensus), - data_loader, - tx_env, - ) - .verify() - .map_err(Reject::Verification); - try_or_return_with_snapshot!(ret, snapshot); - *completed - } else if remote.is_some() { - // for remote transaction with large decleard cycles, we enqueue it to verify queue - let ret = self - .enqueue_suspended_tx(rtx.transaction.clone(), remote) - .await; - try_or_return_with_snapshot!(ret, snapshot); - eprintln!("added to queue here: {:?}", tx.proposal_short_id()); - return Some((Ok(ProcessResult::Suspended), snapshot)); - } else { - // for local transaction, we verify it directly with a max cycles limit - let ret = { - block_in_place(|| { - let cycle_limit = snapshot.cloned_consensus().max_block_cycles(); - ContextualTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&self.consensus), - data_loader, - tx_env, - ) - .verify(cycle_limit, false) - .and_then(|result| { - DaoScriptSizeVerifier::new( + let completed = match cached { + Some(completed) => { + let ret = TimeRelativeTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&self.consensus), + data_loader, + tx_env, + ) + .verify() + .map_err(Reject::Verification); + try_or_return_with_snapshot!(ret, snapshot); + completed + } + None if remote.is_some() => { + // for remote transaction with large decleard cycles, we enqueue it to verify queue + let ret = self + .enqueue_suspended_tx(rtx.transaction.clone(), remote) + .await; + try_or_return_with_snapshot!(ret, snapshot); + eprintln!("added to queue here: {:?}", tx.proposal_short_id()); + return Some((Ok(ProcessResult::Suspended), snapshot)); + } + None => { + // for local transaction, we verify it directly with a max cycles limit + let ret = { + block_in_place(|| { + let cycle_limit = snapshot.cloned_consensus().max_block_cycles(); + ContextualTransactionVerifier::new( Arc::clone(&rtx), - snapshot.cloned_consensus(), - snapshot.as_data_loader(), + Arc::clone(&self.consensus), + data_loader, + tx_env, ) - .verify()?; - Ok(result) + .verify(cycle_limit, false) + .and_then(|result| { + DaoScriptSizeVerifier::new( + Arc::clone(&rtx), + snapshot.cloned_consensus(), + snapshot.as_data_loader(), + ) + .verify()?; + Ok(result) + }) + .map_err(Reject::Verification) }) - .map_err(Reject::Verification) - }) - }; + }; - try_or_return_with_snapshot!(ret, snapshot) + try_or_return_with_snapshot!(ret, snapshot) + } }; let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); @@ -715,10 +719,6 @@ impl TxPoolService { Some((Ok(ProcessResult::Completed(completed)), submit_snapshot)) } - pub(crate) async fn _is_chunk_full(&self) -> bool { - self.verify_queue.read().await.is_full() - } - pub(crate) async fn enqueue_suspended_tx( &self, tx: TransactionView, From e971c8f3a38f823499c07fba05013325dc6e7c2c Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 17 Jan 2024 09:35:33 +0800 Subject: [PATCH 034/294] Add tests for run_vms_with_signal, fix debug pause --- script/src/syscalls/pause.rs | 4 +- script/src/syscalls/spawn.rs | 2 +- script/src/verify.rs | 15 +++- .../tests/ckb_latest/features_since_v2021.rs | 40 ++++++++- .../tests/ckb_latest/features_since_v2023.rs | 82 +++++++++++++++++++ script/src/verify/tests/utils.rs | 70 ++++++++++++++++ tx-pool/src/process.rs | 1 - 7 files changed, 205 insertions(+), 9 deletions(-) diff --git a/script/src/syscalls/pause.rs b/script/src/syscalls/pause.rs index 48044fe013..e6a81e44af 100644 --- a/script/src/syscalls/pause.rs +++ b/script/src/syscalls/pause.rs @@ -28,6 +28,8 @@ impl Syscalls for Pause { if self.skip.load(Ordering::SeqCst) { return Ok(true); } - Err(VMError::CyclesExceeded) + // FIXME(yukang): currently we change to use VMInternalError::CyclesExceeded | VMInternalError::Pause + // as a flag to Suspend, only for compatibility with old tests, maybe we need cleanup this later. + Err(VMError::Pause) } } diff --git a/script/src/syscalls/spawn.rs b/script/src/syscalls/spawn.rs index c5a8eb7548..15301bc199 100644 --- a/script/src/syscalls/spawn.rs +++ b/script/src/syscalls/spawn.rs @@ -235,7 +235,7 @@ where Ok(true) } Err(err) => { - if matches!(err, VMError::CyclesExceeded | VMError::Pause) { + if matches!(err, VMError::Pause | VMError::CyclesExceeded) { let mut context = self.context.lock().map_err(|e| { VMError::Unexpected(format!("Failed to acquire lock: {}", e)) })?; diff --git a/script/src/verify.rs b/script/src/verify.rs index 880a40b53a..0d2dff5c56 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1353,7 +1353,7 @@ fn run_vms( } } Err(error) => match error { - VMInternalError::CyclesExceeded => { + VMInternalError::CyclesExceeded | VMInternalError::Pause => { let mut new_suspended_machines: Vec<_> = { let mut context = context.lock().map_err(|e| { ScriptError::Other(format!("Failed to acquire lock: {}", e)) @@ -1396,9 +1396,13 @@ async fn run_vms_with_signal( )); } - let pause = machines[0].pause(); + let mut pause = machines[0].pause(); let (finished_send, mut finished_recv) = mpsc::unbounded_channel::<(Result, u64)>(); + + // send initial `Resume` command to child + // it's maybe useful to set initial command to `signal.borrow().to_owned()` + // so that we can control the initial state of child, which is useful for testing purpose let (child_sender, child_recv) = watch::channel(ChunkCommand::Resume); let jh = tokio::spawn( @@ -1406,6 +1410,7 @@ async fn run_vms_with_signal( ); loop { + //eprintln!("parent wait to receive: {:?}", signal.borrow().to_owned()); tokio::select! { _ = signal.changed() => { match signal.borrow().to_owned() { @@ -1413,7 +1418,8 @@ async fn run_vms_with_signal( pause.interrupt(); } ChunkCommand::Resume => { - child_sender.send(ChunkCommand::Resume).unwrap(); + pause.free(); + let _res = child_sender.send(ChunkCommand::Resume); } } } @@ -1451,6 +1457,8 @@ async fn run_vms_child( ) { let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); child_recv.mark_changed(); + // mark changed to make sure we can receive initial command + // and start to run immediately loop { select! { _ = child_recv.changed() => { @@ -1501,7 +1509,6 @@ async fn run_vms_child( new_suspended_machines.reverse(); machines.push(machine); machines.append(&mut new_suspended_machines); - eprintln!("suspend here: {:?}", machines.len()); // break to wait for Resume command to begin next loop iteration break; } diff --git a/script/src/verify/tests/ckb_latest/features_since_v2021.rs b/script/src/verify/tests/ckb_latest/features_since_v2021.rs index 819d247443..a9b8176bfd 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2021.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2021.rs @@ -408,6 +408,42 @@ fn check_exec_wrong_callee_format() { assert!(result.is_err()); } +#[tokio::test] +async fn async_check_exec_wrong_callee_format() { + let script_version = SCRIPT_VERSION; + + let (exec_caller_cell, exec_caller_data_hash) = + load_cell_from_path("testdata/exec_caller_from_cell_data"); + let (exec_callee_cell, _exec_caller_data_hash) = + load_cell_from_slice(&[0x00, 0x01, 0x02, 0x03]); + + let exec_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(exec_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(exec_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![exec_caller_cell, exec_callee_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier + .verify_without_limit_async(script_version, &rtx) + .await; + assert!(result.is_err()); +} + #[test] fn check_exec_big_offset_length() { let script_version = SCRIPT_VERSION; @@ -518,7 +554,7 @@ fn _check_type_id_one_in_one_out_resume(step_cycles: Cycle) -> Result<(), TestCa } } Err(error) => match error { - VMInternalError::CyclesExceeded => { + VMInternalError::CyclesExceeded | VMInternalError::Pause => { tmp = Some(vm); limit += step_cycles; continue; @@ -718,7 +754,7 @@ fn _check_typical_secp256k1_blake160_2_in_2_out_tx_with_chunk(step_cycles: Cycle } } Err(error) => match error { - VMInternalError::CyclesExceeded => { + VMInternalError::CyclesExceeded | VMInternalError::Pause => { tmp = Some(vm); limit += step_cycles; continue; diff --git a/script/src/verify/tests/ckb_latest/features_since_v2023.rs b/script/src/verify/tests/ckb_latest/features_since_v2023.rs index aafed8439d..c0dbb80c71 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2023.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2023.rs @@ -647,6 +647,88 @@ fn check_spawn_snapshot() { assert!(chunks_count > 1); } +#[tokio::test] +async fn check_spawn_async() { + let script_version = SCRIPT_VERSION; + if script_version <= ScriptVersion::V1 { + return; + } + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_exec"); + let (snapshot_cell, _) = load_cell_from_path("testdata/current_cycles_with_snapshot"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell, snapshot_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_pause(script_version, &rtx, Cycle::MAX); + let cycles_once = result.unwrap(); + + // we use debug pause to test context resume + // `current_cycles_with_snapshot` will try to pause verifier + // here we use `channel` to send Resume to verifier until it completes + let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); + let _jt = tokio::spawn(async move { + loop { + let res = command_tx.send(ChunkCommand::Resume); + if res.is_err() { + break; + } + tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; + } + }); + let cycles = verifier + .verify_complete_async(script_version, &rtx, &mut command_rx, false) + .await + .unwrap(); + assert_eq!(cycles, cycles_once); + + // we send Resume/Suspend to command_rx in a loop, make sure cycles is still the same + let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); + let _jt = tokio::spawn(async move { + loop { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + let res = command_tx.send(ChunkCommand::Suspend); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + if res.is_err() { + break; + } + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + let res = command_tx.send(ChunkCommand::Resume); + if res.is_err() { + break; + } + tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; + + let _res = command_tx.send(ChunkCommand::Suspend); + tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; + } + }); + + let cycles = verifier + .verify_complete_async(script_version, &rtx, &mut command_rx, true) + .await + .unwrap(); + assert_eq!(cycles, cycles_once); +} + #[test] fn check_spawn_state() { let script_version = SCRIPT_VERSION; diff --git a/script/src/verify/tests/utils.rs b/script/src/verify/tests/utils.rs index 8eaffcd64d..c355523047 100644 --- a/script/src/verify/tests/utils.rs +++ b/script/src/verify/tests/utils.rs @@ -173,6 +173,76 @@ impl TransactionScriptsVerifierWithEnv { self.verify(version, rtx, u64::MAX) } + pub(crate) async fn verify_without_limit_async( + &self, + version: ScriptVersion, + rtx: &ResolvedTransaction, + ) -> Result { + let data_loader = self.store.as_data_loader(); + let epoch = match version { + ScriptVersion::V0 => EpochNumberWithFraction::new(0, 0, 1), + ScriptVersion::V1 => EpochNumberWithFraction::new(self.version_1_enabled_at, 0, 1), + ScriptVersion::V2 => EpochNumberWithFraction::new(self.version_2_enabled_at, 0, 1), + }; + let header = HeaderView::new_advanced_builder() + .epoch(epoch.pack()) + .build(); + let tx_env = Arc::new(TxVerifyEnv::new_commit(&header)); + let verifier = TransactionScriptsVerifier::new( + Arc::new(rtx.clone()), + data_loader, + Arc::clone(&self.consensus), + tx_env, + ); + + let (_command_tx, mut command_rx) = tokio::sync::watch::channel(ChunkCommand::Resume); + let res = verifier + .resumable_verify_with_signal(u64::MAX, &mut command_rx) + .await; + match res { + Ok(VerifyResult::Completed(cycle)) => Ok(cycle), + Ok(VerifyResult::Suspended(_)) => unreachable!(), + Err(err) => Err(err), + } + } + + pub(crate) async fn verify_complete_async( + &self, + version: ScriptVersion, + rtx: &ResolvedTransaction, + command_rx: &mut tokio::sync::watch::Receiver, + skip_debug_pause: bool, + ) -> Result { + let data_loader = self.store.as_data_loader(); + let epoch = match version { + ScriptVersion::V0 => EpochNumberWithFraction::new(0, 0, 1), + ScriptVersion::V1 => EpochNumberWithFraction::new(self.version_1_enabled_at, 0, 1), + ScriptVersion::V2 => EpochNumberWithFraction::new(self.version_2_enabled_at, 0, 1), + }; + let header = HeaderView::new_advanced_builder() + .epoch(epoch.pack()) + .build(); + let tx_env = Arc::new(TxVerifyEnv::new_commit(&header)); + let verifier = TransactionScriptsVerifier::new( + Arc::new(rtx.clone()), + data_loader, + Arc::clone(&self.consensus), + tx_env, + ); + + if skip_debug_pause { + verifier.set_skip_pause(true); + } + let res = verifier + .resumable_verify_with_signal(Cycle::MAX, command_rx) + .await; + match res { + Ok(VerifyResult::Completed(cycle)) => Ok(cycle), + Ok(VerifyResult::Suspended(_)) => unreachable!(), + Err(err) => Err(err), + } + } + // If the max cycles is meaningless, please use `verify_without_limit`, // so reviewers or developers can understand the intentions easier. pub(crate) fn verify( diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index d307e8445b..16547a2ef0 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -303,7 +303,6 @@ impl TxPoolService { } if let Some((ret, snapshot)) = self._resumeble_process_tx(tx.clone(), remote).await { - eprintln!("resumeble_process_tx: ret = {:?}", ret); match ret { Ok(processed) => { if let ProcessResult::Completed(completed) = processed { From de591d9a0e18dfa2bc08fbe08a1ad5a94774b111 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 18 Jan 2024 00:19:30 +0800 Subject: [PATCH 035/294] Add Stop command and test cases to verify suspend/resume/shutdown --- script/Cargo.toml | 2 +- script/src/types.rs | 2 + script/src/verify.rs | 21 +++-- .../tests/ckb_latest/features_since_v2023.rs | 76 +++++++++++++++--- script/testdata/Makefile | 2 + script/testdata/infinite_loop | Bin 0 -> 3624 bytes script/testdata/infinite_loop.c | 15 ++++ tx-pool/src/verify_mgr.rs | 33 +++++--- 8 files changed, 122 insertions(+), 29 deletions(-) create mode 100755 script/testdata/infinite_loop create mode 100644 script/testdata/infinite_loop.c diff --git a/script/Cargo.toml b/script/Cargo.toml index b537c5b993..d1b3f451e1 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -28,7 +28,7 @@ ckb-logger = { path = "../util/logger", version = "= 0.114.0-pre", optional = tr serde = { version = "1.0", features = ["derive"] } ckb-error = { path = "../error", version = "= 0.114.0-pre" } ckb-chain-spec = { path = "../spec", version = "= 0.114.0-pre" } -tokio = { version = "1.35.0", features = ["sync"] } +tokio = { version = "1.35.0", features = ["sync", "rt-multi-thread"] } [dev-dependencies] proptest = "1.0" diff --git a/script/src/types.rs b/script/src/types.rs index 9b4e02de0f..bc4841b660 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -464,4 +464,6 @@ pub enum ChunkCommand { Suspend, /// Resume the verification process Resume, + /// Stop the verification process + Stop, } diff --git a/script/src/verify.rs b/script/src/verify.rs index 0d2dff5c56..b4e3cb558c 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1410,16 +1410,16 @@ async fn run_vms_with_signal( ); loop { - //eprintln!("parent wait to receive: {:?}", signal.borrow().to_owned()); tokio::select! { _ = signal.changed() => { - match signal.borrow().to_owned() { + let command = signal.borrow().to_owned(); + match command { ChunkCommand::Suspend => { pause.interrupt(); } - ChunkCommand::Resume => { + ChunkCommand::Resume | ChunkCommand::Stop => { pause.free(); - let _res = child_sender.send(ChunkCommand::Resume); + let _res = child_sender.send(command); } } } @@ -1462,9 +1462,16 @@ async fn run_vms_child( loop { select! { _ = child_recv.changed() => { - let state = child_recv.borrow().to_owned(); - if state != ChunkCommand::Resume { - continue; + match child_recv.borrow().to_owned() { + ChunkCommand::Stop => { + let exit = (Err(ckb_vm::Error::Unexpected("stopped".to_string())), cycles); + let _ = finished_send.send(exit); + return; + } + ChunkCommand::Suspend => { + continue; + } + ChunkCommand::Resume => {} } if machines.is_empty() { finished_send.send((Ok(exit_code), cycles)).unwrap(); diff --git a/script/src/verify/tests/ckb_latest/features_since_v2023.rs b/script/src/verify/tests/ckb_latest/features_since_v2023.rs index c0dbb80c71..79765d03b7 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2023.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2023.rs @@ -647,7 +647,7 @@ fn check_spawn_snapshot() { assert!(chunks_count > 1); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn check_spawn_async() { let script_version = SCRIPT_VERSION; if script_version <= ScriptVersion::V1 { @@ -705,20 +705,17 @@ async fn check_spawn_async() { let _jt = tokio::spawn(async move { loop { tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - let res = command_tx.send(ChunkCommand::Suspend); + let _res = command_tx.send(ChunkCommand::Suspend); tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - if res.is_err() { - break; - } - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - let res = command_tx.send(ChunkCommand::Resume); - if res.is_err() { - break; - } + + let _res = command_tx.send(ChunkCommand::Resume); tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; let _res = command_tx.send(ChunkCommand::Suspend); - tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + + let _res = command_tx.send(ChunkCommand::Resume); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; } }); @@ -729,6 +726,63 @@ async fn check_spawn_async() { assert_eq!(cycles, cycles_once); } +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn check_spawn_suspend_shutdown() { + let script_version = SCRIPT_VERSION; + if script_version <= ScriptVersion::V1 { + return; + } + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_exec"); + let (snapshot_cell, _) = load_cell_from_path("testdata/infinite_loop"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell, snapshot_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + + let verifier = TransactionScriptsVerifierWithEnv::new(); + let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); + let _jt = tokio::spawn(async move { + loop { + let _res = command_tx.send(ChunkCommand::Suspend); + tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; + + let _res = command_tx.send(ChunkCommand::Resume); + tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; + + let _res = command_tx.send(ChunkCommand::Suspend); + tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; + + let _res = command_tx.send(ChunkCommand::Stop); + tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; + } + }); + + let res = verifier + .verify_complete_async(script_version, &rtx, &mut command_rx, true) + .await; + assert!(res.is_err()); + let err = res.unwrap_err().to_string(); + assert!(err.contains("VM Internal Error: Unexpected(\"stopped\")")); +} + #[test] fn check_spawn_state() { let script_version = SCRIPT_VERSION; diff --git a/script/testdata/Makefile b/script/testdata/Makefile index 1550a931de..70567978d3 100644 --- a/script/testdata/Makefile +++ b/script/testdata/Makefile @@ -45,6 +45,7 @@ ALL_BINS := jalr_zero \ exec_caller_big_offset_length \ exec_configurable_callee \ exec_configurable_caller \ + infinite_loop \ load_code_to_stack_then_reuse \ load_is_even_into_global \ load_is_even_with_snapshot \ @@ -120,6 +121,7 @@ cpop_lock: cpop_lock.c mop_adc_lock: mop_adc_lock.S current_cycles: current_cycles.c current_cycles_with_snapshot: current_cycles_with_snapshot.c +infinite_loop: infinite_loop.c vm_version: vm_version.c vm_version_2: vm_version_2.c vm_version_with_snapshot: vm_version_with_snapshot.c diff --git a/script/testdata/infinite_loop b/script/testdata/infinite_loop new file mode 100755 index 0000000000000000000000000000000000000000..99cd4f7350fd5b5c83245110a392e652be69bc35 GIT binary patch literal 3624 zcmeHJZ%i9y7=P~CYblFJ*Nu)%q_zsPGZ*TBGPf9Dy|PV1mL)U9IBB_dqb{A;c5|4J z^?EH>LS{&3x$mNoOo7aZ}%ydtX*a{IpLd zeUf{hf4|@R+&%ZU_nZ@6bg~QsR9N^8u=mC43eZdv-J28x=bE7jShQ|}(p3~kFb@#e zQub_DDN(AS`~gaX%)=yq*r4F4KC4x5lSHraMiVF!pKkFQxXjKQsYp2a1VBQ%Jz0-++|7QG~xXEj-CGzX>< z)m16K&p-TSBx#Q2p!qRIZ(gj)3Z9H7G2m!0s2vSeLx*NHOOHeoQAY!FP}~}4GadnD zqbM6~tQx|ZD9%*HVb0a+NqJZw=Z=I&T%7I$btJhmwPvNQvi@reT!Fp{PUZbO5mn6}ib;Z|C{v!n8fesKl`h~O^leEP>tQT8B1V$G zOsS@%y!CC_K2RNBNNBYt%f3Gzzu0#FK;KdEt3|a~TEZH(+Tr(o zz?H=ru#6jRmZl#}ai?hf5#+Hp+l}*97W7zOy88D&OQSDEEV7(s}9Le4)=e_K?O{P;HIymwS8x@5beDgXbj z>C=?${_b-9X8)a~pE>k@QIui_iXA9+pxA+82Oh2iJ!D>_x^4gd=S(}>+K)bMve|eW zUk5E#R3YFCif4c^AyZw>1-6KrjTaFu0DQ1J7z%{kUBHLDXG6eyyE~5u z+&Xct3`QtC=~E@oep{N;PB9b zzn%D-if3jllVrY_R9twUB?eI&T+pL9=96(7=$uXGZ}oaQZ1i`ad|3g%gY>_HY)HoX zwOvYxMRhImA1&Z_6Mi@0QykrZ`c&^miNgD*@4bic51`4sP$-Ya_8_B>e~rvr*Gd({ zf89UjHzNb@N&7cL_%npxM1e$atS_KAu8$7eI-&f{>!J!4hcE|AJGxNqo>iw{`H9ru aRS?4_< +#else +#define ckb_debug(...) +#define sprintf(...) +#endif + + +int main() { + for(; ;) { + } + return CKB_SUCCESS; +} diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index a93bb81ab4..2f53419440 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -239,7 +239,7 @@ impl Worker { } pub(crate) struct VerifyMgr { - workers: Vec, + workers: Vec<(watch::Sender, Worker)>, join_handles: Option>>, signal_exit: CancellationToken, command_rx: watch::Receiver, @@ -256,15 +256,18 @@ impl VerifyMgr { let workers: Vec<_> = (0..num_cpus::get()) .map({ let tasks = Arc::clone(&verify_queue); - let command_rx = chunk_rx.clone(); let signal_exit = signal_exit.clone(); move |_| { - Worker::new( - service.clone(), - Arc::clone(&tasks), - command_rx.clone(), - queue_rx.clone(), - signal_exit.clone(), + let (child_tx, child_rx) = watch::channel(ChunkCommand::Resume); + ( + child_tx, + Worker::new( + service.clone(), + Arc::clone(&tasks), + child_rx, + queue_rx.clone(), + signal_exit.clone(), + ), ) } }) @@ -277,10 +280,18 @@ impl VerifyMgr { } } + fn send_child_command(&self, command: ChunkCommand) { + for w in &self.workers { + if let Err(err) = w.0.send(command.clone()) { + info!("send worker command failed, error: {}", err); + } + } + } + async fn start_loop(&mut self) { let mut join_handles = Vec::new(); for w in self.workers.iter_mut() { - let h = w.clone().start(); + let h = w.1.clone().start(); join_handles.push(h); } self.join_handles.replace(join_handles); @@ -288,10 +299,12 @@ impl VerifyMgr { tokio::select! { _ = self.signal_exit.cancelled() => { info!("TxPool chunk_command service received exit signal, exit now"); + self.send_child_command(ChunkCommand::Stop); break; }, _ = self.command_rx.changed() => { - //eprintln!("command: {:?}", self.command_rx.borrow()); + let command = self.command_rx.borrow().to_owned(); + self.send_child_command(command); } } } From 3e23ff22679e549053f413b1e4a2e0734b3602f2 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 18 Jan 2024 00:35:57 +0800 Subject: [PATCH 036/294] refactor verify worker clean up resume_loop --- Cargo.lock | 1 - script/testdata/infinite_loop.c | 3 +- tx-pool/Cargo.toml | 1 - tx-pool/src/process.rs | 105 ++++++++++++++++++++++ tx-pool/src/verify_mgr.rs | 154 ++------------------------------ 5 files changed, 113 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d7e210a44..30e983e259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1622,7 +1622,6 @@ dependencies = [ "ckb-stop-handler", "ckb-store", "ckb-systemtime", - "ckb-traits", "ckb-types", "ckb-util", "ckb-verification", diff --git a/script/testdata/infinite_loop.c b/script/testdata/infinite_loop.c index e911db050b..2a90f89cc7 100644 --- a/script/testdata/infinite_loop.c +++ b/script/testdata/infinite_loop.c @@ -9,7 +9,6 @@ int main() { - for(; ;) { - } + for(; ;) {} return CKB_SUCCESS; } diff --git a/tx-pool/Cargo.toml b/tx-pool/Cargo.toml index e65e054591..9ee7279a61 100644 --- a/tx-pool/Cargo.toml +++ b/tx-pool/Cargo.toml @@ -33,7 +33,6 @@ ckb-stop-handler = { path = "../util/stop-handler", version = "= 0.114.0-pre" } ckb-app-config = { path = "../util/app-config", version = "= 0.114.0-pre" } ckb-network = { path = "../network", version = "= 0.114.0-pre" } ckb-channel = { path = "../util/channel", version = "= 0.114.0-pre" } -ckb-traits = { path = "../traits", version = "= 0.114.0-pre" } ckb-db = { path = "../db", version = "= 0.114.0-pre" } ckb-script = { path = "../script", version = "= 0.114.0-pre" } diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 16547a2ef0..078bf74e75 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -2,6 +2,7 @@ use crate::callback::Callbacks; use crate::component::entry::TxEntry; use crate::component::orphan::Entry as OrphanEntry; use crate::component::pool_map::Status; +use crate::component::verify_queue::Entry; use crate::error::Reject; use crate::pool::TxPool; use crate::service::{BlockAssemblerMessage, TxPoolService, TxVerificationResult}; @@ -16,6 +17,7 @@ use ckb_jsonrpc_types::BlockTemplate; use ckb_logger::Level::Trace; use ckb_logger::{debug, error, info, log_enabled_target, trace_target}; use ckb_network::PeerIndex; +use ckb_script::{ChunkCommand, VerifyResult}; use ckb_snapshot::Snapshot; use ckb_store::data_loader_wrapper::AsDataLoader; use ckb_store::ChainStore; @@ -30,10 +32,12 @@ use ckb_verification::{ ContextualTransactionVerifier, DaoScriptSizeVerifier, TimeRelativeTransactionVerifier, TxVerifyEnv, }; +use ckb_verification::{ContextualWithoutScriptTransactionVerifier, ScriptVerifier}; use std::collections::HashSet; use std::collections::{HashMap, VecDeque}; use std::sync::Arc; use std::time::Duration; +use tokio::sync::watch; use tokio::task::block_in_place; const DELAY_LIMIT: usize = 1_500 * 21; // 1_500 per block, 21 blocks @@ -718,6 +722,107 @@ impl TxPoolService { Some((Ok(ProcessResult::Completed(completed)), submit_snapshot)) } + pub(crate) async fn run_verify_tx( + &mut self, + entry: Entry, + command_rx: &mut watch::Receiver, + ) -> Option<(Result, Arc)> { + let Entry { tx, remote } = entry; + let tx_hash = tx.hash(); + + let (ret, snapshot) = self.pre_check(&tx).await; + let (tip_hash, rtx, status, fee, tx_size) = try_or_return_with_snapshot!(ret, snapshot); + let cached = self.fetch_tx_verify_cache(&tx_hash).await; + let tip_header = snapshot.tip_header(); + let consensus = snapshot.cloned_consensus(); + + let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); + + if let Some(ref completed) = cached { + let ret = TimeRelativeTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&consensus), + snapshot.as_data_loader(), + Arc::clone(&tx_env), + ) + .verify() + .map(|_| *completed) + .map_err(Reject::Verification); + let completed = try_or_return_with_snapshot!(ret, snapshot); + + let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); + let (ret, snapshot) = self.submit_entry(tip_hash, entry, status).await; + try_or_return_with_snapshot!(ret, snapshot); + return Some((Ok(completed), snapshot)); + } + + let cloned_snapshot = Arc::clone(&snapshot); + let data_loader = cloned_snapshot.as_data_loader(); + let ret = ContextualWithoutScriptTransactionVerifier::new( + Arc::clone(&rtx), + Arc::clone(&consensus), + data_loader.clone(), + Arc::clone(&tx_env), + ) + .verify() + .and_then(|result| { + DaoScriptSizeVerifier::new( + Arc::clone(&rtx), + Arc::clone(&consensus), + data_loader.clone(), + ) + .verify()?; + Ok(result) + }) + .map_err(Reject::Verification); + let fee = try_or_return_with_snapshot!(ret, snapshot); + + let max_cycles = if let Some((declared_cycle, _peer)) = remote { + declared_cycle + } else { + consensus.max_block_cycles() + }; + + let ret = { + let script_verifier = + ScriptVerifier::new(Arc::clone(&rtx), data_loader, consensus, tx_env); + script_verifier + .resumable_verify_with_signal(max_cycles, command_rx) + .await + .map_err(Reject::Verification) + }; + + let state = try_or_return_with_snapshot!(ret, snapshot); + let completed: Completed = match state { + VerifyResult::Completed(cycles) => Completed { cycles, fee }, + _ => { + panic!("not expected"); + } + }; + if let Some((declared_cycle, _peer)) = remote { + if declared_cycle != completed.cycles { + return Some(( + Err(Reject::DeclaredWrongCycles( + declared_cycle, + completed.cycles, + )), + snapshot, + )); + } + } + // verify passed + let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); + let (ret, snapshot) = self.submit_entry(tip_hash, entry, status).await; + try_or_return_with_snapshot!(ret, snapshot); + + self.notify_block_assembler(status).await; + + let mut guard = self.txs_verify_cache.write().await; + guard.put(tx_hash, completed); + + Some((Ok(completed), snapshot)) + } + pub(crate) async fn enqueue_suspended_tx( &self, tx: TransactionView, diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 2f53419440..5188e8c50a 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,20 +1,9 @@ extern crate num_cpus; -use crate::component::entry::TxEntry; -use crate::component::verify_queue::{Entry, VerifyQueue}; -use crate::try_or_return_with_snapshot; -use crate::{error::Reject, service::TxPoolService}; -use ckb_chain_spec::consensus::Consensus; +use crate::component::verify_queue::VerifyQueue; +use crate::service::TxPoolService; use ckb_logger::info; -use ckb_script::{ChunkCommand, VerifyResult}; -use ckb_snapshot::Snapshot; +use ckb_script::ChunkCommand; use ckb_stop_handler::CancellationToken; -use ckb_store::data_loader_wrapper::AsDataLoader; -use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; -use ckb_types::core::{cell::ResolvedTransaction, Cycle}; -use ckb_verification::{ - cache::Completed, ContextualWithoutScriptTransactionVerifier, DaoScriptSizeVerifier, - ScriptVerifier, TimeRelativeTransactionVerifier, TxVerifyEnv, -}; use std::sync::Arc; use std::time::Duration; use tokio::sync::{watch, RwLock}; @@ -98,143 +87,14 @@ impl Worker { }; let (res, snapshot) = self - .run_verify_tx(entry.clone()) + .service + .run_verify_tx(entry.clone(), &mut self.command_rx) .await .expect("run_verify_tx failed"); - match res { - Ok(completed) => { - self.service - .after_process(entry.tx, entry.remote, &snapshot, &Ok(completed)) - .await; - } - Err(e) => { - self.service - .after_process(entry.tx, entry.remote, &snapshot, &Err(e)) - .await; - } - } - } - - async fn run_verify_tx( - &mut self, - entry: Entry, - ) -> Option<(Result, Arc)> { - let Entry { tx, remote } = entry; - let tx_hash = tx.hash(); - - let (ret, snapshot) = self.service.pre_check(&tx).await; - let (tip_hash, rtx, status, fee, tx_size) = try_or_return_with_snapshot!(ret, snapshot); - - let cached = self.service.fetch_tx_verify_cache(&tx_hash).await; - - let tip_header = snapshot.tip_header(); - let consensus = snapshot.cloned_consensus(); - - let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); - - if let Some(ref completed) = cached { - let ret = TimeRelativeTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - snapshot.as_data_loader(), - Arc::clone(&tx_env), - ) - .verify() - .map(|_| *completed) - .map_err(Reject::Verification); - let completed = try_or_return_with_snapshot!(ret, snapshot); - - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, snapshot) = self.service.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, snapshot); - return Some((Ok(completed), snapshot)); - } - - let cloned_snapshot = Arc::clone(&snapshot); - let data_loader = cloned_snapshot.as_data_loader(); - let ret = ContextualWithoutScriptTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - data_loader.clone(), - Arc::clone(&tx_env), - ) - .verify() - .and_then(|result| { - DaoScriptSizeVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - data_loader.clone(), - ) - .verify()?; - Ok(result) - }) - .map_err(Reject::Verification); - let fee = try_or_return_with_snapshot!(ret, snapshot); - - let max_cycles = if let Some((declared_cycle, _peer)) = remote { - declared_cycle - } else { - consensus.max_block_cycles() - }; - - let ret = self - .loop_resume( - Arc::clone(&rtx), - data_loader, - max_cycles, - Arc::clone(&consensus), - Arc::clone(&tx_env), - ) + self.service + .after_process(entry.tx, entry.remote, &snapshot, &res) .await; - let state = try_or_return_with_snapshot!(ret, snapshot); - - let completed: Completed = match state { - VerifyResult::Completed(cycles) => Completed { cycles, fee }, - _ => { - panic!("not expected"); - } - }; - if let Some((declared_cycle, _peer)) = remote { - if declared_cycle != completed.cycles { - return Some(( - Err(Reject::DeclaredWrongCycles( - declared_cycle, - completed.cycles, - )), - snapshot, - )); - } - } - // verify passed - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, snapshot) = self.service.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, snapshot); - - self.service.notify_block_assembler(status).await; - - let mut guard = self.service.txs_verify_cache.write().await; - guard.put(tx_hash, completed); - - Some((Ok(completed), snapshot)) - } - - async fn loop_resume< - DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, - >( - &mut self, - rtx: Arc, - data_loader: DL, - max_cycles: Cycle, - consensus: Arc, - tx_env: Arc, - ) -> Result { - let script_verifier = ScriptVerifier::new(rtx, data_loader, consensus, tx_env); - let res = script_verifier - .resumable_verify_with_signal(max_cycles, &mut self.command_rx) - .await - .map_err(Reject::Verification)?; - Ok(res) } } From f22f40776f6920cbe12c95e037f9439d658c53b0 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 18 Jan 2024 11:01:04 +0800 Subject: [PATCH 037/294] add comments and more cleanup --- script/Cargo.toml | 2 +- script/src/syscalls/spawn.rs | 3 +++ script/src/verify.rs | 28 ++++++++------------- tx-pool/src/component/verify_queue.rs | 12 ++++----- tx-pool/src/process.rs | 32 ++++++++++++------------ tx-pool/src/verify_mgr.rs | 6 +++++ verification/src/transaction_verifier.rs | 7 ------ 7 files changed, 42 insertions(+), 48 deletions(-) diff --git a/script/Cargo.toml b/script/Cargo.toml index d1b3f451e1..f2481f5f0f 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -28,7 +28,7 @@ ckb-logger = { path = "../util/logger", version = "= 0.114.0-pre", optional = tr serde = { version = "1.0", features = ["derive"] } ckb-error = { path = "../error", version = "= 0.114.0-pre" } ckb-chain-spec = { path = "../spec", version = "= 0.114.0-pre" } -tokio = { version = "1.35.0", features = ["sync", "rt-multi-thread"] } +tokio = { version = "1.35.0", features = ["rt-multi-thread"] } [dev-dependencies] proptest = "1.0" diff --git a/script/src/syscalls/spawn.rs b/script/src/syscalls/spawn.rs index 15301bc199..9b7fee0298 100644 --- a/script/src/syscalls/spawn.rs +++ b/script/src/syscalls/spawn.rs @@ -235,6 +235,9 @@ where Ok(true) } Err(err) => { + // `CyclesExceeded` for old version snapshot + // `Pause` for new version suspend with pause signal + // Maybe we need to cleanup in future if matches!(err, VMError::Pause | VMError::CyclesExceeded) { let mut context = self.context.lock().map_err(|e| { VMError::Unexpected(format!("Failed to acquire lock: {}", e)) diff --git a/script/src/verify.rs b/script/src/verify.rs index b4e3cb558c..cc8dee5bde 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -693,8 +693,9 @@ impl { - // FIXME: we need to cleanup this later, state will not contain snapshot - panic!("unexpect suspend in resumable_verify_with_signal"); + // FIXME(yukang): we need to cleanup this later, state will not contain snapshot + unreachable!("unexpect suspend in resumable_verify_with_signal"); } Err(e) => { #[cfg(feature = "logging")] @@ -1051,10 +1052,7 @@ impl Ok(ChunkState::Completed(cycles)), - Err(e) => Err(e), - } + verifier.verify().map(ChunkState::Completed) } else { self.chunk_run_with_signal(group, max_cycles, command_rx) .await @@ -1200,7 +1198,7 @@ impl, context: Arc>, signal: &mut Receiver, @@ -1435,11 +1433,7 @@ async fn run_vms_with_signal( exit_code ))}, (Err(err), _) => { - let map_vm_internal_error = |error: VMInternalError| match error { - VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), - _ => ScriptError::VMInternalError(error), - }; - return Err(map_vm_internal_error(err)); + return Err(ScriptError::VMInternalError(err)); } } @@ -1456,13 +1450,13 @@ async fn run_vms_child( context: Arc>, ) { let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); - child_recv.mark_changed(); // mark changed to make sure we can receive initial command // and start to run immediately + child_recv.mark_changed(); loop { select! { _ = child_recv.changed() => { - match child_recv.borrow().to_owned() { + match *child_recv.borrow() { ChunkCommand::Stop => { let exit = (Err(ckb_vm::Error::Unexpected("stopped".to_string())), cycles); let _ = finished_send.send(exit); diff --git a/tx-pool/src/component/verify_queue.rs b/tx-pool/src/component/verify_queue.rs index 04eb0e782b..1da2add87f 100644 --- a/tx-pool/src/component/verify_queue.rs +++ b/tx-pool/src/component/verify_queue.rs @@ -3,6 +3,7 @@ extern crate rustc_hash; extern crate slab; use ckb_network::PeerIndex; +use ckb_systemtime::unix_time_as_millis; use ckb_types::{ core::{tx_pool::Reject, Cycle, TransactionView}, packed::ProposalShortId, @@ -14,7 +15,7 @@ use tokio::sync::watch; const DEFAULT_MAX_VERIFY_TRANSACTIONS: usize = 100; const SHRINK_THRESHOLD: usize = 100; -/// The verify queue is a priority queue of transactions to verify. +/// The verify queue Entry to verify. #[derive(Debug, Clone, Eq)] pub struct Entry { pub(crate) tx: TransactionView, @@ -71,7 +72,7 @@ impl VerifyQueue { /// Returns true if the queue is full. pub fn is_full(&self) -> bool { - self.len() > DEFAULT_MAX_VERIFY_TRANSACTIONS + self.len() >= DEFAULT_MAX_VERIFY_TRANSACTIONS } /// Returns true if the queue contains a tx with the specified id. @@ -130,16 +131,13 @@ impl VerifyQueue { } if self.is_full() { return Err(Reject::Full(format!( - "chunk is full, tx_hash: {:#x}", + "chunk is full, failed to add tx: {:#x}", tx.hash() ))); } self.inner.insert(VerifyEntry { id: tx.proposal_short_id(), - added_time: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("timestamp") - .as_millis() as u64, + added_time: unix_time_as_millis(), inner: Entry { tx, remote }, }); self.queue_tx.send(self.len()).unwrap(); diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 078bf74e75..dcba356d0c 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -189,16 +189,16 @@ impl TxPoolService { } } + pub(crate) async fn verify_queue_contains(&self, tx: &TransactionView) -> bool { + let queue = self.verify_queue.read().await; + queue.contains_key(&tx.proposal_short_id()) + } + pub(crate) async fn orphan_contains(&self, tx: &TransactionView) -> bool { let orphan = self.orphan.read().await; orphan.contains_key(&tx.proposal_short_id()) } - pub(crate) async fn chunk_contains(&self, tx: &TransactionView) -> bool { - let chunk = self.verify_queue.read().await; - chunk.contains_key(&tx.proposal_short_id()) - } - pub(crate) async fn with_tx_pool_read_lock) -> U>( &self, mut f: F, @@ -302,7 +302,7 @@ impl TxPoolService { return Err(Reject::Duplicated(tx.hash())); } - if self.chunk_contains(&tx).await { + if self.verify_queue_contains(&tx).await { return Err(Reject::Duplicated(tx.hash())); } @@ -334,7 +334,7 @@ impl TxPoolService { // non contextual verify first self.non_contextual_verify(&tx, remote)?; - if self.chunk_contains(&tx).await || self.orphan_contains(&tx).await { + if self.verify_queue_contains(&tx).await || self.orphan_contains(&tx).await { return Err(Reject::Duplicated(tx.hash())); } @@ -365,8 +365,8 @@ impl TxPoolService { pub(crate) async fn remove_tx(&self, tx_hash: Byte32) -> bool { let id = ProposalShortId::from_tx_hash(&tx_hash); { - let mut chunk = self.verify_queue.write().await; - if chunk.remove_tx(&id).is_some() { + let mut queue = self.verify_queue.write().await; + if queue.remove_tx(&id).is_some() { return true; } } @@ -527,7 +527,7 @@ impl TxPoolService { for orphan in orphans.into_iter() { if orphan.cycle > self.tx_pool_config.max_tx_verify_cycles { debug!( - "process_orphan {} added to chunk; find previous from {}", + "process_orphan {} added to verify queue; find previous from {}", orphan.tx.hash(), tx.hash(), ); @@ -673,7 +673,7 @@ impl TxPoolService { .enqueue_suspended_tx(rtx.transaction.clone(), remote) .await; try_or_return_with_snapshot!(ret, snapshot); - eprintln!("added to queue here: {:?}", tx.proposal_short_id()); + error!("added to verify queue: {:?}", tx.proposal_short_id()); return Some((Ok(ProcessResult::Suspended), snapshot)); } None => { @@ -796,7 +796,7 @@ impl TxPoolService { let completed: Completed = match state { VerifyResult::Completed(cycles) => Completed { cycles, fee }, _ => { - panic!("not expected"); + unreachable!("unexpected Suspend in run_verify_tx"); } }; if let Some((declared_cycle, _peer)) = remote { @@ -828,8 +828,8 @@ impl TxPoolService { tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, ) -> Result { - let mut chunk = self.verify_queue.write().await; - chunk.add_tx(tx, remote) + let mut queue = self.verify_queue.write().await; + queue.add_tx(tx, remote) } pub(crate) async fn _process_tx( @@ -1015,8 +1015,8 @@ impl TxPoolService { self.remove_orphan_txs_by_attach(&attached).await; { - let mut chunk = self.verify_queue.write().await; - chunk.remove_txs(attached.iter().map(|tx| tx.proposal_short_id())); + let mut queue = self.verify_queue.write().await; + queue.remove_txs(attached.iter().map(|tx| tx.proposal_short_id())); } } diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 5188e8c50a..b5ac11527d 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -168,6 +168,12 @@ impl VerifyMgr { } } } + if let Some(jh) = self.join_handles.take() { + for h in jh { + h.await.expect("Worker thread panic"); + } + } + info!("TxPool verify_mgr service exited"); } pub async fn run(&mut self) { diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 0fd8547f8d..1cf56f12c7 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -5,13 +5,7 @@ use ckb_chain_spec::consensus::Consensus; use ckb_dao::DaoCalculator; use ckb_dao_utils::DaoError; use ckb_error::Error; -<<<<<<< HEAD use ckb_script::{TransactionScriptsVerifier, TransactionSnapshot, VerifyResult}; -======= -use ckb_script::{ - ChunkCommand, TransactionScriptsVerifier, TransactionSnapshot, TransactionState, VerifyResult, -}; ->>>>>>> 6a1178ada (begin to fix dataloader issue) use ckb_traits::{ CellDataProvider, EpochProvider, ExtensionProvider, HeaderFieldsProvider, HeaderProvider, }; @@ -177,7 +171,6 @@ where .script .resumable_verify_with_signal(limit_cycles, &mut command_rx) .await?; - eprintln!("resumable_verify_with_signal ret: {:?}", ret); Ok((ret, fee)) } From e0dabff3437d012a27596035f1610b7fa002b3a3 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 18 Jan 2024 17:08:06 +0800 Subject: [PATCH 038/294] do not use select! in verify --- script/src/verify.rs | 144 ++++++++++++++++++++++--------------------- 1 file changed, 74 insertions(+), 70 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index cc8dee5bde..d783543d84 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -40,12 +40,9 @@ use ckb_vm::{ use std::collections::{BTreeMap, HashMap}; use std::sync::RwLock; use std::sync::{Arc, Mutex}; -use tokio::{ - select, - sync::{ - mpsc, - watch::{self, Receiver}, - }, +use tokio::sync::{ + mpsc, + watch::{self, Receiver}, }; #[cfg(test)] @@ -1409,7 +1406,7 @@ async fn run_vms_with_signal( loop { tokio::select! { - _ = signal.changed() => { + Ok(_) = signal.changed() => { let command = signal.borrow().to_owned(); match command { ChunkCommand::Suspend => { @@ -1454,74 +1451,81 @@ async fn run_vms_child( // and start to run immediately child_recv.mark_changed(); loop { - select! { - _ = child_recv.changed() => { - match *child_recv.borrow() { - ChunkCommand::Stop => { - let exit = (Err(ckb_vm::Error::Unexpected("stopped".to_string())), cycles); - let _ = finished_send.send(exit); - return; + let _ = child_recv.changed().await; + match *child_recv.borrow() { + ChunkCommand::Stop => { + let exit = ( + Err(ckb_vm::Error::Unexpected("stopped".to_string())), + cycles, + ); + let _ = finished_send.send(exit); + return; + } + ChunkCommand::Suspend => { + continue; + } + ChunkCommand::Resume => {} + } + if machines.is_empty() { + finished_send + .send((Ok(exit_code), cycles)) + .expect("send finished"); + return; + } + + while let Some(mut machine) = machines.pop() { + if let Some(callee_spawn_data) = &spawn_data { + update_caller_machine( + &mut machine.machine_mut().machine, + exit_code, + cycles, + callee_spawn_data, + ) + .unwrap(); + } + + let res = machine.run(); + match res { + Ok(code) => { + exit_code = code; + cycles = machine.cycles(); + if let ResumableMachine::Spawn(_, data) = machine { + spawn_data = Some(data); + } else { + spawn_data = None; } - ChunkCommand::Suspend => { - continue; + if machines.is_empty() { + finished_send.send((Ok(exit_code), cycles)).unwrap(); + return; } - ChunkCommand::Resume => {} } - if machines.is_empty() { - finished_send.send((Ok(exit_code), cycles)).unwrap(); - return; - } - - while let Some(mut machine) = machines.pop() { - if let Some(callee_spawn_data) = &spawn_data { - update_caller_machine( - &mut machine.machine_mut().machine, - exit_code, - cycles, - callee_spawn_data, - ) - .unwrap(); - } - - let res = machine.run(); - match res { - Ok(code) => { - exit_code = code; - cycles = machine.cycles(); - if let ResumableMachine::Spawn(_, data) = machine { - spawn_data = Some(data); - } else { - spawn_data = None; - } - if machines.is_empty() { - finished_send.send((Ok(exit_code), cycles)).unwrap(); - return; - } - } - Err(VMInternalError::Pause) => { - let mut new_suspended_machines: Vec<_> = { - let mut context = context.lock().map_err(|e| { - ScriptError::Other(format!("Failed to acquire lock: {}", e)) - }).unwrap(); - context.suspended_machines.drain(..).collect() - }; - // The inner most machine lives at the top of the vector, - // reverse the list for natural order. - new_suspended_machines.reverse(); - machines.push(machine); - machines.append(&mut new_suspended_machines); - // break to wait for Resume command to begin next loop iteration - break; - } - _ => { - // other error happened here, for example CyclesExceeded, - // we need to return as verification failed - finished_send.send((res, machine.cycles())).unwrap(); - return; - } + Err(VMInternalError::Pause) => { + let mut new_suspended_machines: Vec<_> = { + let mut context = context + .lock() + .map_err(|e| { + ScriptError::Other(format!("Failed to acquire lock: {}", e)) + }) + .unwrap(); + context.suspended_machines.drain(..).collect() }; + // The inner most machine lives at the top of the vector, + // reverse the list for natural order. + new_suspended_machines.reverse(); + machines.push(machine); + machines.append(&mut new_suspended_machines); + // break to wait for Resume command to begin next loop iteration + break; } - } + _ => { + // other error happened here, for example CyclesExceeded, + // we need to return as verification failed + finished_send + .send((res, machine.cycles())) + .expect("send finished"); + return; + } + }; } } } From e4db477e8128a055a093e205ac21e97d24e820c8 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 18 Jan 2024 18:15:50 +0800 Subject: [PATCH 039/294] use oneshot channel in verify --- script/src/verify.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index d783543d84..c5bf904389 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -41,7 +41,7 @@ use std::collections::{BTreeMap, HashMap}; use std::sync::RwLock; use std::sync::{Arc, Mutex}; use tokio::sync::{ - mpsc, + oneshot, watch::{self, Receiver}, }; @@ -1392,17 +1392,14 @@ async fn run_vms_with_signal( } let mut pause = machines[0].pause(); - let (finished_send, mut finished_recv) = - mpsc::unbounded_channel::<(Result, u64)>(); + let (finish_tx, mut finish_rx) = oneshot::channel::<(Result, u64)>(); // send initial `Resume` command to child // it's maybe useful to set initial command to `signal.borrow().to_owned()` // so that we can control the initial state of child, which is useful for testing purpose - let (child_sender, child_recv) = watch::channel(ChunkCommand::Resume); + let (child_tx, child_rx) = watch::channel(ChunkCommand::Resume); let jh = - tokio::spawn( - async move { run_vms_child(machines, child_recv, finished_send, context).await }, - ); + tokio::spawn(async move { run_vms_child(machines, child_rx, finish_tx, context).await }); loop { tokio::select! { @@ -1414,11 +1411,11 @@ async fn run_vms_with_signal( } ChunkCommand::Resume | ChunkCommand::Stop => { pause.free(); - let _res = child_sender.send(command); + let _res = child_tx.send(command); } } } - Some(res) = finished_recv.recv() => { + Ok(res) = &mut finish_rx => { let _ = jh.await; match res { (Ok(0), cycles) => { @@ -1442,23 +1439,23 @@ async fn run_vms_with_signal( async fn run_vms_child( mut machines: Vec, - mut child_recv: watch::Receiver, - finished_send: mpsc::UnboundedSender<(Result, u64)>, + mut child_rx: watch::Receiver, + finish_tx: oneshot::Sender<(Result, u64)>, context: Arc>, ) { let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); // mark changed to make sure we can receive initial command // and start to run immediately - child_recv.mark_changed(); + child_rx.mark_changed(); loop { - let _ = child_recv.changed().await; - match *child_recv.borrow() { + let _ = child_rx.changed().await; + match *child_rx.borrow() { ChunkCommand::Stop => { let exit = ( Err(ckb_vm::Error::Unexpected("stopped".to_string())), cycles, ); - let _ = finished_send.send(exit); + let _ = finish_tx.send(exit); return; } ChunkCommand::Suspend => { @@ -1467,7 +1464,7 @@ async fn run_vms_child( ChunkCommand::Resume => {} } if machines.is_empty() { - finished_send + finish_tx .send((Ok(exit_code), cycles)) .expect("send finished"); return; @@ -1495,7 +1492,7 @@ async fn run_vms_child( spawn_data = None; } if machines.is_empty() { - finished_send.send((Ok(exit_code), cycles)).unwrap(); + finish_tx.send((Ok(exit_code), cycles)).unwrap(); return; } } @@ -1520,7 +1517,7 @@ async fn run_vms_child( _ => { // other error happened here, for example CyclesExceeded, // we need to return as verification failed - finished_send + finish_tx .send((res, machine.cycles())) .expect("send finished"); return; From d84d36eb43292d87ff50c8ba052462890e1af11d Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 18 Jan 2024 18:30:03 +0800 Subject: [PATCH 040/294] remove uncessary clone in verify_queue --- tx-pool/src/component/tests/chunk.rs | 9 +++++++++ tx-pool/src/component/verify_queue.rs | 9 ++++----- tx-pool/src/verify_mgr.rs | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tx-pool/src/component/tests/chunk.rs b/tx-pool/src/component/tests/chunk.rs index c2ea1a0f1c..fcf0c37395 100644 --- a/tx-pool/src/component/tests/chunk.rs +++ b/tx-pool/src/component/tests/chunk.rs @@ -57,6 +57,15 @@ async fn verify_queue_basic() { let counts = count.await.unwrap(); assert_eq!(counts, vec![1, 1, 1, 2]); + let cur = queue.pop_first(); + assert_eq!(cur.unwrap().tx, tx); + + assert!(!queue.is_empty()); + let cur = queue.pop_first(); + assert_eq!(cur.unwrap().tx, tx2); + + assert!(queue.is_empty()); + queue.clear(); assert!(!queue.contains_key(&id)); } diff --git a/tx-pool/src/component/verify_queue.rs b/tx-pool/src/component/verify_queue.rs index 1da2add87f..d0192a7e27 100644 --- a/tx-pool/src/component/verify_queue.rs +++ b/tx-pool/src/component/verify_queue.rs @@ -103,20 +103,19 @@ impl VerifyQueue { /// Returns the first entry in the queue and remove it pub fn pop_first(&mut self) -> Option { - if let Some(entry) = self.get_first() { - self.remove_tx(&entry.tx.proposal_short_id()); - Some(entry) + if let Some(short_id) = self.peak_first() { + self.remove_tx(&short_id) } else { None } } /// Returns the first entry in the queue - pub fn get_first(&self) -> Option { + pub fn peak_first(&self) -> Option { self.inner .iter_by_added_time() .next() - .map(|entry| entry.inner.clone()) + .map(|entry| entry.inner.tx.proposal_short_id()) } /// If the queue did not have this tx present, true is returned. diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index b5ac11527d..18aabc11d7 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -77,7 +77,7 @@ impl Worker { return; } - if self.tasks.read().await.get_first().is_none() { + if self.tasks.read().await.peak_first().is_none() { return; } // pick a entry to run verify From f69f9274cc633233b568eb0507f23b387ad90308 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 18 Jan 2024 21:28:01 +0800 Subject: [PATCH 041/294] fix script verification stop error --- script/src/verify.rs | 9 ++------- .../src/verify/tests/ckb_latest/features_since_v2023.rs | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index c5bf904389..1dee43338d 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1451,10 +1451,7 @@ async fn run_vms_child( let _ = child_rx.changed().await; match *child_rx.borrow() { ChunkCommand::Stop => { - let exit = ( - Err(ckb_vm::Error::Unexpected("stopped".to_string())), - cycles, - ); + let exit = (Err(ckb_vm::Error::External("stopped".into())), cycles); let _ = finish_tx.send(exit); return; } @@ -1517,9 +1514,7 @@ async fn run_vms_child( _ => { // other error happened here, for example CyclesExceeded, // we need to return as verification failed - finish_tx - .send((res, machine.cycles())) - .expect("send finished"); + finish_tx.send((res, machine.cycles())).expect("send error"); return; } }; diff --git a/script/src/verify/tests/ckb_latest/features_since_v2023.rs b/script/src/verify/tests/ckb_latest/features_since_v2023.rs index 79765d03b7..85b9b08ad1 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2023.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2023.rs @@ -780,7 +780,7 @@ async fn check_spawn_suspend_shutdown() { .await; assert!(res.is_err()); let err = res.unwrap_err().to_string(); - assert!(err.contains("VM Internal Error: Unexpected(\"stopped\")")); + assert!(err.contains("VM Internal Error: External(\"stopped\")")); } #[test] From 1af1190ed7200f9716d2c8935b1ffd3afd200fe8 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 18 Jan 2024 23:20:57 +0800 Subject: [PATCH 042/294] enqueue Notify transactions directly --- tx-pool/src/component/verify_queue.rs | 4 ++-- tx-pool/src/process.rs | 14 +++++++++++--- tx-pool/src/service.rs | 6 +++--- tx-pool/src/verify_mgr.rs | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tx-pool/src/component/verify_queue.rs b/tx-pool/src/component/verify_queue.rs index d0192a7e27..34cd4c880f 100644 --- a/tx-pool/src/component/verify_queue.rs +++ b/tx-pool/src/component/verify_queue.rs @@ -103,7 +103,7 @@ impl VerifyQueue { /// Returns the first entry in the queue and remove it pub fn pop_first(&mut self) -> Option { - if let Some(short_id) = self.peak_first() { + if let Some(short_id) = self.peek() { self.remove_tx(&short_id) } else { None @@ -111,7 +111,7 @@ impl VerifyQueue { } /// Returns the first entry in the queue - pub fn peak_first(&self) -> Option { + pub fn peek(&self) -> Option { self.inner .iter_by_added_time() .next() diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index dcba356d0c..91c84083a8 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -293,6 +293,7 @@ impl TxPoolService { &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, + add_verify_queue: bool, ) -> Result<(), Reject> { // non contextual verify first self.non_contextual_verify(&tx, remote)?; @@ -306,7 +307,10 @@ impl TxPoolService { return Err(Reject::Duplicated(tx.hash())); } - if let Some((ret, snapshot)) = self._resumeble_process_tx(tx.clone(), remote).await { + if let Some((ret, snapshot)) = self + ._resumeble_process_tx(tx.clone(), remote, add_verify_queue) + .await + { match ret { Ok(processed) => { if let ProcessResult::Completed(completed) = processed { @@ -634,6 +638,7 @@ impl TxPoolService { &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, + add_verify_queue: bool, ) -> Option<(Result, Arc)> { let tx_hash = tx.hash(); @@ -667,8 +672,10 @@ impl TxPoolService { try_or_return_with_snapshot!(ret, snapshot); completed } - None if remote.is_some() => { - // for remote transaction with large decleard cycles, we enqueue it to verify queue + None if add_verify_queue => { + // for remote transaction with decleard cycles, we enqueue it to verify queue directly + // notified transaction now don't have decleard cycles, we may need to fix it in future, + // now we also enqueue it to verify queue directly let ret = self .enqueue_suspended_tx(rtx.transaction.clone(), remote) .await; @@ -678,6 +685,7 @@ impl TxPoolService { } None => { // for local transaction, we verify it directly with a max cycles limit + assert!(remote.is_none()); let ret = { block_in_place(|| { let cycle_limit = snapshot.cloned_consensus().max_block_cycles(); diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index 7b87ff9e7a..26a9536226 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -685,7 +685,7 @@ async fn process(mut service: TxPoolService, message: Message) { responder, arguments: tx, }) => { - let result = service.resumeble_process_tx(tx, None).await; + let result = service.resumeble_process_tx(tx, None, false).await; if let Err(e) = responder.send(result) { error!("Responder sending submit_tx result failed {:?}", e); }; @@ -705,7 +705,7 @@ async fn process(mut service: TxPoolService, message: Message) { }) => { if declared_cycles > service.tx_pool_config.max_tx_verify_cycles { let _result = service - .resumeble_process_tx(tx, Some((declared_cycles, peer))) + .resumeble_process_tx(tx, Some((declared_cycles, peer)), true) .await; if let Err(e) = responder.send(()) { error!("Responder sending submit_tx result failed {:?}", e); @@ -719,7 +719,7 @@ async fn process(mut service: TxPoolService, message: Message) { } Message::NotifyTxs(Notify { arguments: txs }) => { for tx in txs { - let _ret = service.resumeble_process_tx(tx, None).await; + let _ret = service.resumeble_process_tx(tx, None, true).await; } } Message::FreshProposalsFilter(Request { diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 18aabc11d7..0be480d9a6 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -77,7 +77,7 @@ impl Worker { return; } - if self.tasks.read().await.peak_first().is_none() { + if self.tasks.read().await.peek().is_none() { return; } // pick a entry to run verify From 355f2d1daf007cbf6b9173572e8ebbb0f232d3be Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 19 Jan 2024 00:27:47 +0800 Subject: [PATCH 043/294] refactor verify queue channel --- tx-pool/src/component/tests/chunk.rs | 4 ++-- tx-pool/src/component/verify_queue.rs | 13 +++++++++++-- tx-pool/src/service.rs | 12 +++--------- tx-pool/src/verify_mgr.rs | 24 +++++++++--------------- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/tx-pool/src/component/tests/chunk.rs b/tx-pool/src/component/tests/chunk.rs index fcf0c37395..2bba8b618f 100644 --- a/tx-pool/src/component/tests/chunk.rs +++ b/tx-pool/src/component/tests/chunk.rs @@ -15,9 +15,9 @@ async fn verify_queue_basic() { let tx2 = build_tx(vec![(&tx.hash(), 0)], 1); let id = tx.proposal_short_id(); - let (queue_tx, mut queue_rx) = watch::channel(0_usize); let (exit_tx, mut exit_rx) = watch::channel(()); - let mut queue = VerifyQueue::new(queue_tx); + let mut queue = VerifyQueue::new(); + let mut queue_rx = queue.subscribe(); let count = tokio::spawn(async move { let mut counts = vec![]; loop { diff --git a/tx-pool/src/component/verify_queue.rs b/tx-pool/src/component/verify_queue.rs index 34cd4c880f..f72d25989d 100644 --- a/tx-pool/src/component/verify_queue.rs +++ b/tx-pool/src/component/verify_queue.rs @@ -46,16 +46,20 @@ pub struct VerifyEntry { pub struct VerifyQueue { /// inner tx entry inner: MultiIndexVerifyEntryMap, - /// used to notify the tx-pool to update the txs count + /// when queue is changed, notify the tx-pool to update the txs count queue_tx: watch::Sender, + /// subscribe this channel to get the txs count in the queue + queue_rx: watch::Receiver, } impl VerifyQueue { /// Create a new VerifyQueue - pub(crate) fn new(queue_tx: watch::Sender) -> Self { + pub(crate) fn new() -> Self { + let (queue_tx, queue_rx) = watch::channel(0_usize); VerifyQueue { inner: MultiIndexVerifyEntryMap::default(), queue_tx, + queue_rx, } } @@ -85,6 +89,11 @@ impl VerifyQueue { shrink_to_fit!(self.inner, SHRINK_THRESHOLD); } + /// get a queue_rx to subscribe the txs count in the queue + pub fn subscribe(&self) -> watch::Receiver { + self.queue_rx.clone() + } + /// Remove a tx from the queue pub fn remove_tx(&mut self, id: &ProposalShortId) -> Option { self.inner.remove_by_id(id).map(|e| { diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index 26a9536226..ae6cb1c726 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -470,8 +470,7 @@ impl TxPoolServiceBuilder { } }; - let (queue_tx, queue_rx) = watch::channel(0_usize); - let verify_queue = Arc::new(RwLock::new(VerifyQueue::new(queue_tx))); + let verify_queue = Arc::new(RwLock::new(VerifyQueue::new())); let (block_assembler_sender, mut block_assembler_receiver) = self.block_assembler_channel; let service = TxPoolService { tx_pool_config: Arc::new(tx_pool.config.clone()), @@ -489,13 +488,8 @@ impl TxPoolServiceBuilder { after_delay: Arc::new(AtomicBool::new(after_delay_window)), }; - let mut verify_mgr = VerifyMgr::new( - service.clone(), - self.chunk_rx, - self.signal_receiver.clone(), - verify_queue, - queue_rx, - ); + let mut verify_mgr = + VerifyMgr::new(service.clone(), self.chunk_rx, self.signal_receiver.clone()); self.handle.spawn(async move { verify_mgr.run().await }); let mut receiver = self.receiver; diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 0be480d9a6..0b20d4d028 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -12,7 +12,6 @@ use tokio::task::JoinHandle; struct Worker { tasks: Arc>, command_rx: watch::Receiver, - queue_rx: watch::Receiver, service: TxPoolService, exit_signal: CancellationToken, } @@ -22,7 +21,6 @@ impl Clone for Worker { Self { tasks: Arc::clone(&self.tasks), command_rx: self.command_rx.clone(), - queue_rx: self.queue_rx.clone(), exit_signal: self.exit_signal.clone(), service: self.service.clone(), } @@ -34,19 +32,18 @@ impl Worker { service: TxPoolService, tasks: Arc>, command_rx: watch::Receiver, - queue_rx: watch::Receiver, exit_signal: CancellationToken, ) -> Self { Worker { service, tasks, command_rx, - queue_rx, exit_signal, } } - pub fn start(mut self) -> JoinHandle<()> { + pub async fn start(mut self) -> JoinHandle<()> { + let mut queue_rx = self.tasks.read().await.subscribe(); tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_millis(500)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); @@ -55,10 +52,10 @@ impl Worker { _ = self.exit_signal.cancelled() => { break; } - _ = self.queue_rx.changed() => { + _ = self.command_rx.changed() => { true } - _ = self.command_rx.changed() => { + _ = queue_rx.changed() => { true } _ = interval.tick() => { @@ -73,7 +70,7 @@ impl Worker { } async fn process_inner(&mut self) { - if self.command_rx.borrow().to_owned() == ChunkCommand::Suspend { + if self.command_rx.borrow().to_owned() != ChunkCommand::Resume { return; } @@ -108,14 +105,12 @@ pub(crate) struct VerifyMgr { impl VerifyMgr { pub fn new( service: TxPoolService, - chunk_rx: watch::Receiver, + command_rx: watch::Receiver, signal_exit: CancellationToken, - verify_queue: Arc>, - queue_rx: watch::Receiver, ) -> Self { let workers: Vec<_> = (0..num_cpus::get()) .map({ - let tasks = Arc::clone(&verify_queue); + let tasks = Arc::clone(&service.verify_queue); let signal_exit = signal_exit.clone(); move |_| { let (child_tx, child_rx) = watch::channel(ChunkCommand::Resume); @@ -125,7 +120,6 @@ impl VerifyMgr { service.clone(), Arc::clone(&tasks), child_rx, - queue_rx.clone(), signal_exit.clone(), ), ) @@ -136,7 +130,7 @@ impl VerifyMgr { workers, join_handles: None, signal_exit, - command_rx: chunk_rx, + command_rx, } } @@ -151,7 +145,7 @@ impl VerifyMgr { async fn start_loop(&mut self) { let mut join_handles = Vec::new(); for w in self.workers.iter_mut() { - let h = w.1.clone().start(); + let h = w.1.clone().start().await; join_handles.push(h); } self.join_handles.replace(join_handles); From 2f69785c9306105ce9cbe9cd2d35ccd03613907b Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 19 Jan 2024 00:45:24 +0800 Subject: [PATCH 044/294] add comments for verify mgr --- tx-pool/src/verify_mgr.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 0b20d4d028..9ac8d5fc4e 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -43,6 +43,8 @@ impl Worker { } pub async fn start(mut self) -> JoinHandle<()> { + // use a channel to receive the queue change event makes sure the worker + // know immediately when the queue is changed, otherwise we may have a delay of `interval` let mut queue_rx = self.tasks.read().await.subscribe(); tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_millis(500)); @@ -53,7 +55,7 @@ impl Worker { break; } _ = self.command_rx.changed() => { - true + *self.command_rx.borrow() == ChunkCommand::Resume } _ = queue_rx.changed() => { true @@ -70,10 +72,6 @@ impl Worker { } async fn process_inner(&mut self) { - if self.command_rx.borrow().to_owned() != ChunkCommand::Resume { - return; - } - if self.tasks.read().await.peek().is_none() { return; } From 75c1cacb24547f3c670facceb7aae6596422a451 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 20 Jan 2024 13:05:23 +0800 Subject: [PATCH 045/294] use notify in verify queue --- tx-pool/src/component/tests/chunk.rs | 13 ++++++------- tx-pool/src/component/verify_queue.rs | 19 ++++++++----------- tx-pool/src/verify_mgr.rs | 12 ++++++------ 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/tx-pool/src/component/tests/chunk.rs b/tx-pool/src/component/tests/chunk.rs index 2bba8b618f..f91821f4b5 100644 --- a/tx-pool/src/component/tests/chunk.rs +++ b/tx-pool/src/component/tests/chunk.rs @@ -17,21 +17,20 @@ async fn verify_queue_basic() { let id = tx.proposal_short_id(); let (exit_tx, mut exit_rx) = watch::channel(()); let mut queue = VerifyQueue::new(); - let mut queue_rx = queue.subscribe(); + let queue_rx = queue.subscribe(); let count = tokio::spawn(async move { - let mut counts = vec![]; + let mut count = 0; loop { select! { - _ = queue_rx.changed() => { - let value = queue_rx.borrow().to_owned(); - counts.push(value); + _ = queue_rx.notified() => { + count += 1; } _ = exit_rx.changed() => { break; } } } - counts + count }); assert!(queue.add_tx(tx.clone(), None).unwrap()); @@ -55,7 +54,7 @@ async fn verify_queue_basic() { exit_tx.send(()).unwrap(); let counts = count.await.unwrap(); - assert_eq!(counts, vec![1, 1, 1, 2]); + assert_eq!(counts, 4); let cur = queue.pop_first(); assert_eq!(cur.unwrap().tx, tx); diff --git a/tx-pool/src/component/verify_queue.rs b/tx-pool/src/component/verify_queue.rs index f72d25989d..34bb6e8bfd 100644 --- a/tx-pool/src/component/verify_queue.rs +++ b/tx-pool/src/component/verify_queue.rs @@ -10,7 +10,8 @@ use ckb_types::{ }; use ckb_util::shrink_to_fit; use multi_index_map::MultiIndexMap; -use tokio::sync::watch; +use std::sync::Arc; +use tokio::sync::Notify; const DEFAULT_MAX_VERIFY_TRANSACTIONS: usize = 100; const SHRINK_THRESHOLD: usize = 100; @@ -46,20 +47,16 @@ pub struct VerifyEntry { pub struct VerifyQueue { /// inner tx entry inner: MultiIndexVerifyEntryMap, - /// when queue is changed, notify the tx-pool to update the txs count - queue_tx: watch::Sender, - /// subscribe this channel to get the txs count in the queue - queue_rx: watch::Receiver, + /// subscribe this notify to get be notified when there is item in the queue + ready_rx: Arc, } impl VerifyQueue { /// Create a new VerifyQueue pub(crate) fn new() -> Self { - let (queue_tx, queue_rx) = watch::channel(0_usize); VerifyQueue { inner: MultiIndexVerifyEntryMap::default(), - queue_tx, - queue_rx, + ready_rx: Arc::new(Notify::new()), } } @@ -90,8 +87,8 @@ impl VerifyQueue { } /// get a queue_rx to subscribe the txs count in the queue - pub fn subscribe(&self) -> watch::Receiver { - self.queue_rx.clone() + pub fn subscribe(&self) -> Arc { + Arc::clone(&self.ready_rx) } /// Remove a tx from the queue @@ -148,7 +145,7 @@ impl VerifyQueue { added_time: unix_time_as_millis(), inner: Entry { tx, remote }, }); - self.queue_tx.send(self.len()).unwrap(); + self.ready_rx.notify_one(); Ok(true) } diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 9ac8d5fc4e..47397d3567 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -42,11 +42,11 @@ impl Worker { } } - pub async fn start(mut self) -> JoinHandle<()> { - // use a channel to receive the queue change event makes sure the worker - // know immediately when the queue is changed, otherwise we may have a delay of `interval` - let mut queue_rx = self.tasks.read().await.subscribe(); + pub fn start(mut self) -> JoinHandle<()> { tokio::spawn(async move { + // use a notify to receive the queue change event makes sure the worker + // know immediately when the queue is changed, otherwise we may have a delay of `interval` + let queue_ready = self.tasks.read().await.subscribe(); let mut interval = tokio::time::interval(Duration::from_millis(500)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { @@ -57,7 +57,7 @@ impl Worker { _ = self.command_rx.changed() => { *self.command_rx.borrow() == ChunkCommand::Resume } - _ = queue_rx.changed() => { + _ = queue_ready.notified() => { true } _ = interval.tick() => { @@ -143,7 +143,7 @@ impl VerifyMgr { async fn start_loop(&mut self) { let mut join_handles = Vec::new(); for w in self.workers.iter_mut() { - let h = w.1.clone().start().await; + let h = w.1.clone().start(); join_handles.push(h); } self.join_handles.replace(join_handles); From adae970816a9640241dadb723a138958840c497a Mon Sep 17 00:00:00 2001 From: yukang Date: Tue, 23 Jan 2024 11:36:09 +0800 Subject: [PATCH 046/294] remove interval in worker --- tx-pool/src/verify_mgr.rs | 59 +++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 47397d3567..3f4f453e53 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -5,7 +5,6 @@ use ckb_logger::info; use ckb_script::ChunkCommand; use ckb_stop_handler::CancellationToken; use std::sync::Arc; -use std::time::Duration; use tokio::sync::{watch, RwLock}; use tokio::task::JoinHandle; @@ -14,6 +13,7 @@ struct Worker { command_rx: watch::Receiver, service: TxPoolService, exit_signal: CancellationToken, + status: ChunkCommand, } impl Clone for Worker { @@ -23,6 +23,7 @@ impl Clone for Worker { command_rx: self.command_rx.clone(), exit_signal: self.exit_signal.clone(), service: self.service.clone(), + status: self.status.clone(), } } } @@ -39,57 +40,55 @@ impl Worker { tasks, command_rx, exit_signal, + status: ChunkCommand::Resume, } } pub fn start(mut self) -> JoinHandle<()> { tokio::spawn(async move { - // use a notify to receive the queue change event makes sure the worker - // know immediately when the queue is changed, otherwise we may have a delay of `interval` let queue_ready = self.tasks.read().await.subscribe(); - let mut interval = tokio::time::interval(Duration::from_millis(500)); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { - let try_pick = tokio::select! { + tokio::select! { _ = self.exit_signal.cancelled() => { break; } _ = self.command_rx.changed() => { - *self.command_rx.borrow() == ChunkCommand::Resume + self.status = self.command_rx.borrow().to_owned(); + self.process_inner().await; } _ = queue_ready.notified() => { - true - } - _ = interval.tick() => { - true + self.process_inner().await; } }; - if try_pick { - self.process_inner().await; - } } }) } async fn process_inner(&mut self) { - if self.tasks.read().await.peek().is_none() { - return; - } - // pick a entry to run verify - let entry = match self.tasks.write().await.pop_first() { - Some(entry) => entry, - None => return, - }; + loop { + if self.status != ChunkCommand::Resume { + return; + } + // cheap query to check queue is not empty + if self.tasks.read().await.peek().is_none() { + return; + } + // pick a entry to run verify + let entry = match self.tasks.write().await.pop_first() { + Some(entry) => entry, + None => return, + }; - let (res, snapshot) = self - .service - .run_verify_tx(entry.clone(), &mut self.command_rx) - .await - .expect("run_verify_tx failed"); + let (res, snapshot) = self + .service + .run_verify_tx(entry.clone(), &mut self.command_rx) + .await + .expect("run_verify_tx failed"); - self.service - .after_process(entry.tx, entry.remote, &snapshot, &res) - .await; + self.service + .after_process(entry.tx, entry.remote, &snapshot, &res) + .await; + } } } From e580b46e80e8aa1bb71ccbad9a27f7c92de406d1 Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 26 Jan 2024 10:39:17 +0800 Subject: [PATCH 047/294] code refactor to remove ContextualWithoutScriptTransactionVerifier and run_verify_tx --- tx-pool/src/process.rs | 127 +++-------------------- tx-pool/src/util.rs | 21 +++- tx-pool/src/verify_mgr.rs | 8 +- verification/src/lib.rs | 4 +- verification/src/transaction_verifier.rs | 102 ++++-------------- 5 files changed, 67 insertions(+), 195 deletions(-) diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 91c84083a8..169da31fb7 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -2,7 +2,6 @@ use crate::callback::Callbacks; use crate::component::entry::TxEntry; use crate::component::orphan::Entry as OrphanEntry; use crate::component::pool_map::Status; -use crate::component::verify_queue::Entry; use crate::error::Reject; use crate::pool::TxPool; use crate::service::{BlockAssemblerMessage, TxPoolService, TxVerificationResult}; @@ -17,7 +16,7 @@ use ckb_jsonrpc_types::BlockTemplate; use ckb_logger::Level::Trace; use ckb_logger::{debug, error, info, log_enabled_target, trace_target}; use ckb_network::PeerIndex; -use ckb_script::{ChunkCommand, VerifyResult}; +use ckb_script::ChunkCommand; use ckb_snapshot::Snapshot; use ckb_store::data_loader_wrapper::AsDataLoader; use ckb_store::ChainStore; @@ -32,7 +31,6 @@ use ckb_verification::{ ContextualTransactionVerifier, DaoScriptSizeVerifier, TimeRelativeTransactionVerifier, TxVerifyEnv, }; -use ckb_verification::{ContextualWithoutScriptTransactionVerifier, ScriptVerifier}; use std::collections::HashSet; use std::collections::{HashMap, VecDeque}; use std::sync::Arc; @@ -342,7 +340,10 @@ impl TxPoolService { return Err(Reject::Duplicated(tx.hash())); } - if let Some((ret, snapshot)) = self._process_tx(tx.clone(), remote.map(|r| r.0)).await { + if let Some((ret, snapshot)) = self + ._process_tx(tx.clone(), remote.map(|r| r.0), None) + .await + { self.after_process(tx, remote, &snapshot, &ret).await; ret } else { @@ -540,7 +541,7 @@ impl TxPoolService { .await .expect("enqueue suspended tx"); } else if let Some((ret, snapshot)) = self - ._process_tx(orphan.tx.clone(), Some(orphan.cycle)) + ._process_tx(orphan.tx.clone(), Some(orphan.cycle), None) .await { match ret { @@ -730,107 +731,6 @@ impl TxPoolService { Some((Ok(ProcessResult::Completed(completed)), submit_snapshot)) } - pub(crate) async fn run_verify_tx( - &mut self, - entry: Entry, - command_rx: &mut watch::Receiver, - ) -> Option<(Result, Arc)> { - let Entry { tx, remote } = entry; - let tx_hash = tx.hash(); - - let (ret, snapshot) = self.pre_check(&tx).await; - let (tip_hash, rtx, status, fee, tx_size) = try_or_return_with_snapshot!(ret, snapshot); - let cached = self.fetch_tx_verify_cache(&tx_hash).await; - let tip_header = snapshot.tip_header(); - let consensus = snapshot.cloned_consensus(); - - let tx_env = Arc::new(TxVerifyEnv::new_submit(tip_header)); - - if let Some(ref completed) = cached { - let ret = TimeRelativeTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - snapshot.as_data_loader(), - Arc::clone(&tx_env), - ) - .verify() - .map(|_| *completed) - .map_err(Reject::Verification); - let completed = try_or_return_with_snapshot!(ret, snapshot); - - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, snapshot) = self.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, snapshot); - return Some((Ok(completed), snapshot)); - } - - let cloned_snapshot = Arc::clone(&snapshot); - let data_loader = cloned_snapshot.as_data_loader(); - let ret = ContextualWithoutScriptTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - data_loader.clone(), - Arc::clone(&tx_env), - ) - .verify() - .and_then(|result| { - DaoScriptSizeVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - data_loader.clone(), - ) - .verify()?; - Ok(result) - }) - .map_err(Reject::Verification); - let fee = try_or_return_with_snapshot!(ret, snapshot); - - let max_cycles = if let Some((declared_cycle, _peer)) = remote { - declared_cycle - } else { - consensus.max_block_cycles() - }; - - let ret = { - let script_verifier = - ScriptVerifier::new(Arc::clone(&rtx), data_loader, consensus, tx_env); - script_verifier - .resumable_verify_with_signal(max_cycles, command_rx) - .await - .map_err(Reject::Verification) - }; - - let state = try_or_return_with_snapshot!(ret, snapshot); - let completed: Completed = match state { - VerifyResult::Completed(cycles) => Completed { cycles, fee }, - _ => { - unreachable!("unexpected Suspend in run_verify_tx"); - } - }; - if let Some((declared_cycle, _peer)) = remote { - if declared_cycle != completed.cycles { - return Some(( - Err(Reject::DeclaredWrongCycles( - declared_cycle, - completed.cycles, - )), - snapshot, - )); - } - } - // verify passed - let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); - let (ret, snapshot) = self.submit_entry(tip_hash, entry, status).await; - try_or_return_with_snapshot!(ret, snapshot); - - self.notify_block_assembler(status).await; - - let mut guard = self.txs_verify_cache.write().await; - guard.put(tx_hash, completed); - - Some((Ok(completed), snapshot)) - } - pub(crate) async fn enqueue_suspended_tx( &self, tx: TransactionView, @@ -844,6 +744,7 @@ impl TxPoolService { &self, tx: TransactionView, declared_cycles: Option, + command_rx: Option<&mut watch::Receiver>, ) -> Option<(Result, Arc)> { let tx_hash = tx.hash(); @@ -870,7 +771,9 @@ impl TxPoolService { tx_env, &verify_cache, max_cycles, - ); + command_rx, + ) + .await; let verified = try_or_return_with_snapshot!(verified_ret, snapshot); @@ -984,7 +887,8 @@ impl TxPoolService { } // notice: readd_detached_tx don't update cache - self.readd_detached_tx(&mut tx_pool, retain, fetched_cache); + self.readd_detached_tx(&mut tx_pool, retain, fetched_cache) + .await; txs_opt }; @@ -1036,7 +940,7 @@ impl TxPoolService { orphan.remove_orphan_txs(txs.iter().map(|tx| tx.proposal_short_id())); } - fn readd_detached_tx( + async fn readd_detached_tx( &self, tx_pool: &mut TxPool, txs: Vec, @@ -1058,7 +962,10 @@ impl TxPoolService { tx_env, &verify_cache, max_cycles, - ) { + None, + ) + .await + { let entry = TxEntry::new(rtx, verified.cycles, fee, tx_size); if let Err(e) = _submit_entry(tx_pool, status, entry, &self.callbacks) { error!("readd_detached_tx submit_entry {} error {}", tx_hash, e); diff --git a/tx-pool/src/util.rs b/tx-pool/src/util.rs index 937af7d841..0bb3bf9aae 100644 --- a/tx-pool/src/util.rs +++ b/tx-pool/src/util.rs @@ -2,6 +2,7 @@ use crate::error::Reject; use crate::pool::TxPool; use ckb_chain_spec::consensus::Consensus; use ckb_dao::DaoCalculator; +use ckb_script::ChunkCommand; use ckb_snapshot::Snapshot; use ckb_store::data_loader_wrapper::AsDataLoader; use ckb_store::ChainStore; @@ -15,7 +16,7 @@ use ckb_verification::{ TimeRelativeTransactionVerifier, TxVerifyEnv, }; use std::sync::Arc; -use tokio::task::block_in_place; +use tokio::{sync::watch, task::block_in_place}; pub(crate) fn check_txid_collision(tx_pool: &TxPool, tx: &TransactionView) -> Result<(), Reject> { let short_id = tx.proposal_short_id(); @@ -82,12 +83,13 @@ pub(crate) fn non_contextual_verify( Ok(()) } -pub(crate) fn verify_rtx( +pub(crate) async fn verify_rtx( snapshot: Arc, rtx: Arc, tx_env: Arc, cache_entry: &Option, max_tx_verify_cycles: Cycle, + command_rx: Option<&mut watch::Receiver>, ) -> Result { let consensus = snapshot.cloned_consensus(); let data_loader = snapshot.as_data_loader(); @@ -97,6 +99,21 @@ pub(crate) fn verify_rtx( .verify() .map(|_| *completed) .map_err(Reject::Verification) + } else if let Some(command_rx) = command_rx { + ContextualTransactionVerifier::new( + Arc::clone(&rtx), + consensus, + data_loader, + Arc::clone(&tx_env), + ) + .verify_with_pause(max_tx_verify_cycles, command_rx) + .await + .and_then(|result| { + DaoScriptSizeVerifier::new(rtx, snapshot.cloned_consensus(), snapshot.as_data_loader()) + .verify()?; + Ok(result) + }) + .map_err(Reject::Verification) } else { block_in_place(|| { ContextualTransactionVerifier::new(Arc::clone(&rtx), consensus, data_loader, tx_env) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 3f4f453e53..6aa537cc6a 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -81,9 +81,13 @@ impl Worker { let (res, snapshot) = self .service - .run_verify_tx(entry.clone(), &mut self.command_rx) + ._process_tx( + entry.tx.clone(), + entry.remote.map(|e| e.0), + Some(&mut self.command_rx), + ) .await - .expect("run_verify_tx failed"); + .expect("verify worker _process_tx failed"); self.service .after_process(entry.tx, entry.remote, &snapshot, &res) diff --git a/verification/src/lib.rs b/verification/src/lib.rs index e2417019f6..7ae9bab362 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -21,8 +21,8 @@ pub use crate::error::{ pub use crate::genesis_verifier::GenesisVerifier; pub use crate::header_verifier::HeaderVerifier; pub use crate::transaction_verifier::{ - CapacityVerifier, ContextualTransactionVerifier, ContextualWithoutScriptTransactionVerifier, - DaoScriptSizeVerifier, NonContextualTransactionVerifier, ScriptVerifier, Since, SinceMetric, + CapacityVerifier, ContextualTransactionVerifier, DaoScriptSizeVerifier, + NonContextualTransactionVerifier, ScriptVerifier, Since, SinceMetric, TimeRelativeTransactionVerifier, }; pub use ckb_script::{ diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 1cf56f12c7..06f8489c52 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -154,26 +154,6 @@ where } } - /// Perform context-dependent verification, return a `Result` to `CacheEntry` - /// FIXME(yukang): only used in tests - pub async fn verify_until_completed( - &self, - limit_cycles: Cycle, - ) -> Result<(VerifyResult, Capacity), Error> { - self.compatible.verify()?; - self.time_relative.verify()?; - self.capacity.verify()?; - let fee = self.fee_calculator.transaction_fee()?; - // here we use a dummy command_rx, which will never receive a Suspend command - // to make sure the verification will not be interrupted and ExceededMaximumCycles will be treated as an error - let (_command_tx, mut command_rx) = tokio::sync::watch::channel(ChunkCommand::Resume); - let ret = self - .script - .resumable_verify_with_signal(limit_cycles, &mut command_rx) - .await?; - Ok((ret, fee)) - } - /// Perform context-dependent verification, return a `Result` to `CacheEntry` /// /// skip script verify will result in the return value cycle always is zero @@ -190,6 +170,29 @@ where Ok(Completed { cycles, fee }) } + /// Perform context-dependent verification with command + /// The verification will be interrupted when receiving a Suspend command + pub async fn verify_with_pause( + &self, + max_cycles: Cycle, + command_rx: &mut tokio::sync::watch::Receiver, + ) -> Result { + self.compatible.verify()?; + self.time_relative.verify()?; + self.capacity.verify()?; + let fee = self.fee_calculator.transaction_fee()?; + let ret = self + .script + .resumable_verify_with_signal(max_cycles, command_rx) + .await?; + let cycles = if let VerifyResult::Completed(cycles) = ret { + cycles + } else { + unimplemented!("should not in suspend here"); + }; + Ok(Completed { cycles, fee }) + } + /// Perform complete a suspend context-dependent verification, return a `Result` to `CacheEntry` /// /// skip script verify will result in the return value cycle always is zero @@ -840,65 +843,6 @@ impl CompatibleVerifier { } } -/// Context-dependent checks exclude script -/// -/// Contains: -/// [`TimeRelativeTransactionVerifier`](./struct.TimeRelativeTransactionVerifier.html) -/// [`CapacityVerifier`](./struct.CapacityVerifier.html) -/// [`FeeCalculator`](./struct.FeeCalculator.html) -pub struct ContextualWithoutScriptTransactionVerifier
{ - pub(crate) compatible: CompatibleVerifier, - pub(crate) time_relative: TimeRelativeTransactionVerifier
, - pub(crate) capacity: CapacityVerifier, - pub(crate) fee_calculator: FeeCalculator
, -} - -impl
ContextualWithoutScriptTransactionVerifier
-where - DL: CellDataProvider - + HeaderProvider - + HeaderFieldsProvider - + EpochProvider - + ExtensionProvider - + Send - + Sync - + Clone - + 'static, -{ - /// Creates a new ContextualWithoutScriptTransactionVerifier - pub fn new( - rtx: Arc, - consensus: Arc, - data_loader: DL, - tx_env: Arc, - ) -> Self { - ContextualWithoutScriptTransactionVerifier { - compatible: CompatibleVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - Arc::clone(&tx_env), - ), - time_relative: TimeRelativeTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&consensus), - data_loader.clone(), - tx_env, - ), - capacity: CapacityVerifier::new(Arc::clone(&rtx), consensus.dao_type_hash()), - fee_calculator: FeeCalculator::new(rtx, consensus, data_loader), - } - } - - /// Perform verification - pub fn verify(&self) -> Result { - self.compatible.verify()?; - self.time_relative.verify()?; - self.capacity.verify()?; - let fee = self.fee_calculator.transaction_fee()?; - Ok(fee) - } -} - /// Verifies that deposit cell and withdrawing cell in Nervos DAO use same sized lock scripts. /// It provides a temporary solution till Nervos DAO script can be properly upgraded. pub struct DaoScriptSizeVerifier
{ From 69d27ed34f6b10f7ee85842f63d0d48d11ce4590 Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 26 Jan 2024 12:31:45 +0800 Subject: [PATCH 048/294] code refactor for resumeble_process_tx --- tx-pool/src/process.rs | 70 ++++++++---------------------------------- tx-pool/src/service.rs | 6 ++-- 2 files changed, 16 insertions(+), 60 deletions(-) diff --git a/tx-pool/src/process.rs b/tx-pool/src/process.rs index 169da31fb7..bebce45de4 100644 --- a/tx-pool/src/process.rs +++ b/tx-pool/src/process.rs @@ -28,15 +28,13 @@ use ckb_types::{ use ckb_util::LinkedHashSet; use ckb_verification::{ cache::{CacheEntry, Completed}, - ContextualTransactionVerifier, DaoScriptSizeVerifier, TimeRelativeTransactionVerifier, - TxVerifyEnv, + TimeRelativeTransactionVerifier, TxVerifyEnv, }; use std::collections::HashSet; use std::collections::{HashMap, VecDeque}; use std::sync::Arc; use std::time::Duration; use tokio::sync::watch; -use tokio::task::block_in_place; const DELAY_LIMIT: usize = 1_500 * 21; // 1_500 per block, 21 blocks @@ -291,7 +289,6 @@ impl TxPoolService { &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, - add_verify_queue: bool, ) -> Result<(), Reject> { // non contextual verify first self.non_contextual_verify(&tx, remote)?; @@ -305,10 +302,7 @@ impl TxPoolService { return Err(Reject::Duplicated(tx.hash())); } - if let Some((ret, snapshot)) = self - ._resumeble_process_tx(tx.clone(), remote, add_verify_queue) - .await - { + if let Some((ret, snapshot)) = self._resumeble_process_tx(tx.clone(), remote).await { match ret { Ok(processed) => { if let ProcessResult::Completed(completed) = processed { @@ -537,7 +531,7 @@ impl TxPoolService { tx.hash(), ); self.remove_orphan_tx(&orphan.tx.proposal_short_id()).await; - self.enqueue_suspended_tx(orphan.tx, Some((orphan.cycle, orphan.peer))) + self.enqueue_verify_queue(orphan.tx, Some((orphan.cycle, orphan.peer))) .await .expect("enqueue suspended tx"); } else if let Some((ret, snapshot)) = self @@ -639,7 +633,6 @@ impl TxPoolService { &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, - add_verify_queue: bool, ) -> Option<(Result, Arc)> { let tx_hash = tx.hash(); @@ -654,14 +647,12 @@ impl TxPoolService { return None; } - let cached = self.fetch_tx_verify_cache(&tx_hash).await; - let tip_header = snapshot.tip_header(); - let tx_env = Arc::new(status.with_env(tip_header)); - - let data_loader = snapshot.as_data_loader(); - - let completed = match cached { + let completed = match self.fetch_tx_verify_cache(&tx_hash).await { Some(completed) => { + let tip_header = snapshot.tip_header(); + let tx_env = Arc::new(status.with_env(tip_header)); + let data_loader = snapshot.as_data_loader(); + let ret = TimeRelativeTransactionVerifier::new( Arc::clone(&rtx), Arc::clone(&self.consensus), @@ -673,45 +664,17 @@ impl TxPoolService { try_or_return_with_snapshot!(ret, snapshot); completed } - None if add_verify_queue => { + None => { // for remote transaction with decleard cycles, we enqueue it to verify queue directly // notified transaction now don't have decleard cycles, we may need to fix it in future, // now we also enqueue it to verify queue directly let ret = self - .enqueue_suspended_tx(rtx.transaction.clone(), remote) + .enqueue_verify_queue(rtx.transaction.clone(), remote) .await; try_or_return_with_snapshot!(ret, snapshot); error!("added to verify queue: {:?}", tx.proposal_short_id()); return Some((Ok(ProcessResult::Suspended), snapshot)); } - None => { - // for local transaction, we verify it directly with a max cycles limit - assert!(remote.is_none()); - let ret = { - block_in_place(|| { - let cycle_limit = snapshot.cloned_consensus().max_block_cycles(); - ContextualTransactionVerifier::new( - Arc::clone(&rtx), - Arc::clone(&self.consensus), - data_loader, - tx_env, - ) - .verify(cycle_limit, false) - .and_then(|result| { - DaoScriptSizeVerifier::new( - Arc::clone(&rtx), - snapshot.cloned_consensus(), - snapshot.as_data_loader(), - ) - .verify()?; - Ok(result) - }) - .map_err(Reject::Verification) - }) - }; - - try_or_return_with_snapshot!(ret, snapshot) - } }; let entry = TxEntry::new(rtx, completed.cycles, fee, tx_size); @@ -719,19 +682,12 @@ impl TxPoolService { try_or_return_with_snapshot!(ret, submit_snapshot); self.notify_block_assembler(status).await; - if cached.is_none() { - // update cache in background - let txs_verify_cache = Arc::clone(&self.txs_verify_cache); - tokio::spawn(async move { - let mut guard = txs_verify_cache.write().await; - guard.put(tx_hash, completed); - }); - } - + // if tx is not in cache, it's added into verify queue and won't run into here + // the cache will be updated in `verify_mgr`, so we don't update cache here Some((Ok(ProcessResult::Completed(completed)), submit_snapshot)) } - pub(crate) async fn enqueue_suspended_tx( + async fn enqueue_verify_queue( &self, tx: TransactionView, remote: Option<(Cycle, PeerIndex)>, diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index ae6cb1c726..4e006ebe18 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -679,7 +679,7 @@ async fn process(mut service: TxPoolService, message: Message) { responder, arguments: tx, }) => { - let result = service.resumeble_process_tx(tx, None, false).await; + let result = service.process_tx(tx, None).await.map(|_| ()); if let Err(e) = responder.send(result) { error!("Responder sending submit_tx result failed {:?}", e); }; @@ -699,7 +699,7 @@ async fn process(mut service: TxPoolService, message: Message) { }) => { if declared_cycles > service.tx_pool_config.max_tx_verify_cycles { let _result = service - .resumeble_process_tx(tx, Some((declared_cycles, peer)), true) + .resumeble_process_tx(tx, Some((declared_cycles, peer))) .await; if let Err(e) = responder.send(()) { error!("Responder sending submit_tx result failed {:?}", e); @@ -713,7 +713,7 @@ async fn process(mut service: TxPoolService, message: Message) { } Message::NotifyTxs(Notify { arguments: txs }) => { for tx in txs { - let _ret = service.resumeble_process_tx(tx, None, true).await; + let _ret = service.resumeble_process_tx(tx, None).await; } } Message::FreshProposalsFilter(Request { From b7cbbeeb3f10036e014a3768325d8f26be4bf412 Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 26 Jan 2024 14:53:05 +0800 Subject: [PATCH 049/294] add verify test logs for testing --- script/src/verify.rs | 6 +++++- tx-pool/src/service.rs | 4 +++- tx-pool/src/verify_mgr.rs | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/script/src/verify.rs b/script/src/verify.rs index 1dee43338d..fc630243ab 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1405,6 +1405,7 @@ async fn run_vms_with_signal( tokio::select! { Ok(_) = signal.changed() => { let command = signal.borrow().to_owned(); + debug!("[verify-test] run_vms_with_signal: {:?}", command); match command { ChunkCommand::Suspend => { pause.interrupt(); @@ -1458,7 +1459,9 @@ async fn run_vms_child( ChunkCommand::Suspend => { continue; } - ChunkCommand::Resume => {} + ChunkCommand::Resume => { + debug!("[verify-test] run_vms_child: resume"); + } } if machines.is_empty() { finish_tx @@ -1509,6 +1512,7 @@ async fn run_vms_child( machines.push(machine); machines.append(&mut new_suspended_machines); // break to wait for Resume command to begin next loop iteration + debug!("[verify-test] run_vms_child: suspend at {:?}", cycles); break; } _ => { diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index 4e006ebe18..5413763c93 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -15,8 +15,8 @@ use ckb_chain_spec::consensus::Consensus; use ckb_channel::oneshot; use ckb_error::AnyError; use ckb_jsonrpc_types::BlockTemplate; -use ckb_logger::error; use ckb_logger::info; +use ckb_logger::{debug, error}; use ckb_network::{NetworkController, PeerIndex}; use ckb_script::ChunkCommand; use ckb_snapshot::Snapshot; @@ -318,6 +318,7 @@ impl TxPoolController { /// Sends suspend chunk process cmd pub fn suspend_chunk_process(&self) -> Result<(), AnyError> { + debug!("[verify-test] run suspend_chunk_process"); self.chunk_tx .send(ChunkCommand::Suspend) .map_err(handle_send_cmd_error) @@ -326,6 +327,7 @@ impl TxPoolController { /// Sends continue chunk process cmd pub fn continue_chunk_process(&self) -> Result<(), AnyError> { + debug!("[verify-test] run continue_chunk_process"); self.chunk_tx .send(ChunkCommand::Resume) .map_err(handle_send_cmd_error) diff --git a/tx-pool/src/verify_mgr.rs b/tx-pool/src/verify_mgr.rs index 6aa537cc6a..2aa0826744 100644 --- a/tx-pool/src/verify_mgr.rs +++ b/tx-pool/src/verify_mgr.rs @@ -1,7 +1,7 @@ extern crate num_cpus; use crate::component::verify_queue::VerifyQueue; use crate::service::TxPoolService; -use ckb_logger::info; +use ckb_logger::{debug, info}; use ckb_script::ChunkCommand; use ckb_stop_handler::CancellationToken; use std::sync::Arc; @@ -136,6 +136,7 @@ impl VerifyMgr { } fn send_child_command(&self, command: ChunkCommand) { + debug!("[verify-test] verify-mgr send child command: {:?}", command); for w in &self.workers { if let Err(err) = w.0.send(command.clone()) { info!("send worker command failed, error: {}", err); From 7aa48a5834d45afaae8490df853f50a130071e24 Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 26 Jan 2024 18:07:15 +0800 Subject: [PATCH 050/294] add send_test_transaction to help verify testing, we may need to rollback this commit --- rpc/README.md | 251 ++++++++++++++++++++++++++----------- rpc/src/module/test.rs | 140 ++++++++++++++++++++- rpc/src/service_builder.rs | 4 + rpc/src/tests/setup.rs | 2 + tx-pool/src/service.rs | 15 +++ util/launcher/src/lib.rs | 16 ++- 6 files changed, 350 insertions(+), 78 deletions(-) diff --git a/rpc/README.md b/rpc/README.md index 03fbac6f0b..9fd0da17aa 100644 --- a/rpc/README.md +++ b/rpc/README.md @@ -31,80 +31,81 @@ The crate `ckb-rpc`'s minimum supported rustc version is 1.71.1. * [RPC Methods](#rpc-methods) - * [Module Alert](#module-alert) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Alert&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/alert_rpc_doc.json) - * [Method `send_alert`](#alert-send_alert) - * [Module Chain](#module-chain) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Chain&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/chain_rpc_doc.json) - * [Method `get_block`](#chain-get_block) - * [Method `get_block_by_number`](#chain-get_block_by_number) - * [Method `get_header`](#chain-get_header) - * [Method `get_header_by_number`](#chain-get_header_by_number) - * [Method `get_block_filter`](#chain-get_block_filter) - * [Method `get_transaction`](#chain-get_transaction) - * [Method `get_block_hash`](#chain-get_block_hash) - * [Method `get_tip_header`](#chain-get_tip_header) - * [Method `get_live_cell`](#chain-get_live_cell) - * [Method `get_tip_block_number`](#chain-get_tip_block_number) - * [Method `get_current_epoch`](#chain-get_current_epoch) - * [Method `get_epoch_by_number`](#chain-get_epoch_by_number) - * [Method `get_block_economic_state`](#chain-get_block_economic_state) - * [Method `get_transaction_proof`](#chain-get_transaction_proof) - * [Method `verify_transaction_proof`](#chain-verify_transaction_proof) - * [Method `get_transaction_and_witness_proof`](#chain-get_transaction_and_witness_proof) - * [Method `verify_transaction_and_witness_proof`](#chain-verify_transaction_and_witness_proof) - * [Method `get_fork_block`](#chain-get_fork_block) - * [Method `get_consensus`](#chain-get_consensus) - * [Method `get_block_median_time`](#chain-get_block_median_time) - * [Method `estimate_cycles`](#chain-estimate_cycles) - * [Method `get_fee_rate_statics`](#chain-get_fee_rate_statics) - * [Method `get_fee_rate_statistics`](#chain-get_fee_rate_statistics) - * [Module Debug](#module-debug) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Debug&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/debug_rpc_doc.json) - * [Method `jemalloc_profiling_dump`](#debug-jemalloc_profiling_dump) - * [Method `update_main_logger`](#debug-update_main_logger) - * [Method `set_extra_logger`](#debug-set_extra_logger) - * [Module Experiment](#module-experiment) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Experiment&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/experiment_rpc_doc.json) - * [Method `dry_run_transaction`](#experiment-dry_run_transaction) - * [Method `calculate_dao_maximum_withdraw`](#experiment-calculate_dao_maximum_withdraw) - * [Module Indexer](#module-indexer) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Indexer&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/indexer_rpc_doc.json) - * [Method `get_indexer_tip`](#indexer-get_indexer_tip) - * [Method `get_cells`](#indexer-get_cells) - * [Method `get_transactions`](#indexer-get_transactions) - * [Method `get_cells_capacity`](#indexer-get_cells_capacity) - * [Module Integration_test](#module-integration_test) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Integration_test&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/integration_test_rpc_doc.json) - * [Method `process_block_without_verify`](#integration_test-process_block_without_verify) - * [Method `truncate`](#integration_test-truncate) - * [Method `generate_block`](#integration_test-generate_block) - * [Method `generate_epochs`](#integration_test-generate_epochs) - * [Method `notify_transaction`](#integration_test-notify_transaction) - * [Method `generate_block_with_template`](#integration_test-generate_block_with_template) - * [Method `calculate_dao_field`](#integration_test-calculate_dao_field) - * [Module Miner](#module-miner) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Miner&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/miner_rpc_doc.json) - * [Method `get_block_template`](#miner-get_block_template) - * [Method `submit_block`](#miner-submit_block) - * [Module Net](#module-net) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Net&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/net_rpc_doc.json) - * [Method `local_node_info`](#net-local_node_info) - * [Method `get_peers`](#net-get_peers) - * [Method `get_banned_addresses`](#net-get_banned_addresses) - * [Method `clear_banned_addresses`](#net-clear_banned_addresses) - * [Method `set_ban`](#net-set_ban) - * [Method `sync_state`](#net-sync_state) - * [Method `set_network_active`](#net-set_network_active) - * [Method `add_node`](#net-add_node) - * [Method `remove_node`](#net-remove_node) - * [Method `ping_peers`](#net-ping_peers) - * [Module Pool](#module-pool) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Pool&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/pool_rpc_doc.json) - * [Method `send_transaction`](#pool-send_transaction) - * [Method `remove_transaction`](#pool-remove_transaction) - * [Method `tx_pool_info`](#pool-tx_pool_info) - * [Method `clear_tx_pool`](#pool-clear_tx_pool) - * [Method `get_raw_tx_pool`](#pool-get_raw_tx_pool) - * [Method `get_pool_tx_detail_info`](#pool-get_pool_tx_detail_info) - * [Method `tx_pool_ready`](#pool-tx_pool_ready) - * [Module Stats](#module-stats) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Stats&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/stats_rpc_doc.json) - * [Method `get_blockchain_info`](#stats-get_blockchain_info) - * [Method `get_deployments_info`](#stats-get_deployments_info) - * [Module Subscription](#module-subscription) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Subscription&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/subscription_rpc_doc.json) - * [Method `subscribe`](#subscription-subscribe) - * [Method `unsubscribe`](#subscription-unsubscribe) + * [Module Alert](#module-alert) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Alert&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/alert_rpc_doc.json) + * [Method `send_alert`](#method-send_alert) + * [Module Chain](#module-chain) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Chain&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/chain_rpc_doc.json) + * [Method `get_block`](#method-get_block) + * [Method `get_block_by_number`](#method-get_block_by_number) + * [Method `get_header`](#method-get_header) + * [Method `get_header_by_number`](#method-get_header_by_number) + * [Method `get_block_filter`](#method-get_block_filter) + * [Method `get_transaction`](#method-get_transaction) + * [Method `get_block_hash`](#method-get_block_hash) + * [Method `get_tip_header`](#method-get_tip_header) + * [Method `get_live_cell`](#method-get_live_cell) + * [Method `get_tip_block_number`](#method-get_tip_block_number) + * [Method `get_current_epoch`](#method-get_current_epoch) + * [Method `get_epoch_by_number`](#method-get_epoch_by_number) + * [Method `get_block_economic_state`](#method-get_block_economic_state) + * [Method `get_transaction_proof`](#method-get_transaction_proof) + * [Method `verify_transaction_proof`](#method-verify_transaction_proof) + * [Method `get_transaction_and_witness_proof`](#method-get_transaction_and_witness_proof) + * [Method `verify_transaction_and_witness_proof`](#method-verify_transaction_and_witness_proof) + * [Method `get_fork_block`](#method-get_fork_block) + * [Method `get_consensus`](#method-get_consensus) + * [Method `get_block_median_time`](#method-get_block_median_time) + * [Method `estimate_cycles`](#method-estimate_cycles) + * [Method `get_fee_rate_statics`](#method-get_fee_rate_statics) + * [Method `get_fee_rate_statistics`](#method-get_fee_rate_statistics) + * [Module Debug](#module-debug) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Debug&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/debug_rpc_doc.json) + * [Method `jemalloc_profiling_dump`](#method-jemalloc_profiling_dump) + * [Method `update_main_logger`](#method-update_main_logger) + * [Method `set_extra_logger`](#method-set_extra_logger) + * [Module Experiment](#module-experiment) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Experiment&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/experiment_rpc_doc.json) + * [Method `dry_run_transaction`](#method-dry_run_transaction) + * [Method `calculate_dao_maximum_withdraw`](#method-calculate_dao_maximum_withdraw) + * [Module Indexer](#module-indexer) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Indexer&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/indexer_rpc_doc.json) + * [Method `get_indexer_tip`](#method-get_indexer_tip) + * [Method `get_cells`](#method-get_cells) + * [Method `get_transactions`](#method-get_transactions) + * [Method `get_cells_capacity`](#method-get_cells_capacity) + * [Module Integration_test](#module-integration_test) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Integration_test&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/integration_test_rpc_doc.json) + * [Method `process_block_without_verify`](#method-process_block_without_verify) + * [Method `truncate`](#method-truncate) + * [Method `generate_block`](#method-generate_block) + * [Method `generate_epochs`](#method-generate_epochs) + * [Method `notify_transaction`](#method-notify_transaction) + * [Method `generate_block_with_template`](#method-generate_block_with_template) + * [Method `calculate_dao_field`](#method-calculate_dao_field) + * [Method `send_test_transaction`](#method-send_test_transaction) + * [Module Miner](#module-miner) + * [Method `get_block_template`](#method-get_block_template) + * [Method `submit_block`](#method-submit_block) + * [Module Net](#module-net) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Net&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/net_rpc_doc.json) + * [Method `local_node_info`](#method-local_node_info) + * [Method `get_peers`](#method-get_peers) + * [Method `get_banned_addresses`](#method-get_banned_addresses) + * [Method `clear_banned_addresses`](#method-clear_banned_addresses) + * [Method `set_ban`](#method-set_ban) + * [Method `sync_state`](#method-sync_state) + * [Method `set_network_active`](#method-set_network_active) + * [Method `add_node`](#method-add_node) + * [Method `remove_node`](#method-remove_node) + * [Method `ping_peers`](#method-ping_peers) + * [Module Pool](#module-pool) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Pool&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/pool_rpc_doc.json) + * [Method `send_transaction`](#method-send_transaction) + * [Method `remove_transaction`](#method-remove_transaction) + * [Method `tx_pool_info`](#method-tx_pool_info) + * [Method `clear_tx_pool`](#method-clear_tx_pool) + * [Method `get_raw_tx_pool`](#method-get_raw_tx_pool) + * [Method `get_pool_tx_detail_info`](#method-get_pool_tx_detail_info) + * [Method `tx_pool_ready`](#method-tx_pool_ready) + * [Module Stats](#module-stats) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Stats&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/stats_rpc_doc.json) + * [Method `get_blockchain_info`](#method-get_blockchain_info) + * [Method `get_deployments_info`](#method-get_deployments_info) + * [Module Subscription](#module-subscription) [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Subscription&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/e7b5c1c42c07d4ac93acd1b02e9b29458658d3fa/json/subscription_rpc_doc.json) + * [Method `subscribe`](#method-subscribe) + * [Method `unsubscribe`](#method-unsubscribe) * [RPC Types](#rpc-types) * [Type `Alert`](#type-alert) @@ -3495,8 +3496,106 @@ Response } ``` -### Module `Miner` -- [👉 OpenRPC spec](http://playground.open-rpc.org/?uiSchema[appBar][ui:title]=CKB-Miner&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:logoUrl]=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/main/ckb-logo.jpg&schemaUrl=https://raw.githubusercontent.com/cryptape/ckb-rpc-resources/5d696307edb59dfa198fb78800ae14588d4bafd8/json/miner_rpc_doc.json) + +#### Method `send_test_transaction` +* `send_test_transaction(tx, outputs_validator)` + * `tx`: [`Transaction`](#type-transaction) + * `outputs_validator`: [`OutputsValidator`](#type-outputsvalidator) `|` `null` +* result: [`H256`](#type-h256) + +Submits a new test local transaction into the transaction pool, only for testing. If the transaction is already in the pool, rebroadcast it to peers. + +###### Params + +* `transaction` - The transaction. + +* `outputs_validator` - Validates the transaction outputs before entering the tx-pool. (**Optional**, default is “passthrough”). + +###### Errors + +* [`PoolRejectedTransactionByOutputsValidator (-1102)`](#error-poolrejectedtransactionbyoutputsvalidator) - The transaction is rejected by the validator specified by `outputs_validator`. If you really want to send transactions with advanced scripts, please set `outputs_validator` to “passthrough”. + +* [`PoolRejectedTransactionByMinFeeRate (-1104)`](#error-poolrejectedtransactionbyminfeerate) - The transaction fee rate must be greater than or equal to the config option `tx_pool.min_fee_rate`. + +* [`PoolRejectedTransactionByMaxAncestorsCountLimit (-1105)`](#error-poolrejectedtransactionbymaxancestorscountlimit) - The ancestors count must be greater than or equal to the config option `tx_pool.max_ancestors_count`. + +* [`PoolIsFull (-1106)`](#error-poolisfull) - Pool is full. + +* [`PoolRejectedDuplicatedTransaction (-1107)`](#error-poolrejectedduplicatedtransaction) - The transaction is already in the pool. + +* [`TransactionFailedToResolve (-301)`](#error-transactionfailedtoresolve) - Failed to resolve the referenced cells and headers used in the transaction, as inputs or dependencies. + +* [`TransactionFailedToVerify (-302)`](#error-transactionfailedtoverify) - Failed to verify the transaction. + +###### Examples + +Request + + +``` +{ + "id": 42, + "jsonrpc": "2.0", + "method": "send_test_transaction", + "params": [ + { + "cell_deps": [ + { + "dep_type": "code", + "out_point": { + "index": "0x0", + "tx_hash": "0xa4037a893eb48e18ed4ef61034ce26eba9c585f15c9cee102ae58505565eccc3" + } + } + ], + "header_deps": [ + "0x7978ec7ce5b507cfb52e149e36b1a23f6062ed150503c85bbf825da3599095ed" + ], + "inputs": [ + { + "previous_output": { + "index": "0x0", + "tx_hash": "0x365698b50ca0da75dca2c87f9e7b563811d3b5813736b8cc62cc3b106faceb17" + }, + "since": "0x0" + } + ], + "outputs": [ + { + "capacity": "0x2540be400", + "lock": { + "code_hash": "0x28e83a1277d48add8e72fadaa9248559e1b632bab2bd60b27955ebc4c03800a5", + "hash_type": "data", + "args": "0x" + }, + "type": null + } + ], + "outputs_data": [ + "0x" + ], + "version": "0x0", + "witnesses": [] + }, + "passthrough" + ] +} +``` + + +Response + + +``` +{ + "id": 42, + "jsonrpc": "2.0", + "result": "0xa0ef4eb5f4ceeb08a4c8524d84c5da95dce2f608e0ca2ec8091191b0f330c6e3" +} +``` + + +### Module Miner RPC Module Miner for miners. diff --git a/rpc/src/module/test.rs b/rpc/src/module/test.rs index 1490e1101f..add04c472c 100644 --- a/rpc/src/module/test.rs +++ b/rpc/src/module/test.rs @@ -2,7 +2,9 @@ use crate::error::RPCError; use async_trait::async_trait; use ckb_chain::chain::ChainController; use ckb_dao::DaoCalculator; -use ckb_jsonrpc_types::{Block, BlockTemplate, Byte32, EpochNumberWithFraction, Transaction}; +use ckb_jsonrpc_types::{ + Block, BlockTemplate, Byte32, EpochNumberWithFraction, OutputsValidator, Transaction, +}; use ckb_logger::error; use ckb_network::{NetworkController, SupportProtocols}; use ckb_shared::{shared::Shared, Snapshot}; @@ -25,6 +27,8 @@ use jsonrpc_utils::rpc; use std::collections::HashSet; use std::sync::Arc; +use super::pool::WellKnownScriptsOnlyValidator; + /// RPC for Integration Test. #[rpc(openrpc)] #[async_trait] @@ -498,6 +502,95 @@ pub trait IntegrationTestRpc { /// ``` #[rpc(name = "calculate_dao_field")] fn calculate_dao_field(&self, block_template: BlockTemplate) -> Result; + + /// Submits a new test local transaction into the transaction pool, only for testing. + /// If the transaction is already in the pool, rebroadcast it to peers. + /// + /// ## Params + /// + /// * `transaction` - The transaction. + /// * `outputs_validator` - Validates the transaction outputs before entering the tx-pool. (**Optional**, default is "passthrough"). + /// + /// ## Errors + /// + /// * [`PoolRejectedTransactionByOutputsValidator (-1102)`](../enum.RPCError.html#variant.PoolRejectedTransactionByOutputsValidator) - The transaction is rejected by the validator specified by `outputs_validator`. If you really want to send transactions with advanced scripts, please set `outputs_validator` to "passthrough". + /// * [`PoolRejectedTransactionByMinFeeRate (-1104)`](../enum.RPCError.html#variant.PoolRejectedTransactionByMinFeeRate) - The transaction fee rate must be greater than or equal to the config option `tx_pool.min_fee_rate`. + /// * [`PoolRejectedTransactionByMaxAncestorsCountLimit (-1105)`](../enum.RPCError.html#variant.PoolRejectedTransactionByMaxAncestorsCountLimit) - The ancestors count must be greater than or equal to the config option `tx_pool.max_ancestors_count`. + /// * [`PoolIsFull (-1106)`](../enum.RPCError.html#variant.PoolIsFull) - Pool is full. + /// * [`PoolRejectedDuplicatedTransaction (-1107)`](../enum.RPCError.html#variant.PoolRejectedDuplicatedTransaction) - The transaction is already in the pool. + /// * [`TransactionFailedToResolve (-301)`](../enum.RPCError.html#variant.TransactionFailedToResolve) - Failed to resolve the referenced cells and headers used in the transaction, as inputs or dependencies. + /// * [`TransactionFailedToVerify (-302)`](../enum.RPCError.html#variant.TransactionFailedToVerify) - Failed to verify the transaction. + /// + /// ## Examples + /// + /// Request + /// + /// ```json + /// { + /// "id": 42, + /// "jsonrpc": "2.0", + /// "method": "send_test_transaction", + /// "params": [ + /// { + /// "cell_deps": [ + /// { + /// "dep_type": "code", + /// "out_point": { + /// "index": "0x0", + /// "tx_hash": "0xa4037a893eb48e18ed4ef61034ce26eba9c585f15c9cee102ae58505565eccc3" + /// } + /// } + /// ], + /// "header_deps": [ + /// "0x7978ec7ce5b507cfb52e149e36b1a23f6062ed150503c85bbf825da3599095ed" + /// ], + /// "inputs": [ + /// { + /// "previous_output": { + /// "index": "0x0", + /// "tx_hash": "0x365698b50ca0da75dca2c87f9e7b563811d3b5813736b8cc62cc3b106faceb17" + /// }, + /// "since": "0x0" + /// } + /// ], + /// "outputs": [ + /// { + /// "capacity": "0x2540be400", + /// "lock": { + /// "code_hash": "0x28e83a1277d48add8e72fadaa9248559e1b632bab2bd60b27955ebc4c03800a5", + /// "hash_type": "data", + /// "args": "0x" + /// }, + /// "type": null + /// } + /// ], + /// "outputs_data": [ + /// "0x" + /// ], + /// "version": "0x0", + /// "witnesses": [] + /// }, + /// "passthrough" + /// ] + /// } + /// ``` + /// + /// Response + /// + /// ```json + /// { + /// "id": 42, + /// "jsonrpc": "2.0", + /// "result": "0xa0ef4eb5f4ceeb08a4c8524d84c5da95dce2f608e0ca2ec8091191b0f330c6e3" + /// } + /// ``` + /// + #[rpc(name = "send_test_transaction")] + fn send_test_transaction( + &self, + tx: Transaction, + outputs_validator: Option, + ) -> Result; } #[derive(Clone)] @@ -505,6 +598,8 @@ pub(crate) struct IntegrationTestRpcImpl { pub network_controller: NetworkController, pub shared: Shared, pub chain: ChainController, + pub well_known_lock_scripts: Vec, + pub well_known_type_scripts: Vec, } #[async_trait] @@ -667,6 +762,49 @@ impl IntegrationTestRpc for IntegrationTestRpcImpl { .into(), ) } + + fn send_test_transaction( + &self, + tx: Transaction, + outputs_validator: Option, + ) -> Result { + let tx: packed::Transaction = tx.into(); + let tx: core::TransactionView = tx.into_view(); + + if let Err(e) = match outputs_validator { + None | Some(OutputsValidator::Passthrough) => Ok(()), + Some(OutputsValidator::WellKnownScriptsOnly) => WellKnownScriptsOnlyValidator::new( + self.shared.consensus(), + &self.well_known_lock_scripts, + &self.well_known_type_scripts, + ) + .validate(&tx), + } { + return Err(RPCError::custom_with_data( + RPCError::PoolRejectedTransactionByOutputsValidator, + format!( + "The transaction is rejected by OutputsValidator set in params[1]: {}. \ + Please check the related information in https://github.com/nervosnetwork/ckb/wiki/Transaction-%C2%BB-Default-Outputs-Validator", + outputs_validator.unwrap_or(OutputsValidator::WellKnownScriptsOnly).json_display() + ), + e, + )); + } + + let tx_pool = self.shared.tx_pool_controller(); + let submit_tx = tx_pool.submit_local_test_tx(tx.clone()); + + if let Err(e) = submit_tx { + error!("Send submit_tx request error {}", e); + return Err(RPCError::ckb_internal_error(e)); + } + + let tx_hash = tx.hash(); + match submit_tx.unwrap() { + Ok(_) => Ok(tx_hash.unpack()), + Err(reject) => Err(RPCError::from_submit_transaction_reject(&reject)), + } + } } impl IntegrationTestRpcImpl { diff --git a/rpc/src/service_builder.rs b/rpc/src/service_builder.rs index 210accd72c..c9600cd1f3 100644 --- a/rpc/src/service_builder.rs +++ b/rpc/src/service_builder.rs @@ -141,6 +141,8 @@ impl<'a> ServiceBuilder<'a> { shared: Shared, network_controller: NetworkController, chain: ChainController, + well_known_lock_scripts: Vec