Skip to content

Commit

Permalink
Pruning related RPC error (#1899)
Browse files Browse the repository at this point in the history
  • Loading branch information
exeokan authored Feb 18, 2025
1 parent 92c71fb commit c3adb1d
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 39 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 48 additions & 17 deletions crates/evm/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,9 @@ impl<C: sov_modules_api::Context> Evm<C> {
Err(err) => return Err(err.into()),
};

self.check_if_l2_block_pruned(block_number, working_set)
.map_err(EthApiError::from)?;

let block = self
.blocks_rlp
.get(block_number as usize, &mut working_set.accessory_state())
Expand Down Expand Up @@ -1631,23 +1634,30 @@ impl<C: sov_modules_api::Context> Evm<C> {
) -> Result<Option<SealedBlock>, EthApiError> {
// safe, finalized, and pending are not supported
match block_number {
Some(BlockNumberOrTag::Number(block_number)) => Ok(self
.blocks_rlp
.get(block_number as usize, &mut working_set.accessory_state())),
Some(BlockNumberOrTag::Earliest) => Ok(Some(
self.blocks_rlp
.get(0, &mut working_set.accessory_state())
.or_else(|| {
// upgrading from v0.5.7 to v0.6+ requires a codec change
// this only applies to the sequencer
// which will only query the genesis block and the head block
// right after the upgrade
self.blocks
.get(0, &mut working_set.accessory_state())
.map(Into::into)
})
.expect("Genesis block must be set"),
)),
Some(BlockNumberOrTag::Number(block_number)) => {
self.check_if_l2_block_pruned(block_number, working_set)?;

Ok(self
.blocks_rlp
.get(block_number as usize, &mut working_set.accessory_state()))
}
Some(BlockNumberOrTag::Earliest) => {
self.check_if_l2_block_pruned(0, working_set)?;
Ok(Some(
self.blocks_rlp
.get(0, &mut working_set.accessory_state())
.or_else(|| {
// upgrading from v0.5.7 to v0.6+ requires a codec change
// this only applies to the sequencer
// which will only query the genesis block and the head block
// right after the upgrade
self.blocks
.get(0, &mut working_set.accessory_state())
.map(Into::into)
})
.expect("Genesis block must be set"),
))
}
Some(BlockNumberOrTag::Latest) => Ok(Some(
self.blocks_rlp
.last(&mut working_set.accessory_state())
Expand Down Expand Up @@ -1689,6 +1699,10 @@ impl<C: sov_modules_api::Context> Evm<C> {
Some(BlockId::Number(block_num)) => {
match block_num {
BlockNumberOrTag::Number(num) => {
if num != 0 {
// state at genesis block is being preserved
self.check_if_l2_block_pruned(num, working_set)?;
}
let curr_block_number = self
.blocks_rlp
.last(&mut working_set.accessory_state())
Expand Down Expand Up @@ -1723,6 +1737,23 @@ impl<C: sov_modules_api::Context> Evm<C> {

Ok(())
}

/// Returns `ProviderError::StateAtBlockPruned` if the state at the given block number is pruned
fn check_if_l2_block_pruned(
&self,
block_number: u64,
working_set: &mut WorkingSet<C::Storage>,
) -> Result<(), ProviderError> {
if let Some(last_pruned_l2_height) = working_set
.get_last_pruned_l2_height()
.expect("Failed to get last pruned l2 height")
{
if block_number <= last_pruned_l2_height {
return Err(ProviderError::StateAtBlockPruned(block_number));
}
}
Ok(())
}
}

// modified from: https://github.com/paradigmxyz/reth/blob/cc576bc8690a3e16e6e5bf1cbbbfdd029e85e3d4/crates/rpc/rpc/src/eth/api/transactions.rs#L849
Expand Down
14 changes: 14 additions & 0 deletions crates/sovereign-sdk/full-node/db/sov-db/src/ledger_db/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ const MAX_BATCHES_PER_REQUEST: u64 = 20;
/// The maximum number of soft confirmations that can be requested in a single RPC range query
const MAX_SOFT_CONFIRMATIONS_PER_REQUEST: u64 = 20;

fn check_if_l2_block_pruned(ledger_db: &LedgerDB, l2_height: u64) -> Result<(), anyhow::Error> {
let last_pruned_l2_height = ledger_db.get_last_pruned_l2_height()?;
if let Some(last_pruned_l2_height) = last_pruned_l2_height {
if l2_height <= last_pruned_l2_height {
anyhow::bail!("Soft confirmation at height {} has been pruned.", l2_height);
}
}
Ok(())
}

use super::{L2GenesisStateRoot, LedgerDB, ProofsBySlotNumberV2, SharedLedgerOps};

impl LedgerRpcProvider for LedgerDB {
Expand All @@ -26,6 +36,8 @@ impl LedgerRpcProvider for LedgerDB {
let batch_num = self.resolve_soft_confirmation_identifier(batch_id)?;
Ok(match batch_num {
Some(num) => {
check_if_l2_block_pruned(self, num.0)?;

if let Some(stored_batch) = self.db.get::<SoftConfirmationByNumber>(&num)? {
Some(stored_batch.try_into()?)
} else {
Expand Down Expand Up @@ -93,6 +105,8 @@ impl LedgerRpcProvider for LedgerDB {
&self,
l2_height: u64,
) -> Result<sov_rollup_interface::rpc::SoftConfirmationStatus, anyhow::Error> {
check_if_l2_block_pruned(self, l2_height)?;

if self
.db
.get::<SoftConfirmationByNumber>(&SoftConfirmationNumber(l2_height))
Expand Down
11 changes: 10 additions & 1 deletion crates/sovereign-sdk/full-node/db/sov-db/src/native_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use sov_schema_db::transaction::DbTransaction;
use sov_schema_db::{SchemaBatch, DB};

use crate::rocks_db_config::RocksdbConfig;
use crate::schema::tables::{ModuleAccessoryState, NATIVE_TABLES};
use crate::schema::tables::{LastPrunedL2Height, ModuleAccessoryState, NATIVE_TABLES};
use crate::schema::types::StateKeyRef;

/// Specifies a particular version of the Accessory state.
Expand Down Expand Up @@ -73,6 +73,15 @@ impl NativeDB {
}
}

/// Get the last pruned l2 height
pub fn get_last_pruned_l2_height(&self) -> anyhow::Result<Option<u64>> {
let found = self.db.get_prev::<LastPrunedL2Height>(&())?;
match found {
Some((_, value)) => Ok(Some(value)),
None => Ok(None),
}
}

/// Sets a sequence of key-value pairs in the [`NativeDB`]. The write is atomic.
pub fn set_values(
&self,
Expand Down
36 changes: 35 additions & 1 deletion crates/sovereign-sdk/full-node/db/sov-db/src/schema/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ pub const LEDGER_TABLES: &[&str] = &[
/// A list of all tables used by the NativeDB. These tables store
/// "accessory" state only accessible from a native execution context, to be
/// used for JSON-RPC and other tooling.
pub const NATIVE_TABLES: &[&str] = &[ModuleAccessoryState::table_name()];
pub const NATIVE_TABLES: &[&str] = &[
ModuleAccessoryState::table_name(),
LastPrunedL2Height::table_name(),
];

/// Macro to define a table that implements [`sov_schema_db::Schema`].
/// KeyCodec<Schema> and ValueCodec<Schema> must be implemented separately.
Expand Down Expand Up @@ -559,6 +562,11 @@ define_table_without_codec!(
(ModuleAccessoryState) (AccessoryKey, Version) => AccessoryStateValue
);

define_table_without_codec!(
/// last pruned l2 height
(LastPrunedL2Height) () => u64
);

impl KeyEncoder<ModuleAccessoryState> for (AccessoryKey, Version) {
fn encode_key(&self) -> sov_schema_db::schema::Result<Vec<u8>> {
let mut out = Vec::with_capacity(self.0.len() + std::mem::size_of::<Version>() + 8);
Expand All @@ -573,12 +581,24 @@ impl KeyEncoder<ModuleAccessoryState> for (AccessoryKey, Version) {
}
}

impl KeyEncoder<LastPrunedL2Height> for () {
fn encode_key(&self) -> sov_schema_db::schema::Result<Vec<u8>> {
Ok(vec![])
}
}

impl SeekKeyEncoder<ModuleAccessoryState> for (AccessoryKey, Version) {
fn encode_seek_key(&self) -> sov_schema_db::schema::Result<Vec<u8>> {
<(Vec<u8>, u64) as KeyEncoder<ModuleAccessoryState>>::encode_key(self)
}
}

impl SeekKeyEncoder<LastPrunedL2Height> for () {
fn encode_seek_key(&self) -> sov_schema_db::schema::Result<Vec<u8>> {
<() as KeyEncoder<LastPrunedL2Height>>::encode_key(self)
}
}

impl KeyDecoder<ModuleAccessoryState> for (AccessoryKey, Version) {
fn decode_key(data: &[u8]) -> sov_schema_db::schema::Result<Self> {
let mut cursor = std::io::Cursor::new(data);
Expand All @@ -588,6 +608,11 @@ impl KeyDecoder<ModuleAccessoryState> for (AccessoryKey, Version) {
}
}

impl KeyDecoder<LastPrunedL2Height> for () {
fn decode_key(_data: &[u8]) -> sov_schema_db::schema::Result<Self> {
Ok(())
}
}
impl ValueCodec<ModuleAccessoryState> for AccessoryStateValue {
fn encode_value(&self) -> sov_schema_db::schema::Result<Vec<u8>> {
borsh::to_vec(self).map_err(CodecError::from)
Expand All @@ -597,3 +622,12 @@ impl ValueCodec<ModuleAccessoryState> for AccessoryStateValue {
Ok(BorshDeserialize::deserialize_reader(&mut &data[..])?)
}
}
impl ValueCodec<LastPrunedL2Height> for u64 {
fn encode_value(&self) -> sov_schema_db::schema::Result<Vec<u8>> {
borsh::to_vec(self).map_err(CodecError::from)
}

fn decode_value(data: &[u8]) -> sov_schema_db::schema::Result<Self> {
Ok(BorshDeserialize::deserialize_reader(&mut &data[..])?)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ pub trait Storage: Clone {
/// hard clone the storage to not overwrite the version of cloned
/// storage.
fn clone_with_version(&self, version: Version) -> Self;

/// Get the last pruned l2 height. Blanket implemented to return [`Ok(None)`].
fn get_last_pruned_l2_height(&self) -> Result<Option<u64>, anyhow::Error> {
Ok(None)
}
}

/// Used only in tests.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,11 @@ impl<S: Storage> WorkingSet<S> {
// First inner is `RevertableWriter` and second inner is actually a `Storage` instance
self.delta.storage.get_root_hash(version)
}

/// Get the last pruned L2 height.
pub fn get_last_pruned_l2_height(&mut self) -> Result<Option<u64>, anyhow::Error> {
self.delta.storage.get_last_pruned_l2_height()
}
}

impl<S: Storage> StateReaderAndWriter for WorkingSet<S> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ impl Storage for ProverStorage {
committable: false,
}
}

/// Get the last pruned L2 height from the native db.
fn get_last_pruned_l2_height(&self) -> Result<Option<u64>, anyhow::Error> {
self.native_db.get_last_pruned_l2_height()
}
}

impl NativeStorage for ProverStorage {
Expand Down
2 changes: 2 additions & 0 deletions crates/storage-ops/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ tokio-util = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
sov-prover-storage-manager = { path = "../sovereign-sdk/full-node/sov-prover-storage-manager" }
sov-rollup-interface = { path = "../../crates/sovereign-sdk/rollup-interface", features = ["testing"] }
sov-state = { path = "../sovereign-sdk/module-system/sov-state", features = ["native"] }
tempfile = { workspace = true }
7 changes: 5 additions & 2 deletions crates/storage-ops/src/pruning/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::sync::Arc;

use futures::future;
use serde::{Deserialize, Serialize};
use sov_db::schema::tables::LastPrunedBlock;
use sov_db::schema::tables::{LastPrunedBlock, LastPrunedL2Height};
use tracing::info;
use types::PruningNodeType;

Expand Down Expand Up @@ -61,7 +61,10 @@ impl Pruner {

pub fn store_last_pruned_l2_height(&self, last_pruned_l2_height: u64) -> anyhow::Result<()> {
self.ledger_db
.put::<LastPrunedBlock>(&(), &last_pruned_l2_height)
.put::<LastPrunedBlock>(&(), &last_pruned_l2_height)?;

self.native_db
.put::<LastPrunedL2Height>(&(), &last_pruned_l2_height)
}

pub fn should_prune(&self, last_pruned_l2_height: u64, current_l2_height: u64) -> Option<u64> {
Expand Down
60 changes: 42 additions & 18 deletions crates/storage-ops/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use sov_db::schema::types::{SlotNumber, SoftConfirmationNumber};
use sov_db::state_db::StateDB;
use sov_rollup_interface::mmr::MMRGuest;
use sov_schema_db::DB;
use sov_state::Storage;
use tokio::sync::broadcast;
use tokio_util::sync::CancellationToken;

Expand All @@ -29,33 +30,56 @@ use crate::pruning::{Pruner, PrunerService, PruningConfig};
#[tokio::test(flavor = "multi_thread")]
async fn test_pruning_simple_run() {
let tmpdir = tempfile::tempdir().unwrap();
let (sender, receiver) = broadcast::channel(1);
let cancellation_token = CancellationToken::new();

let rocksdb_config = RocksdbConfig::new(tmpdir.path(), None, None);
let ledger_db = LedgerDB::with_config(&rocksdb_config).unwrap();
let native_db = NativeDB::setup_schema_db(&rocksdb_config).unwrap();
let state_db = StateDB::setup_schema_db(&rocksdb_config).unwrap();
{
let (sender, receiver) = broadcast::channel(1);
let cancellation_token = CancellationToken::new();

let native_db = NativeDB::setup_schema_db(&rocksdb_config).unwrap();
let state_db = StateDB::setup_schema_db(&rocksdb_config).unwrap();

let pruner = Pruner::new(
PruningConfig { distance: 5 },
ledger_db.inner(),
Arc::new(state_db),
Arc::new(native_db),
);
let pruner_service = PrunerService::new(pruner, 0, receiver);

tokio::spawn(pruner_service.run(PruningNodeType::Sequencer, cancellation_token.clone()));

let pruner = Pruner::new(
PruningConfig { distance: 5 },
ledger_db.inner(),
Arc::new(state_db),
Arc::new(native_db),
);
let pruner_service = PrunerService::new(pruner, 0, receiver);
sleep(Duration::from_secs(1));

tokio::spawn(pruner_service.run(PruningNodeType::Sequencer, cancellation_token.clone()));
for i in 1..=10 {
let _ = sender.send(i);
}

sleep(Duration::from_secs(1));
sleep(Duration::from_secs(1));

for i in 1..=10 {
let _ = sender.send(i);
cancellation_token.cancel();
}
tokio::time::sleep(Duration::from_secs(1)).await;

sleep(Duration::from_secs(1));
let storage_manager =
sov_prover_storage_manager::ProverStorageManager::new(sov_state::Config {
path: tmpdir.path().to_path_buf(),
db_max_open_files: None,
})
.unwrap();
let finalized_storage = storage_manager.create_final_view_storage();

let native_height = finalized_storage
.get_last_pruned_l2_height()
.unwrap()
.expect("Last pruned L2 height should be set");
let ledger_height = ledger_db
.get_last_pruned_l2_height()
.unwrap()
.expect("Last pruned L2 height should be set");

cancellation_token.cancel();
assert_eq!(native_height, 5);
assert_eq!(ledger_height, 5);
}

#[test]
Expand Down

0 comments on commit c3adb1d

Please sign in to comment.