Skip to content

Commit 8162cfb

Browse files
feat: Add command to dump cbor hex of block
1 parent b74ff28 commit 8162cfb

File tree

10 files changed

+737
-465
lines changed

10 files changed

+737
-465
lines changed

Cargo.lock

Lines changed: 494 additions & 453 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cncli"
3-
version = "6.6.0"
3+
version = "6.7.0"
44
authors = ["Andrew Westberg <andrewwestberg@gmail.com>"]
55
edition = "2021"
66
build = "build.rs"
@@ -16,19 +16,19 @@ byteorder = "1.5"
1616
#pallas-math = { git = "https://github.com/txpipe/pallas", rev = "b641c4e6862be447336429878f4e0a57a2281588" }
1717
#pallas-network = { git = "https://github.com/txpipe/pallas", rev = "b641c4e6862be447336429878f4e0a57a2281588" }
1818
#pallas-traverse = { git = "https://github.com/txpipe/pallas", rev = "b641c4e6862be447336429878f4e0a57a2281588" }
19-
pallas-crypto = "0.32.1"
20-
pallas-math = "0.32.1"
21-
pallas-network = "0.32.1"
22-
pallas-traverse = "0.32.1"
23-
amaru-ouroboros = { git = "https://github.com/pragma-org/amaru", rev = "981381fb6bb980f27c244e9f80765f0d653bb212" }
19+
pallas-crypto = "0.33.0"
20+
pallas-math = "0.33.0"
21+
pallas-network = "0.33.0"
22+
pallas-traverse = "0.33.0"
23+
amaru-ouroboros = { git = "https://github.com/pragma-org/amaru", rev = "8a4f994" }
2424
chrono = "0.4"
2525
chrono-tz = "0.10"
2626
futures = "0.3"
2727
hex = "0.4"
2828
#malachite-base = "0.4.16"
2929
#malachite = "0.4.16"
30-
minicbor = { version = "0.26", features = ["std"] }
31-
redb = "2.6.1"
30+
minicbor = { version = "0.25.1", features = ["std", "half", "derive"] }
31+
redb = "2.6.3"
3232
regex = "1.11"
3333
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls-webpki-roots", "rustls-tls", "json", "gzip", "deflate"] }
3434
rusqlite = { version = "0.37", features = ["bundled"] }

USAGE.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,4 +689,29 @@ OPTIONS:
689689
--network-magic <network-magic> network magic. [default: 764824073]
690690
--output-file <output-file> The name of the output file (CSV format) [default: mark.csv]
691691
--socket-path <socket-path> cardano-node socket path
692+
```
693+
694+
### Dump Block Command
695+
696+
This command dump the raw cbor hex of a block given the previous block's hash and slot number.
697+
698+
#### Retrieve the raw cbor hex of a block
699+
700+
```bash
701+
$ cncli dump-block --help
702+
cncli-dump-block 6.7.0
703+
704+
USAGE:
705+
cncli dump-block [OPTIONS] --host <host> --intersect-hash <intersect-hash> --intersect-slot <intersect-slot>
706+
707+
FLAGS:
708+
--help Prints help information
709+
-V, --version Prints version information
710+
711+
OPTIONS:
712+
-h, --host <host> cardano-node hostname to connect to
713+
--intersect-hash <intersect-hash> Block hash of the intersect point (hex)
714+
--intersect-slot <intersect-slot> Slot number of the intersect point
715+
--network-magic <network-magic> network magic. [default: 764824073]
716+
-p, --port <port>
692717
```

src/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::thread::JoinHandle;
77

88
use structopt::StructOpt;
99

10+
use crate::nodeclient::dumpblock;
1011
use crate::nodeclient::leaderlog::handle_error;
1112
use crate::nodeclient::sync::pooltool;
1213
use crate::nodeclient::sync::pooltool::PooltoolConfig;
@@ -59,6 +60,18 @@ pub enum Command {
5960
)]
6061
db: PathBuf,
6162
},
63+
DumpBlock {
64+
#[structopt(long, help = "Slot number of the intersect point")]
65+
intersect_slot: u64,
66+
#[structopt(long, help = "Block hash of the intersect point (hex)")]
67+
intersect_hash: String,
68+
#[structopt(short, long, help = "cardano-node hostname to connect to")]
69+
host: String,
70+
#[structopt(short, long, default_value = "3001", help = "cardano-node port")]
71+
port: u16,
72+
#[structopt(long, default_value = "764824073", help = "network magic.")]
73+
network_magic: u64,
74+
},
6275
Sync {
6376
#[structopt(
6477
parse(from_os_str),
@@ -335,6 +348,25 @@ pub async fn start(cmd: Command) {
335348
Command::Validate { ref db, ref hash } => {
336349
validate::validate_block(db, hash.as_str());
337350
}
351+
Command::DumpBlock {
352+
ref intersect_slot,
353+
ref intersect_hash,
354+
ref host,
355+
ref port,
356+
ref network_magic,
357+
} => {
358+
if let Err(error) = dumpblock::run(nodeclient::dumpblock::Args {
359+
intersect_slot: *intersect_slot,
360+
intersect_hash: intersect_hash.clone(),
361+
host: host.clone(),
362+
port: *port,
363+
network_magic: *network_magic,
364+
})
365+
.await
366+
{
367+
handle_error(error);
368+
}
369+
}
338370
Command::Sync {
339371
ref db,
340372
ref host,

src/nodeclient/blockstore/redb.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ impl RedbBlockStore {
348348
fn redb_load_blocks(&mut self) -> Result<Vec<(u64, Vec<u8>)>, Error> {
349349
let read_tx = self.db.begin_read()?;
350350
// get slot_number and hash from chain table ordering by slot_number descending where orphaned is false
351-
// limit the result to 33 records
351+
// limit the result to 262145 records
352352
let chain_table = read_tx.open_table(CHAIN_TABLE)?;
353353
let mut chain_iter = chain_table.iter()?;
354354
let mut blocks: Vec<(u64, Vec<u8>)> = Vec::new();
@@ -361,7 +361,7 @@ impl RedbBlockStore {
361361
let slot_number = chain_record.slot_number;
362362
let hash = chain_record.hash.clone();
363363
blocks.push((slot_number, hash));
364-
if blocks.len() >= 33 {
364+
if blocks.len() >= 262145 {
365365
break;
366366
}
367367
}

src/nodeclient/blockstore/sqlite.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ impl SqLiteBlockStore {
312312
fn sql_load_blocks(&mut self) -> Result<Vec<(u64, Vec<u8>)>, Error> {
313313
let db = &self.db;
314314
let mut stmt = db
315-
.prepare("SELECT slot_number, hash FROM (SELECT slot_number, hash, orphaned FROM chain ORDER BY slot_number DESC LIMIT 100) WHERE orphaned = 0 ORDER BY slot_number DESC LIMIT 33;")?;
315+
.prepare("SELECT slot_number, hash FROM (SELECT slot_number, hash, orphaned FROM chain ORDER BY slot_number DESC LIMIT 524289) WHERE orphaned = 0 ORDER BY slot_number DESC LIMIT 262145;")?;
316316
let blocks = stmt.query_map([], |row| {
317317
let slot_result: Result<u64, rusqlite::Error> = row.get(0);
318318
let hash_result: Result<String, rusqlite::Error> = row.get(1);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use pallas_network::miniprotocols::chainsync::HeaderContent;
2+
use pallas_traverse::MultiEraHeader;
3+
4+
pub fn header_hash(header: &HeaderContent) -> Result<Vec<u8>, pallas_traverse::Error> {
5+
let subtag = header.byron_prefix.map(|(tag, _)| tag);
6+
let multi = MultiEraHeader::decode(header.variant, subtag, &header.cbor)?;
7+
Ok(multi.hash().to_vec())
8+
}
9+
10+
pub fn extract_slot(header: &HeaderContent) -> Result<u64, pallas_traverse::Error> {
11+
let subtag = header.byron_prefix.map(|(tag, _)| tag);
12+
let multi = MultiEraHeader::decode(header.variant, subtag, &header.cbor)?;
13+
Ok(multi.slot())
14+
}

src/nodeclient/dumpblock/mod.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use hex::ToHex;
2+
use pallas_network::facades::{KeepAliveLoop, PeerClient, DEFAULT_KEEP_ALIVE_INTERVAL_SEC};
3+
use pallas_network::miniprotocols::chainsync::{HeaderContent, NextResponse};
4+
use pallas_network::miniprotocols::{
5+
blockfetch, chainsync, handshake, keepalive, txsubmission, Point, PROTOCOL_N2N_BLOCK_FETCH,
6+
PROTOCOL_N2N_CHAIN_SYNC, PROTOCOL_N2N_HANDSHAKE, PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_TX_SUBMISSION,
7+
};
8+
use pallas_network::multiplexer::{Bearer, Plexer};
9+
use std::io;
10+
use std::net::ToSocketAddrs;
11+
use std::time::Duration;
12+
use structopt::StructOpt;
13+
use thiserror::Error;
14+
use tracing::debug;
15+
16+
use crate::nodeclient::dumpblock::hash_utils::{extract_slot, header_hash};
17+
18+
mod hash_utils;
19+
20+
#[derive(Error, Debug)]
21+
pub enum DumpBlockError {
22+
#[error("Invalid hex: {0}")]
23+
InvalidHex(#[from] hex::FromHexError),
24+
25+
#[error("IO error: {0}")]
26+
Io(#[from] io::Error),
27+
28+
#[error("Unable to resolve host")]
29+
UnableToResolveHost,
30+
31+
#[error("Handshake error: {0}")]
32+
Handshake(String),
33+
34+
#[error("Handshake rejected: {0:?}")]
35+
HandshakeRejected(String),
36+
37+
#[error("Chain sync error: {0}")]
38+
ChainSync(#[from] chainsync::ClientError),
39+
40+
#[error("Block fetch error: {0}")]
41+
BlockFetch(#[from] blockfetch::ClientError),
42+
43+
#[error("Pallas traverse error: {0}")]
44+
PallasTraverse(#[from] pallas_traverse::Error),
45+
46+
#[error("Intersection not found")]
47+
IntersectionNotFound,
48+
49+
#[error("Unexpected await from chainsync")]
50+
UnexpectedAwait,
51+
}
52+
53+
#[derive(Debug, StructOpt)]
54+
pub struct Args {
55+
#[structopt(long, help = "Slot number of the intersect point")]
56+
pub intersect_slot: u64,
57+
#[structopt(long, help = "Block hash of the intersect point (hex)")]
58+
pub intersect_hash: String,
59+
#[structopt(short, long, help = "cardano-node hostname to connect to")]
60+
pub host: String,
61+
#[structopt(short, long, default_value = "3001", help = "cardano-node port")]
62+
pub port: u16,
63+
#[structopt(long, default_value = "764824073", help = "network magic.")]
64+
pub network_magic: u64,
65+
}
66+
67+
pub async fn run(args: Args) -> Result<(), DumpBlockError> {
68+
let point = Point::Specific(args.intersect_slot, hex::decode(&args.intersect_hash)?);
69+
70+
let addr = format!("{}:{}", args.host, args.port)
71+
.to_socket_addrs()?
72+
.next()
73+
.ok_or(DumpBlockError::UnableToResolveHost)?;
74+
75+
let bearer = Bearer::connect_tcp(addr).await?;
76+
let mut plexer = Plexer::new(bearer);
77+
78+
let hs_channel = plexer.subscribe_client(PROTOCOL_N2N_HANDSHAKE);
79+
let cs_channel = plexer.subscribe_client(PROTOCOL_N2N_CHAIN_SYNC);
80+
let bf_channel = plexer.subscribe_client(PROTOCOL_N2N_BLOCK_FETCH);
81+
let ka_channel = plexer.subscribe_client(PROTOCOL_N2N_KEEP_ALIVE);
82+
let txsub_channel = plexer.subscribe_client(PROTOCOL_N2N_TX_SUBMISSION);
83+
84+
let keepalive = keepalive::Client::new(ka_channel);
85+
86+
let plexer = plexer.spawn();
87+
88+
let mut handshake = handshake::Client::new(hs_channel);
89+
let versions = handshake::n2n::VersionTable::v7_and_above(args.network_magic);
90+
let confirmation = handshake
91+
.handshake(versions)
92+
.await
93+
.map_err(|e| DumpBlockError::Handshake(format!("{e:?}")))?;
94+
95+
let block_hex = match confirmation {
96+
handshake::Confirmation::Accepted(_, _) => {
97+
let keepalive =
98+
KeepAliveLoop::client(keepalive, Duration::from_secs(DEFAULT_KEEP_ALIVE_INTERVAL_SEC)).spawn();
99+
let peer = PeerClient {
100+
plexer,
101+
keepalive,
102+
chainsync: chainsync::Client::new(cs_channel),
103+
blockfetch: blockfetch::Client::new(bf_channel),
104+
txsubmission: txsubmission::Client::new(txsub_channel),
105+
};
106+
let PeerClient {
107+
mut chainsync,
108+
mut blockfetch,
109+
plexer,
110+
keepalive: _keepalive,
111+
txsubmission: _txsubmission,
112+
} = peer;
113+
let res = fetch_block(&mut chainsync, &mut blockfetch, point).await;
114+
plexer.abort().await;
115+
res
116+
}
117+
handshake::Confirmation::Rejected(reason) => {
118+
return Err(DumpBlockError::HandshakeRejected(format!("{reason:?}")));
119+
}
120+
handshake::Confirmation::QueryReply(_) => {
121+
return Err(DumpBlockError::HandshakeRejected("Unexpected QueryReply".to_string()));
122+
}
123+
}?;
124+
125+
println!("{block_hex}");
126+
Ok(())
127+
}
128+
129+
async fn fetch_block(
130+
chainsync: &mut chainsync::Client<HeaderContent>,
131+
blockfetch: &mut blockfetch::Client,
132+
point: Point,
133+
) -> Result<String, DumpBlockError> {
134+
let (intersection, _) = chainsync.find_intersect(vec![point.clone()]).await?;
135+
136+
let intersect_point = intersection.ok_or(DumpBlockError::IntersectionNotFound)?;
137+
debug!(?intersect_point, "intersected");
138+
139+
let block_point = loop {
140+
match chainsync.request_next().await? {
141+
NextResponse::RollForward(header, _) => break point_from_header(&header)?,
142+
NextResponse::RollBackward(_, _) => {
143+
debug!("received RollBackward, requesting next block");
144+
continue;
145+
}
146+
NextResponse::Await => return Err(DumpBlockError::UnexpectedAwait),
147+
}
148+
};
149+
150+
let body = blockfetch.fetch_single(block_point).await?;
151+
152+
Ok(body.encode_hex::<String>())
153+
}
154+
155+
fn point_from_header(header: &HeaderContent) -> Result<Point, DumpBlockError> {
156+
let slot = extract_slot(header)?;
157+
let hash = header_hash(header)?;
158+
Ok(Point::Specific(slot, hash))
159+
}

src/nodeclient/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub(crate) mod blockstore;
2+
pub(crate) mod dumpblock;
23
pub(crate) mod leaderlog;
34
pub(crate) mod ping;
45
pub(crate) mod sign;

src/nodeclient/sync/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ fn get_intersect_blocks(block_store: &mut Box<dyn BlockStore + Send>) -> Result<
268268
/* Classic sync: Use blocks from store if available. */
269269
let blocks = block_store.load_blocks()?;
270270
for (i, (slot, hash)) in blocks.iter().enumerate() {
271-
// all powers of 2 including 0th element 0, 2, 4, 8, 16, 32
271+
// all powers of 2 including 0th element 0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144
272272
if (i == 0) || ((i > 1) && (i & (i - 1) == 0)) {
273273
chain_blocks.push(Point::Specific(*slot, hash.clone()));
274274
}

0 commit comments

Comments
 (0)