Repository: nervosnetwork/ckb (language: rust)

Issue:
Hardcoding a Default `assume_valid_target` to Reduce the Timecost of Block Synchronization
## Feature Request

#### Is your feature request related to a problem? Please describe.
We are proposing the hardcoding of a default `assume_valid_target` in order to reduce the timecost of CKB's block synchronization.
#### Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. Add any considered drawbacks. -->
The desired solution involves implementing:
- A default `assume_valid_target` for both the `mainnet` and `testnet`. The hardcoded default "target" block should be from `60 days` ago.
- If `assume_valid_target` is `0x0000000000000000000000000000000000000000000000000000000000000000`, then CKB will disable the default behavior and execute full verification for all blocks/
- The hardcoded value should be updated prior to every CKB's release.

![image](https://github.com/nervosnetwork/ckb/assets/46400566/fbdbb4d0-6e42-4edc-8be5-4614978ee611)



Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 3ea9c97766..0812550f35 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -589,6 +589,7 @@ version = "0.115.0-pre"
 dependencies = [
  "ckb-build-info",
  "ckb-chain-spec",
+ "ckb-constant",
  "ckb-jsonrpc-types",
  "ckb-logger",
  "ckb-logger-config",
diff --git a/Makefile b/Makefile
index b35ff2955b..4915a49164 100644
--- a/Makefile
+++ b/Makefile
@@ -245,6 +245,11 @@ GEN_MOL_OUT_DIR := util/gen-types/src/generated
 GEN_MOL_FILES := ${GEN_MOL_OUT_DIR}/blockchain.rs ${GEN_MOL_OUT_DIR}/extensions.rs ${GEN_MOL_OUT_DIR}/protocols.rs
 gen: check-moleculec-version ${GEN_MOL_FILES} # Generate Protocol Files
 
+.PHONY: update-default-valid-target
+update-default-valid-target: ## update hardcoded default assume valid target to a 60 days ago block
+	./devtools/release/update_default_valid_target.sh
+	git --no-pager diff util/constant/src/default_assume_valid_target.rs
+
 .PHONY: check-moleculec-version
 check-moleculec-version:
 	test "$$(${MOLC} --version | awk '{ print $$2 }' | tr -d ' ')" = ${MOLC_VERSION}
diff --git a/devtools/release/update_default_valid_target.sh b/devtools/release/update_default_valid_target.sh
new file mode 100755
index 0000000000..5f7cb21e30
--- /dev/null
+++ b/devtools/release/update_default_valid_target.sh
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+function get_block_timestamp() {
+	local host=$1
+	local block_number=$2
+    header=$(curl -s -X POST ${host} -H 'Content-Type: application/json' -d '{ "id": 42, "jsonrpc": "2.0", "method": "get_header_by_number", "params": [ "'"$block_number"'" ] }')
+	echo ${header} | jq -r .result.timestamp
+}
+
+function get_block_hash() {
+	local host=$1
+	local block_number=$2
+    header=$(curl -s -X POST ${host} -H 'Content-Type: application/json' -d '{ "id": 42, "jsonrpc": "2.0", "method": "get_header_by_number", "params": [ "'"$block_number"'" ] }')
+	echo ${header} | jq -r .result.hash
+}
+
+function get_60days_ago_block(){
+	local host=$1
+
+	TIP_HEADER=$(curl -s -X POST ${host} -H 'Content-Type: application/json' -d '{ "id": 42, "jsonrpc": "2.0", "method": "get_tip_header", "params": [] }')
+	TIP_NUMBER=$(echo ${TIP_HEADER} | jq -r .result.number )
+	TIP_TIMESTAMP=$(echo ${TIP_HEADER} | jq -r .result.timestamp )
+
+	START_NUMBER=$(printf "0x%x\n" $(( $(printf "%d\n" ${TIP_NUMBER}) - 700000 )))
+	END_NUMBER=$(printf "0x%x\n" $(( $(printf "%d\n" ${TIP_NUMBER}) - 500000 )))
+
+
+	# Binary search
+	while [[ $(($END_NUMBER - $START_NUMBER)) -gt 1 ]]
+	do
+		MID_NUMBER=$(printf "0x%x\n" $(( ($START_NUMBER + $END_NUMBER) / 2 )))
+		MID_TIMESTAMP=$(get_block_timestamp ${host} ${MID_NUMBER})
+
+		if [[ $(($MID_TIMESTAMP + ((61 * 24 * 60 * 60 * 1000)) )) -gt ${TIP_TIMESTAMP} ]]; then
+			END_NUMBER=${MID_NUMBER}
+		else
+			START_NUMBER=${MID_NUMBER}
+		fi
+	done
+
+	echo ${START_NUMBER}
+}
+
+function print_60_days_ago_block(){
+  local network=$1
+  local host=$2
+  local explorer_url=$3
+
+  ASSUME_TARGET_HEIGHT=$(get_60days_ago_block ${host})
+  ASSUME_TARGET_HEIGHT_DECIMAL=$(printf "%d" ${ASSUME_TARGET_HEIGHT})
+  ASSUME_TARGET_HASH=$(get_block_hash ${host} ${ASSUME_TARGET_HEIGHT})
+  ASSUME_TARGET_TIMESTAMP=$(get_block_timestamp ${host} ${ASSUME_TARGET_HEIGHT})
+  ASSUME_TARGET_DATE=$(date -d @$((${ASSUME_TARGET_TIMESTAMP} / 1000)))
+  EXPLORER_URL=${explorer_url}/block/${ASSUME_TARGET_HASH}
+  printf "the 60 days ago block is: %d %s in %s\n" ${ASSUME_TARGET_HEIGHT_DECIMAL} ${ASSUME_TARGET_HASH} "${ASSUME_TARGET_DATE}" >&2
+  printf "you can view this block in ${EXPLORER_URL}\n\n" >&2
+
+  TEXT=$(cat <<END_HEREDOC
+/// sync config related to ${network}
+pub mod ${network} {
+    /// Default assume valid target for ${network}, expect to be a block 60 days ago.
+    ///
+    /// Need to update when CKB's new release
+    /// in ${network}: the 60 days ago block is:
+    /// height: ${ASSUME_TARGET_HEIGHT_DECIMAL}
+    /// hash: ${ASSUME_TARGET_HASH}
+    /// date: ${ASSUME_TARGET_DATE}
+    /// you can view this block in ${EXPLORER_URL}
+    pub const DEFAULT_ASSUME_VALID_TARGET: &str =
+        "${ASSUME_TARGET_HASH}";
+}
+END_HEREDOC
+)
+
+    echo "${TEXT}"
+}
+
+IFS='' read -r -d '' TEXT_HEADER <<'EOF' || true
+/// The mod mainnet and mod testnet's codes are generated
+/// by script: ./devtools/release/update_default_valid_target.sh
+/// Please don't modify them manually.
+///
+EOF
+
+printf "Now: %s\n\n" "$(date)"
+printf "Finding the 60 days ago block..., this script may take 1 minute\n\n"
+
+printf "MainNet:\n"
+TEXT_MAINNET=$(print_60_days_ago_block mainnet https://mainnet.ckb.dev https://explorer.nervos.org)
+
+printf "TestNet:\n"
+TEXT_TESTNET=$(print_60_days_ago_block testnet https://testnet.ckb.dev https://pudge.explorer.nervos.org)
+echo "${TEXT_HEADER}" > util/constant/src/default_assume_valid_target.rs
+echo "${TEXT_MAINNET}" >> util/constant/src/default_assume_valid_target.rs
+echo "${TEXT_TESTNET}" >> util/constant/src/default_assume_valid_target.rs
+echo
+echo this script has overwrite file: util/constant/src/default_assume_valid_target.rs
+echo Please review the changes
diff --git a/sync/src/synchronizer/mod.rs b/sync/src/synchronizer/mod.rs
index 8974e10ea2..1ea3b32fb7 100644
--- a/sync/src/synchronizer/mod.rs
+++ b/sync/src/synchronizer/mod.rs
@@ -171,9 +171,11 @@ impl BlockFetchCMD {
                 match state.header_map().get(&target.pack()) {
                     Some(header) => {
                         *flag = CanStart::Ready;
+                        info!("assume valid target found in header_map; CKB will start fetch blocks now");
                         // Blocks that are no longer in the scope of ibd must be forced to verify
                         if unix_time_as_millis().saturating_sub(header.timestamp()) < MAX_TIP_AGE {
                             assume_valid_target.take();
+                            warn!("the duration gap between 'assume valid target' and 'now' is less than 24h; CKB will ignore the specified assume valid target and do full verification from now on");
                         }
                     }
                     None => {
@@ -182,6 +184,7 @@ impl BlockFetchCMD {
                             .saturating_sub(state.shared_best_header_ref().timestamp())
                             < MAX_TIP_AGE
                         {
+                            warn!("the duration gap between 'shared_best_header' and 'now' is less than 24h, but CKB haven't found the assume valid target in header_map; CKB will ignore the specified assume valid target and do full verification from now on");
                             *flag = CanStart::Ready;
                             assume_valid_target.take();
                         }
diff --git a/sync/src/types/mod.rs b/sync/src/types/mod.rs
index 2eaed7cd48..c444f870fa 100644
--- a/sync/src/types/mod.rs
+++ b/sync/src/types/mod.rs
@@ -14,7 +14,7 @@ use ckb_constant::sync::{
     RETRY_ASK_TX_TIMEOUT_INCREASE, SUSPEND_SYNC_TIME,
 };
 use ckb_error::Error as CKBError;
-use ckb_logger::{debug, error, trace};
+use ckb_logger::{debug, error, info, trace};
 use ckb_network::{CKBProtocolContext, PeerIndex, SupportProtocols};
 use ckb_shared::{shared::Shared, Snapshot};
 use ckb_store::{ChainDB, ChainStore};
@@ -1463,6 +1463,7 @@ impl SyncShared {
                 // if the target has been reached, delete it
                 let switch = if target == &Unpack::<H256>::unpack(&core::BlockView::hash(&block)) {
                     assume_valid_target.take();
+                    info!("assume valid target reached; CKB will do full verification from now on");
                     Switch::NONE
                 } else {
                     Switch::DISABLE_SCRIPT
diff --git a/util/app-config/Cargo.toml b/util/app-config/Cargo.toml
index 531e5310fd..0fc154c40a 100644
--- a/util/app-config/Cargo.toml
+++ b/util/app-config/Cargo.toml
@@ -24,6 +24,7 @@ ckb-pow = { path = "../../pow", version = "= 0.115.0-pre" }
 ckb-resource = { path = "../../resource", version = "= 0.115.0-pre"}
 ckb-build-info = { path = "../build-info", version = "= 0.115.0-pre" }
 ckb-types = { path = "../types", version = "= 0.115.0-pre" }
+ckb-constant = { path = "../constant", version = "= 0.115.0-pre" }
 secio = { version="0.5.7", package="tentacle-secio" }
 multiaddr = { version="0.3.0", package="tentacle-multiaddr" }
 rand = "0.7"
diff --git a/util/app-config/src/cli.rs b/util/app-config/src/cli.rs
index 8bd4e52788..4a31dd93a8 100644
--- a/util/app-config/src/cli.rs
+++ b/util/app-config/src/cli.rs
@@ -191,26 +191,32 @@ fn run() -> Command {
                 .action(clap::ArgAction::SetTrue)
                 .help("Skip checking the chain spec with the hash stored in the database"),
         ).arg(
-            Arg::new(ARG_OVERWRITE_CHAIN_SPEC)
-                .long(ARG_OVERWRITE_CHAIN_SPEC)
-                .action(clap::ArgAction::SetTrue)
-                .help("Overwrite the chain spec in the database with the present configured chain spec")
-        ).arg(
+        Arg::new(ARG_OVERWRITE_CHAIN_SPEC)
+            .long(ARG_OVERWRITE_CHAIN_SPEC)
+            .action(clap::ArgAction::SetTrue)
+            .help("Overwrite the chain spec in the database with the present configured chain spec")
+    ).arg(
         Arg::new(ARG_ASSUME_VALID_TARGET)
             .long(ARG_ASSUME_VALID_TARGET)
             .action(clap::ArgAction::Set)
             .value_parser(is_h256)
-            .help("This parameter specifies the hash of a block. \
-            When the current height does not reach this block's height, script execution will be disabled, \
-            meaning it will skip the verification of the script content. \
-            \
-            Please note that when this option is enabled, the header will be synchronized to \
-            the highest block currently found. During this period, if the assume valid target is found, \
-            the block download starts; \
-            if the assume valid target is either absent or has a timestamp within 24 hours of the current time, \
-            the target is considered invalid, and the block download proceeds with verification.")
-        ).arg(
-            Arg::new(ARG_INDEXER)
+            .help(format!("This parameter specifies the hash of a block. \
+When the height does not reach this block's height, script execution will be disabled, \
+meaning it will skip the verification of the script content. \n\n\
+Please note that when this option is enabled, the header will be synchronized to \
+the highest block currently found. During this period, if the assume valid target is found, \
+the block download starts; \
+If the assume valid target is either absent or has a timestamp within 24 hours of the current time, \
+the target considered invalid, and the block download proceeds with full verification. \n\n\n\
+default(MainNet): {}\n
+default(TestNet): {}\n\n
+You can explicitly set the value to 0x0000000000000000000000000000000000000000000000000000000000000000 \
+to disable the default behavior and execute full verification for all blocks, \
+",
+                          ckb_constant::default_assume_valid_target::mainnet::DEFAULT_ASSUME_VALID_TARGET,
+                          ckb_constant::default_assume_valid_target::testnet::DEFAULT_ASSUME_VALID_TARGET))
+    ).arg(
+        Arg::new(ARG_INDEXER)
             .long(ARG_INDEXER)
             .action(clap::ArgAction::SetTrue)
             .help("Start the built-in indexer service"),
@@ -352,13 +358,13 @@ fn replay() -> Command {
         ))
         .arg(
             Arg::new(ARG_FROM)
-            .value_parser(clap::value_parser!(u64))
-              .help("Specify profile from block number"),
+                .value_parser(clap::value_parser!(u64))
+                .help("Specify profile from block number"),
         )
         .arg(
             Arg::new(ARG_TO)
-            .value_parser(clap::value_parser!(u64))
-              .help("Specify profile to block number"),
+                .value_parser(clap::value_parser!(u64))
+                .help("Specify profile to block number"),
         )
         .arg(
             Arg::new(ARG_SANITY_CHECK).long(ARG_SANITY_CHECK).action(clap::ArgAction::SetTrue).help("Enable sanity check")
diff --git a/util/app-config/src/lib.rs b/util/app-config/src/lib.rs
index fe3d768f7b..02110816aa 100644
--- a/util/app-config/src/lib.rs
+++ b/util/app-config/src/lib.rs
@@ -18,6 +18,7 @@ pub use args::{
     DaemonArgs, ExportArgs, ImportArgs, InitArgs, MigrateArgs, MinerArgs, PeerIDArgs, ReplayArgs,
     ResetDataArgs, RunArgs, StatsArgs,
 };
+use ckb_logger::info;
 pub use configs::*;
 pub use exit_code::ExitCode;
 #[cfg(feature = "with_sentry")]
@@ -86,9 +87,34 @@ impl Setup {
                 u256!("0x0")
             };
 
-        config.network.sync.assume_valid_target = matches
-            .get_one::<String>(cli::ARG_ASSUME_VALID_TARGET)
-            .and_then(|s| H256::from_str(&s[2..]).ok());
+        let arg_assume_valid_target = matches.get_one::<String>(cli::ARG_ASSUME_VALID_TARGET);
+
+        config.network.sync.assume_valid_target =
+            arg_assume_valid_target.and_then(|s| H256::from_str(&s[2..]).ok());
+        if config.network.sync.assume_valid_target.is_none() {
+            config.network.sync.assume_valid_target = match consensus.id.as_str() {
+                ckb_constant::hardfork::mainnet::CHAIN_SPEC_NAME => Some(
+                    H256::from_str(&ckb_constant::default_assume_valid_target::mainnet::DEFAULT_ASSUME_VALID_TARGET[2..])
+                        .expect("default assume_valid_target for mainnet must be valid"),
+                ),
+                ckb_constant::hardfork::testnet::CHAIN_SPEC_NAME => Some(
+                    H256::from_str(&ckb_constant::default_assume_valid_target::testnet::DEFAULT_ASSUME_VALID_TARGET[2..])
+                        .expect("default assume_valid_target for testnet must be valid"),
+                ),
+                _ => None,
+            };
+        }
+
+        if let Some(ref assume_valid_target) = config.network.sync.assume_valid_target {
+            if assume_valid_target
+                == &H256::from_slice(&[0; 32]).expect("must parse Zero h256 successful")
+            {
+                info!("Disable assume valid target since assume_valid_target is zero");
+                config.network.sync.assume_valid_target = None
+            } else {
+                info!("assume_valid_target set to 0x{}", assume_valid_target);
+            }
+        }
 
         Ok(RunArgs {
             config,
diff --git a/util/constant/src/default_assume_valid_target.rs b/util/constant/src/default_assume_valid_target.rs
new file mode 100644
index 0000000000..ad904e451d
--- /dev/null
+++ b/util/constant/src/default_assume_valid_target.rs
@@ -0,0 +1,31 @@
+/// The mod mainnet and mod testnet's codes are generated
+/// by script: ./devtools/release/update_default_valid_target.sh
+/// Please don't modify them manually.
+///
+
+/// sync config related to mainnet
+pub mod mainnet {
+    /// Default assume valid target for mainnet, expect to be a block 60 days ago.
+    ///
+    /// Need to update when CKB's new release
+    /// in mainnet: the 60 days ago block is:
+    /// height: 12051310
+    /// hash: 0xc394fa4c5e5032c49f3502d4fd8054ead76ff693a54ac90757e441b5119afcaf
+    /// date: Fri Jan 26 04:59:07 PM CST 2024
+    /// you can view this block in https://explorer.nervos.org/block/0xc394fa4c5e5032c49f3502d4fd8054ead76ff693a54ac90757e441b5119afcaf
+    pub const DEFAULT_ASSUME_VALID_TARGET: &str =
+        "0xc394fa4c5e5032c49f3502d4fd8054ead76ff693a54ac90757e441b5119afcaf";
+}
+/// sync config related to testnet
+pub mod testnet {
+    /// Default assume valid target for testnet, expect to be a block 60 days ago.
+    ///
+    /// Need to update when CKB's new release
+    /// in testnet: the 60 days ago block is:
+    /// height: 12069984
+    /// hash: 0xb60b1fbe31f02ad58234dee525400c161c604e851c4ef839e4ef4b9422cfb445
+    /// date: Fri Jan 26 04:59:59 PM CST 2024
+    /// you can view this block in https://pudge.explorer.nervos.org/block/0xb60b1fbe31f02ad58234dee525400c161c604e851c4ef839e4ef4b9422cfb445
+    pub const DEFAULT_ASSUME_VALID_TARGET: &str =
+        "0xb60b1fbe31f02ad58234dee525400c161c604e851c4ef839e4ef4b9422cfb445";
+}
diff --git a/util/constant/src/lib.rs b/util/constant/src/lib.rs
index e0b7376133..b4450a3026 100644
--- a/util/constant/src/lib.rs
+++ b/util/constant/src/lib.rs
@@ -8,3 +8,6 @@ pub mod hardfork;
 pub mod store;
 /// sync constant
 pub mod sync;
+
+/// hardcoded default assume valid target
+pub mod default_assume_valid_target;
diff --git a/util/launcher/src/lib.rs b/util/launcher/src/lib.rs
index 6b18484409..0370339a54 100644
--- a/util/launcher/src/lib.rs
+++ b/util/launcher/src/lib.rs
@@ -219,6 +219,7 @@ impl Launcher {
     pub fn check_assume_valid_target(&mut self, shared: &Shared) {
         if let Some(ref target) = self.args.config.network.sync.assume_valid_target {
             if shared.snapshot().block_exists(&target.pack()) {
+                info!("assume valid target is already in db, CKB will do full verification from now on");
                 self.args.config.network.sync.assume_valid_target.take();
             }
         }


Test patch: