Skip to content

Commit 912e382

Browse files
committed
Ocean: Reduce disk usage (#3045)
* Reduce ScriptUnspent by 100bytes * Reduce ScriptActivity size * Reduce TransactionVout size * Optimize TransactionVin * hid to [u8;32] * Lint
1 parent 48a383c commit 912e382

17 files changed

Lines changed: 323 additions & 101 deletions

lib/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/ain-db/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ bincode.workspace = true
1111
rocksdb.workspace = true
1212
anyhow.workspace = true
1313
num_cpus.workspace = true
14-
14+
log.workspace = true

lib/ain-db/src/lib.rs

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use std::{
2+
collections::BTreeMap,
23
fmt::Debug,
34
iter::Iterator,
45
marker::PhantomData,
56
path::{Path, PathBuf},
67
sync::Arc,
78
};
89
pub mod version;
9-
1010
use anyhow::format_err;
11+
use log::debug;
1112
use rocksdb::{
1213
BlockBasedOptions, Cache, ColumnFamily, ColumnFamilyDescriptor, DBIterator, Direction,
1314
IteratorMode, Options, DB,
@@ -49,10 +50,14 @@ fn get_db_default_options() -> Options {
4950
pub struct Rocks(DB);
5051

5152
impl Rocks {
52-
pub fn open(path: &PathBuf, cf_names: &[&'static str], opts: Option<Options>) -> Result<Self> {
53-
let cf_descriptors = cf_names
54-
.iter()
55-
.map(|cf_name| ColumnFamilyDescriptor::new(*cf_name, Options::default()));
53+
pub fn open(
54+
path: &PathBuf,
55+
cf_names: Vec<(&'static str, Option<Options>)>,
56+
opts: Option<Options>,
57+
) -> Result<Self> {
58+
let cf_descriptors = cf_names.into_iter().map(|(cf_name, opts)| {
59+
ColumnFamilyDescriptor::new(cf_name, opts.unwrap_or_else(Options::default))
60+
});
5661

5762
let db_opts = opts.unwrap_or_else(get_db_default_options);
5863
let db = DB::open_cf_descriptors(&db_opts, path, cf_descriptors)?;
@@ -67,6 +72,10 @@ impl Rocks {
6772
Ok(())
6873
}
6974

75+
pub fn compact(&self) {
76+
self.0.compact_range(None::<&[u8]>, None::<&[u8]>);
77+
}
78+
7079
pub fn cf_handle(&self, cf: &str) -> Result<&ColumnFamily> {
7180
self.0
7281
.cf_handle(cf)
@@ -99,6 +108,66 @@ impl Rocks {
99108
self.0.flush()?;
100109
Ok(())
101110
}
111+
112+
pub fn dump_table_sizes(&self, cf_names: &[&'static str]) -> Result<()> {
113+
let mut stats: BTreeMap<String, (u64, u64, f64)> = BTreeMap::new(); // (size, entries, avg_size)
114+
let mut total_size: u64 = 0;
115+
let mut total_entries: u64 = 0;
116+
117+
for cf_name in cf_names.iter() {
118+
if let Some(cf) = self.0.cf_handle(cf_name) {
119+
let size = self
120+
.0
121+
.property_int_value_cf(cf, "rocksdb.estimate-live-data-size")?
122+
.unwrap_or(0);
123+
let entries = self
124+
.0
125+
.property_int_value_cf(cf, "rocksdb.estimate-num-keys")?
126+
.unwrap_or(0);
127+
let avg_size = if entries > 0 {
128+
size as f64 / entries as f64
129+
} else {
130+
0.0
131+
};
132+
133+
stats.insert(cf_name.to_string(), (size, entries, avg_size));
134+
total_size += size;
135+
total_entries += entries;
136+
}
137+
}
138+
139+
debug!("RocksDB Table Statistics:");
140+
debug!("{:-<80}", "");
141+
debug!(
142+
"{:<30} {:>10} {:>15} {:>15} {:>10}",
143+
"Table Name", "Size (MB)", "Entries", "Avg Size (B)", "%% of Total"
144+
);
145+
debug!("{:-<80}", "");
146+
147+
for (name, (size, entries, avg_size)) in stats.iter() {
148+
let size_mb = *size as f64 / (1024.0 * 1024.0);
149+
let percentage = (*size as f64 / total_size as f64) * 100.0;
150+
debug!(
151+
"{:<30} {:>10.2} {:>15} {:>15.2} {:>9.2}%%",
152+
name, size_mb, entries, avg_size, percentage
153+
);
154+
}
155+
156+
debug!("{:-<80}", "");
157+
let total_avg_size = if total_entries > 0 {
158+
total_size as f64 / total_entries as f64
159+
} else {
160+
0.0
161+
};
162+
debug!(
163+
"Total size: {:.2} MB",
164+
total_size as f64 / (1024.0 * 1024.0)
165+
);
166+
debug!("Total entries: {}", total_entries);
167+
debug!("Overall average entry size: {:.2} bytes", total_avg_size);
168+
169+
Ok(())
170+
}
102171
}
103172

104173
//

lib/ain-evm/src/storage/block_store.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ impl BlockStore {
3333
pub fn new(path: &Path) -> Result<Self> {
3434
let path = path.join("indexes");
3535
fs::create_dir_all(&path)?;
36-
let backend = Arc::new(Rocks::open(&path, &COLUMN_NAMES, None)?);
36+
37+
let cf_with_opts = COLUMN_NAMES
38+
.into_iter()
39+
.zip(std::iter::repeat(None))
40+
.collect::<Vec<_>>();
41+
42+
let backend = Arc::new(Rocks::open(&path, cf_with_opts, None)?);
3743
let store = Self(backend);
3844
store.startup()?;
3945
Ok(store)

lib/ain-ocean/src/api/address.rs

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,12 @@ pub struct ScriptAggregationResponse {
116116
impl From<ScriptAggregation> for ScriptAggregationResponse {
117117
fn from(v: ScriptAggregation) -> Self {
118118
Self {
119-
id: format!("{}{}", hex::encode(v.id.1.to_be_bytes()), v.id.0),
120-
hid: v.hid,
119+
id: format!(
120+
"{}{}",
121+
hex::encode(v.id.1.to_be_bytes()),
122+
hex::encode(v.id.0)
123+
),
124+
hid: hex::encode(v.hid),
121125
block: v.block,
122126
script: ScriptAggregationScriptResponse {
123127
r#type: v.script.r#type,
@@ -162,13 +166,13 @@ pub struct ScriptAggregationAmountResponse {
162166

163167
fn get_latest_aggregation(
164168
ctx: &Arc<AppContext>,
165-
hid: String,
169+
hid: [u8; 32],
166170
) -> Result<Option<ScriptAggregationResponse>> {
167171
let latest = ctx
168172
.services
169173
.script_aggregation
170174
.by_id
171-
.list(Some((hid.clone(), u32::MAX)), SortOrder::Descending)?
175+
.list(Some((hid, u32::MAX)), SortOrder::Descending)?
172176
.take(1)
173177
.take_while(|item| match item {
174178
Ok(((v, _), _)) => v == &hid,
@@ -250,9 +254,32 @@ pub struct ScriptActivityResponse {
250254

251255
impl From<ScriptActivity> for ScriptActivityResponse {
252256
fn from(v: ScriptActivity) -> Self {
257+
let id = match v.type_hex {
258+
ScriptActivityTypeHex::Vin => {
259+
// TODO put vin instead ScriptActivityType
260+
let vin = v.vin.as_ref().unwrap();
261+
format!(
262+
"{}{}{}{}",
263+
hex::encode(v.block.height.to_be_bytes()),
264+
ScriptActivityTypeHex::Vin,
265+
vin.txid,
266+
hex::encode(vin.n.to_be_bytes())
267+
)
268+
}
269+
ScriptActivityTypeHex::Vout => {
270+
let vout = v.vout.as_ref().unwrap();
271+
format!(
272+
"{}{}{}{}",
273+
hex::encode(v.block.height.to_be_bytes()),
274+
ScriptActivityTypeHex::Vout,
275+
v.txid,
276+
hex::encode(vout.n.to_be_bytes())
277+
)
278+
}
279+
};
253280
Self {
254-
id: v.id,
255-
hid: v.hid,
281+
id,
282+
hid: hex::encode(v.hid),
256283
r#type: v.r#type.to_string(),
257284
type_hex: v.type_hex.to_string(),
258285
txid: v.txid,
@@ -327,7 +354,7 @@ async fn list_transactions(
327354
.script_activity
328355
.by_id
329356
.list(
330-
Some((hid.clone(), next.0, next.1, next.2, next.3)),
357+
Some((hid, next.0, next.1, next.2, next.3)),
331358
SortOrder::Descending,
332359
)?
333360
.skip(usize::from(query.next.is_some()))
@@ -361,9 +388,14 @@ pub struct ScriptUnspentResponse {
361388
impl From<ScriptUnspent> for ScriptUnspentResponse {
362389
fn from(v: ScriptUnspent) -> Self {
363390
Self {
364-
id: v.id,
365-
hid: v.hid,
366-
sort: v.sort,
391+
id: format!("{}{}", v.id.0, hex::encode(v.id.1)),
392+
hid: hex::encode(v.hid),
393+
sort: format!(
394+
"{}{}{}",
395+
hex::encode(v.block.height.to_be_bytes()),
396+
v.txid,
397+
hex::encode(v.vout.n.to_be_bytes())
398+
),
367399
block: v.block,
368400
script: ScriptUnspentScriptResponse {
369401
r#type: v.script.r#type,
@@ -425,10 +457,7 @@ async fn list_transaction_unspent(
425457
.services
426458
.script_unspent
427459
.by_id
428-
.list(
429-
Some((hid.clone(), next.0, next.1, next.2)),
430-
SortOrder::Ascending,
431-
)?
460+
.list(Some((hid, next.0, next.1, next.2)), SortOrder::Ascending)?
432461
.skip(usize::from(query.next.is_some()))
433462
.take(query.size)
434463
.take_while(|item| match item {

lib/ain-ocean/src/api/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ pub fn to_script(address: &str, network: Network) -> Result<ScriptBuf> {
133133
Ok(ScriptBuf::from(addr))
134134
}
135135

136-
pub fn address_to_hid(address: &str, network: Network) -> Result<String> {
136+
pub fn address_to_hid(address: &str, network: Network) -> Result<[u8; 32]> {
137137
let script = to_script(address, network)?;
138138
let bytes = script.to_bytes();
139139
Ok(as_sha256(bytes))

lib/ain-ocean/src/api/transactions.rs

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ use std::sync::Arc;
33
use ain_macros::ocean_endpoint;
44
use axum::{extract::Query, routing::get, Extension, Router};
55
use bitcoin::Txid;
6-
use serde::Deserialize;
6+
use serde::{Deserialize, Serialize};
77

88
use super::{path::Path, query::PaginationQuery, response::ApiPagedResponse, AppContext};
99
use crate::{
1010
api::{common::Paginate, response::Response},
1111
error::ApiError,
12-
model::{Transaction, TransactionVin, TransactionVout},
12+
model::{
13+
Transaction, TransactionVin, TransactionVinType, TransactionVinVout, TransactionVout,
14+
TransactionVoutScript,
15+
},
1316
storage::{
1417
InitialKeyProvider, RepositoryOps, SortOrder, TransactionVin as TransactionVinStorage,
1518
},
@@ -30,12 +33,43 @@ async fn get_transaction(
3033
Ok(Response::new(transactions))
3134
}
3235

36+
#[derive(Debug, Serialize)]
37+
struct TransactionVinResponse {
38+
pub id: String,
39+
pub txid: Txid,
40+
pub coinbase: Option<String>,
41+
pub vout: Option<TransactionVinVout>,
42+
pub script: Option<String>,
43+
pub tx_in_witness: Option<Vec<String>>,
44+
pub sequence: i64,
45+
}
46+
47+
impl From<TransactionVin> for TransactionVinResponse {
48+
fn from(v: TransactionVin) -> Self {
49+
let (id, coinbase) = match v.r#type {
50+
TransactionVinType::Coinbase(coinbase) => (format!("{}00", v.txid), Some(coinbase)),
51+
TransactionVinType::Standard((txid, vout)) => {
52+
(format!("{}{}{:x}", v.txid, txid, vout), None)
53+
}
54+
};
55+
Self {
56+
id,
57+
txid: v.txid,
58+
coinbase,
59+
vout: v.vout,
60+
script: v.script,
61+
tx_in_witness: v.tx_in_witness,
62+
sequence: v.sequence,
63+
}
64+
}
65+
}
66+
3367
#[ocean_endpoint]
3468
async fn get_vins(
3569
Path(TransactionId { id }): Path<TransactionId>,
3670
Query(query): Query<PaginationQuery>,
3771
Extension(ctx): Extension<Arc<AppContext>>,
38-
) -> Result<ApiPagedResponse<TransactionVin>> {
72+
) -> Result<ApiPagedResponse<TransactionVinResponse>> {
3973
let next = query
4074
.next
4175
.clone()
@@ -53,7 +87,7 @@ async fn get_vins(
5387
})
5488
.map(|item| {
5589
let (_, v) = item?;
56-
Ok(v)
90+
Ok(TransactionVinResponse::from(v))
5791
})
5892
.collect::<Result<Vec<_>>>()?;
5993

@@ -62,13 +96,37 @@ async fn get_vins(
6296
}))
6397
}
6498

99+
#[derive(Debug, Serialize)]
100+
struct TransactionVoutResponse {
101+
pub id: String,
102+
// pub vout: usize,
103+
pub txid: Txid,
104+
pub n: usize,
105+
pub value: String,
106+
pub token_id: Option<u32>,
107+
pub script: TransactionVoutScript,
108+
}
109+
110+
impl From<TransactionVout> for TransactionVoutResponse {
111+
fn from(v: TransactionVout) -> Self {
112+
Self {
113+
id: format!("{}{:x}", v.txid, v.vout),
114+
txid: v.txid,
115+
n: v.n,
116+
value: v.value,
117+
token_id: v.token_id,
118+
script: v.script,
119+
}
120+
}
121+
}
122+
65123
//get list of vout transaction, by passing id which contains txhash + vout_idx
66124
#[ocean_endpoint]
67125
async fn get_vouts(
68126
Path(TransactionId { id }): Path<TransactionId>,
69127
Query(query): Query<PaginationQuery>,
70128
Extension(ctx): Extension<Arc<AppContext>>,
71-
) -> Result<ApiPagedResponse<TransactionVout>> {
129+
) -> Result<ApiPagedResponse<TransactionVoutResponse>> {
72130
let next = query.next.as_deref().unwrap_or("0").parse::<usize>()?;
73131

74132
let list = ctx
@@ -83,7 +141,7 @@ async fn get_vouts(
83141
})
84142
.map(|item| {
85143
let (_, v) = item?;
86-
Ok(v)
144+
Ok(TransactionVoutResponse::from(v))
87145
})
88146
.collect::<Result<Vec<_>>>()?;
89147

lib/ain-ocean/src/hex_encoder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use sha2::{Digest, Sha256};
22

33
#[must_use]
4-
pub fn as_sha256(bytes: Vec<u8>) -> String {
4+
pub fn as_sha256(bytes: Vec<u8>) -> [u8; 32] {
55
let mut hasher = Sha256::new();
66
hasher.update(bytes);
7-
format!("{:x}", hasher.finalize())
7+
hasher.finalize().into()
88
}

0 commit comments

Comments
 (0)