rpc:enhancement and braidpool-cli addition#333
Conversation
|
hi @Sansh2356 Thanks for the review! The PR description has also been updated accordingly. Please let me know if anything else needs adjustment. |
|
ut-ACK have gone through code looks fine, will test it asap. |
zaidmstrr
left a comment
There was a problem hiding this comment.
Thanks for working on this. I reviewed this and found some noticeable points to look at. You can review, implement and further discuss the points below. I give the opinions based on the high-level overview of the PR. More technical go-through might be discussed later on review follow ups.
|
|
||
| #[derive(Debug, Subcommand)] | ||
| #[command(rename_all = "PascalCase")] | ||
| enum Commands { |
There was a problem hiding this comment.
The RPC names here are in PascalCase which is generally not good to have as a JSON-RPC method name. Change these to simple lower_snake_case.
There was a problem hiding this comment.
Agreed this should be changed .
There was a problem hiding this comment.
Updated the RPC method names to use lower_snake_case instead of PascalCase
There was a problem hiding this comment.
You can also remove the underscore from the names. For example getparents getmininginfo etc. As these are easier to type.
There was a problem hiding this comment.
Some of the RPC method names still contain an underscore with them.
|
|
||
| let hash = node | ||
| .parse::<BeadHash>() | ||
| .map_err(|_| ErrorObjectOwned::owned(1, "Invalid bead hash format", None::<()>))?; |
There was a problem hiding this comment.
This error is not propagating to the end user CLI upon passing invalid hex, instead of this the CLI is returning Error: error decoding response body as a common message to all other invalid arguments. This behaviour is same in other RPC methods also.
There was a problem hiding this comment.
Fixed by properly propagating JSON-RPC errors to the CLI using a structured RpcCallError enum, avoiding generic decode errors for cases like invalid hex across all RPC methods.
| async fn get_bead(&self, bead_hash: String) -> Result<Bead, ErrorObjectOwned> { | ||
| let hash = bead_hash | ||
| .parse::<BeadHash>() | ||
| .map_err(|_| ErrorObjectOwned::owned(1, "Invalid bead hash format", None::<()>))?; |
There was a problem hiding this comment.
Same here this is not propagating to the end user CLI upon passing invalid args.
| Commands::GetBead { bead_hash } => ("getbead", json!([bead_hash])), | ||
| Commands::AddBead { bead_data } => ("addbead", json!([bead_data])), | ||
| Commands::GetBeadCount => ("getbeadcount", json!([])), | ||
| Commands::GetCohortCount => ("getcohortcount", json!([])), |
There was a problem hiding this comment.
This command should return int instead of String and this is not related to your change but I think you can change this behaviour in rpc_server.rs::get_cohort_count
| Commands::GetBeadCount => ("getbeadcount", json!([])), | ||
| Commands::GetCohortCount => ("getcohortcount", json!([])), | ||
| Commands::GetTips => ("gettips", json!([])), | ||
| Commands::GetCohort { cohort_id } => ("getcohort", json!([cohort_id])), |
There was a problem hiding this comment.
The name GetCohort is confusing this should be getcohortbyid or similar.
| Commands::GetMiningInfo => ("getmininginfo", json!([])), | ||
| Commands::GetParents { bead_hash } => ("getparents", json!([bead_hash])), | ||
| Commands::GetChildren { bead_hash } => ("getchildren", json!([bead_hash])), | ||
| Commands::GetHwPath { limit } => ("gethwpath", json!([limit])), |
There was a problem hiding this comment.
This name doesn't seem clear GetHwPath. Try to make it more clear or use full spelling, consider using GetHighestWorkPathByCount or similar.
| Ok(children_hashes) | ||
| } | ||
|
|
||
| async fn get_hwpath(&self, limit: u8) -> Result<String, ErrorObjectOwned> { |
There was a problem hiding this comment.
Currently typing the value greater than the available beads doesn't show some error or message to the user; it just shows all the hashes. Consider showing some kind of message to the user, like "The max available count is x" or "The provided count seems too high and does not exist" plus add some more info for the user so they can figure out what to do next.
There was a problem hiding this comment.
Updated to handle this edge case.
When the requested count exceeds the available beads, the RPC now returns a clear error, and the CLI surfaces a user-friendly message (e.g., indicating the maximum available count and why the request failed). This helps users understand what went wrong and how to adjust their input.
| } | ||
| } | ||
|
|
||
| async fn get_braid_info(&self) -> Result<Value, ErrorObjectOwned> { |
There was a problem hiding this comment.
GetBraidInfo showing me orphan_count 5920, which doesn't seem a true value here, but maybe this bug is not related to this code and part of the other file. But please debug and try to find out.
| GetBraidInfo, | ||
|
|
||
| /// Get node information (libp2p PeerID, payout address, miner pubkey, etc.) | ||
| GetNodeInfo { |
There was a problem hiding this comment.
GetNodeInfo is very unclear in passing arguments; it doesn't state what I need to pass along with this method. Please clarify clearly to user what they need to pass with this RPC method.
There was a problem hiding this comment.
Added documentation and clearer argument naming, specifically bead_hash, with inline comments explaining that it identifies which bead’s creator node information is being requested. This should make the expected input and usage explicit to users.
| /// Get statistics about the IPC connection | ||
| GetIpcStats, | ||
|
|
||
| /// Get braid information (similar to getblockchaininfo in bitcoin-cli) |
There was a problem hiding this comment.
Remove similar to getblockchaininfo in bitcoin-cli from here it's unnecessary. You can replace this with some other information that this RPC can return.
Sansh2356
left a comment
There was a problem hiding this comment.
Small nits required kindly check @Shivrajsoni .
165f753 to
de5f994
Compare
|
hi @Sansh2356 @zaidmstrr Thanks for the review! |
There was a problem hiding this comment.
Pull request overview
This PR introduces JSON-RPC server functionality and a command-line interface (braidpool-cli) for interacting with Braidpool nodes. The implementation adds comprehensive RPC endpoints for querying braid data, mining statistics, peer management, and transaction staging, while also providing a proxy mechanism for Bitcoin Core RPC calls.
Changes:
- Added 15+ RPC methods including
getbead,getmininginfo,getpeerinfo,gethighestworkpathbycount, andbitcoinproxy - Created new
braidpool-clipackage with command-line interface for all RPC endpoints - Refactored
PeerManagerand connection mapping visibility to support RPC server queries
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| node/src/rpc_server.rs | Implements RPC server with 15+ methods, Bitcoin RPC proxy, and comprehensive test coverage |
| node/src/main.rs | Integrates RPC server initialization with shared state (braid, peer manager, stratum connections) |
| node/src/peer_manager/mod.rs | Adds get_peers_json() method and makes ip_addr public for RPC access |
| node/src/stratum.rs | Makes downstream_channel_mapping public for RPC server access to miner information |
| node/src/ipc.rs | Adds RPC command channel handling for IPC statistics and transaction removal |
| node/src/cli.rs | Changes rpcuser/rpcpass from Option<String> to String with defaults |
| braidpool-cli/src/main.rs | New CLI tool with command parsing and RPC client implementation |
| braidpool-cli/Cargo.toml | New package dependencies for CLI tool |
| node/Cargo.toml | Adds reqwest dependency for Bitcoin RPC proxy |
| Cargo.toml | Adds braidpool-cli to workspace members |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Use this password for bitcoin RPC | ||
| #[arg(long, default_value = "")] | ||
| pub rpcpass: Option<String>, | ||
| #[arg(long, default_value = "pass")] |
There was a problem hiding this comment.
Using "pass" as the default RPC password is a critical security vulnerability. This weak default password combined with the "root" username creates an easily exploitable attack vector. Remove the default value and require users to explicitly set secure credentials.
| #[arg(long, default_value = "pass")] | |
| #[arg(long)] |
| let rpc_proxy_tx_for_rpc = rpc_proxy_tx; | ||
| let rpc_proxy_rx_for_ipc = rpc_proxy_rx; |
There was a problem hiding this comment.
These intermediate variable names add unnecessary verbosity. The original rpc_proxy_tx and rpc_proxy_rx names are already descriptive. Consider removing these intermediate assignments and using the original names directly where they're needed.
cbea495 to
7c4509a
Compare
Sansh2356
left a comment
There was a problem hiding this comment.
t-ACK @Shivrajsoni you can checkout the co-pilot review if you find any useful otherwise looks good to me .
zaidmstrr
left a comment
There was a problem hiding this comment.
Thanks for implementing and fixing the previously marked issues. Most of the code structure seems solid but there are some more potential issues I found related to performance, structure and user interface.
| let (method, params) = match &cli.commands { | ||
| Commands::GetBead { bead_hash } => ("getbead", json!([bead_hash])), | ||
| Commands::AddBead { bead_data } => ("addbead", json!([bead_data])), | ||
| Commands::GetBeadCount => ("getbeadcount", json!([])), |
There was a problem hiding this comment.
It will be good if this Commands::GetBeadCount return an int instead of String, just do the same thing you did with Commands::GetCohortCount
There was a problem hiding this comment.
fixed it , GetBeadCount and GetCohortCount returns int now instead of string
| Ok(children_hashes) | ||
| } | ||
|
|
||
| async fn get_highest_work_path_by_count(&self, limit: u8) -> Result<String, ErrorObjectOwned> { |
There was a problem hiding this comment.
There are some inconsistent Stringly Typed return types which are creating some structural problems while delivering the response to the user. Currently a client needs to manually double-decode the JSON string inside the JSON response as a result, it shows the output like this:
./target/debug/braidpool-cli get_highest_work_path_by_count 10
"[\"000000000b3ad6bf987cf52981945c72a3c9db445d224d3eb87449ed700262d0\",\"00000000035ee15c78a7a2a588f5ce3df1ed081d83811831d934643b602c2555\",\"000000004d6ef66c16eaa64a297d3dbd91bf21574bb60919f8076bb42c44e913\",\"00000000d4dd3010388ea45036a3a16aaf3831136e918716e469a5d7b31b5d2c\",\"00000000c7368ce0bd30f56dc296750c451e433606eb74d2a1708c9f30130e73\",\"00000000405833a00c55acd14e60009e8a23e698b713abc034395e152a8df293\",\"000000006cb457f27806c757f449aa7d24c2f4d7411718b4c7fa89fc5ca08a9a\",\"00000000f647585406045342e7c23789a7d7f962676033f7441d7b4631ba659b\",\"000000003459c46e416cd4d87adeff3c20de1ce4b92cfc393f48057d9ddd93e8\",\"000000000080b772d9a49dc7f404ea0aba5aae1a99422fc60b2f53f649526cf2\"]"
while the desired way of showing is like this:
./target/debug/braidpool-cli get_highest_work_path_by_count 10
[
"000000000b3ad6bf987cf52981945c72a3c9db445d224d3eb87449ed700262d0",
"00000000035ee15c78a7a2a588f5ce3df1ed081d83811831d934643b602c2555",
"000000004d6ef66c16eaa64a297d3dbd91bf21574bb60919f8076bb42c44e913",
"00000000d4dd3010388ea45036a3a16aaf3831136e918716e469a5d7b31b5d2c",
"00000000c7368ce0bd30f56dc296750c451e433606eb74d2a1708c9f30130e73",
"00000000405833a00c55acd14e60009e8a23e698b713abc034395e152a8df293",
"000000006cb457f27806c757f449aa7d24c2f4d7411718b4c7fa89fc5ca08a9a",
"00000000f647585406045342e7c23789a7d7f962676033f7441d7b4631ba659b",
"000000003459c46e416cd4d87adeff3c20de1ce4b92cfc393f48057d9ddd93e8",
"000000000080b772d9a49dc7f404ea0aba5aae1a99422fc60b2f53f649526cf2"
]
There was a problem hiding this comment.
fixed the inconsistent stringly typed return types by updating the CLI to stop doing the extra decode and to pretty-print the response. That should give the formatted list you described.
| Ok(count as u64) | ||
| } | ||
|
|
||
| async fn get_cohort_by_id(&self, cohort_id: u64) -> Result<String, ErrorObjectOwned> { |
There was a problem hiding this comment.
This function also has the inconsistent Stringly Typed return types problem. The current behaviour is like this:
./target/debug/braidpool-cli get_cohort_by_id 10
"[\"000000002ae201f1836157be9d9334d57f9264bfde07ee4cb5a27ae9564153f2\"]"
| .map_err(|e| ErrorObjectOwned::owned(2, format!("Internal Error: {}", e), None::<()>)) | ||
| } | ||
|
|
||
| async fn get_parents(&self, bead_hash: String) -> Result<String, ErrorObjectOwned> { |
There was a problem hiding this comment.
This function also has the problem of inconsistent Stringly Typed return types. The current behaviour is like this:
./target/debug/braidpool-cli get_parents 000000003459c46e416cd4d8
7adeff3c20de1ce4b92cfc393f48057d9ddd93e8
"[\"00000000f647585406045342e7c23789a7d7f962676033f7441d7b4631ba659b\"]"
|
|
||
| #[derive(Debug, Subcommand)] | ||
| #[command(rename_all = "PascalCase")] | ||
| enum Commands { |
There was a problem hiding this comment.
You can also remove the underscore from the names. For example getparents getmininginfo etc. As these are easier to type.
| }; | ||
|
|
||
| // Build HTTP client with timeout | ||
| let client = reqwest::Client::builder() |
There was a problem hiding this comment.
In bitcoin_proxy and call_bitcoin_rpc_direct, you are creating a new HTTP client for every single request which is not optimal, try to create a client a single time and reuse that for the rest of the session.
There was a problem hiding this comment.
created a bitcoin_proxy http client once at service/session started ,stored it in BitcoinRpcConfig , and reused it , also done same for braidpool_cli http_client , also added timeout of 30 sec
b71f4c3 to
465b516
Compare
zaidmstrr
left a comment
There was a problem hiding this comment.
Thanks for fixing most of the suggested things. But I saw a change which is partially implemented, you can take a look and fix that.
|
|
||
| #[derive(Debug, Subcommand)] | ||
| #[command(rename_all = "PascalCase")] | ||
| enum Commands { |
There was a problem hiding this comment.
Some of the RPC method names still contain an underscore with them.
465b516 to
bd6d9f6
Compare
|
thnx for the review @zaidmstrr , i have implemented the changes |
bd6d9f6 to
9c1cf68
Compare
zaidmstrr
left a comment
There was a problem hiding this comment.
I found few performance optimizations. It will be good if you look at them.
| let mut geo_groups = HashSet::new(); | ||
| let mut inbound_count = 0; | ||
|
|
||
| for info in &connected_peers_info { |
There was a problem hiding this comment.
You're iterating through connected_peers_info twice first to collect the stats and then to build the JSON list.
There was a problem hiding this comment.
I've refactored get_peers_json to make a single pass over self.peers.values().filter(|p| p.connected). In this single loop, both the aggregate statistics (inbound count, total latency for average, geo groups) and the individual peer JSON objects are collected simultaneously.
| } | ||
| } | ||
|
|
||
| if latency_count > 0 { |
There was a problem hiding this comment.
This is redundant and used here and below inside json avg_latency_ms.
| pub fn get_peers_json(&self) -> serde_json::Value { | ||
| use serde_json::json; | ||
|
|
||
| let connected_peers_info: Vec<&PeerInfo> = |
There was a problem hiding this comment.
Instead of allotting this here can you use the direct self.peers.values()?
9c1cf68 to
c355b77
Compare
zaidmstrr
left a comment
There was a problem hiding this comment.
Overall looks good, but there are some small nits. Also please resolve the conflict and push again.
| //result in same peerID leading to OutgoingConnectionError | ||
|
|
||
| // let keypair = identity::Keypair::generate_ed25519(); | ||
|
|
There was a problem hiding this comment.
Thanks for the feedback! I’ve resolved the conflict and addressed the nits. Please take another look.
| let (rpc_proxy_tx, rpc_proxy_rx) = tokio::sync::mpsc::unbounded_channel::<RpcProxyCommand>(); | ||
|
|
||
| // peer_manager_arc is created above and shared between swarm and RPC server | ||
|
|
fix:rustfmt and removing todos
format fix copilot requeste change fixing test
c355b77 to
29f04fe
Compare
zaidmstrr
left a comment
There was a problem hiding this comment.
Code ACK 29f04fe
Thanks for considering the previous suggestions. This looks good to me, although I have suggested one future optimization which we can do, but that is not related to this PR. So you or anyone else can take a look at that.
There was a problem hiding this comment.
Here this is locking the main stratum connection mapping on each RPC call; this can block the critical tasks when someone uses this RPC and stratum tries to update the state on connection mapping, for e.g. adding the new miner, removing the disconnected miner or sending messages to miners. This can be more impactful when someone uses the getminerinfo RPC aggressively. The solution which might be here is to wrap the connection_mapping with RwLock. And I guess this change is out of scope for this PR, so I'm proceeding with this, but this can be a potential future optimization. I think using this lock on connection_mapping is also useful throughout the codebase in many places.
There was a problem hiding this comment.
Thanks for the review @zaidmstrr , I’ll open a follow-up issue and work on it separately
* rpc:enhancement and braidpool-cli addition fix:rustfmt and removing todos * refactor cli names and improve error handling format fix copilot requeste change fixing test * rpc: typed returns, shared HTTP client and also bitcoin client * Optimize JSON output and peer stats calculation * removed redundant newlines and resolved merge conflict

#298
RPC Server and Braidpool-cli implementation
Adds JSON-RPC server for the Braidpool node with endpoints for querying braid data, mining info, peer management, and Bitcoin RPC proxying.
Implemented Methods
Most RPC methods are implemented by reading from shared state (braid, peer manager, stratum connections) and returning the requested data. Methods like
getbead,getbeadcount,gettips,getmininginfo,getpeerinfoetc. query the in-memory braid structure and return JSON responses. Thebitcoinproxymethod forwards requests directly to Bitcoin Core via HTTP JSON-RPC.Methods Not Implemented in this PR
getminerget detailed statistics about a specific miner identified by connection_id/extraonce1.committedtransactionsTransactions committed in beads but not yet mined (might use cmempool IPC) See thisblockstagedtransactionsTransactions in the blocktemplate we're currently mining (might use cmempool IPC) See thisstagetransactionAdd a transaction to our staged list. Can take a txid (from bitcoind) or a full tx.cmempool:<method>proxy IPC calls to cmempoold using @zaidmstrr's prototype https://github.com/zaidmstrr/bitcoin-rpc-multiprocessNote that cmempool is fast-changing (we have to add and remove txs every time we see a new bead to create block templates), so this may be of limited usefulness.