Skip to content

feat(rpc): get block reward by hash#927

Open
IzioDev wants to merge 3 commits into
kaspanet:masterfrom
IzioDev:feat/block-reward
Open

feat(rpc): get block reward by hash#927
IzioDev wants to merge 3 commits into
kaspanet:masterfrom
IzioDev:feat/block-reward

Conversation

@IzioDev
Copy link
Copy Markdown
Collaborator

@IzioDev IzioDev commented Mar 25, 2026

Given a block hash, returns its reward information.

Behaviors:

  • If b has not been merged (orphan), color will be unknown
  • If b has been merged and is part of the red mergeset, color is red
  • If b has been merged and is of the blue mergeset, get total paid reward (subsidy + collected fees)
    • additionally, it b is a chain block / merging block, get red rewards

User persona: pool / bridge / solo miner

This is intended to replace complex systems (often inefficient) built on top of current RPC endpoints, specifically: GetBlocks.

Input:

pub struct GetCurrentBlockColorRequest {
    pub hash: RpcHash,
}

Output:

pub struct GetBlockRewardInfoResponse {
    pub header: RpcHeader,
    pub block_color: RpcBlockColor,
    /// guaranteed to be populated when block color != Unknown
    pub confirmation_count: Option<u64>,
    /// guaranteed to be populated when block color != Unknown
    pub merging_chain_block_hash: Option<RpcHash>,
    /// guaranteed to be populated when block color == Blue
    pub reward_amount: Option<u64>,
}

Example usage in Rust:

    // get block reward by block hash
    let result = client.get_block_reward_info(sink).unwrap().await?;

    println!(
        "confirmation count: {:?}, reward: {:?}, color: {:?}",
        result.confirmation_count, result.reward_amount, result.block_color
    );

Example usage in WASM:

    const rewardInfo = await rpc.getBlockRewardInfo({
      hash: "ab36f709e83bf6ba66e9516cbf02b9a9848a1b913e7c59cd5010e90928a27e22",
    });

    console.log(
      rewardInfo.blockColor,
      rewardInfo.confirmationCount,
      rewardInfo.header,
      rewardInfo.mergingChainBlockHash,
      rewardInfo.rewardAmount,
    );

Comment thread consensus/src/consensus/mod.rs Outdated
Comment thread rpc/core/src/model/message.rs Outdated
Comment thread rpc/core/src/wasm/message.rs Outdated
Comment thread rpc/service/src/service.rs Outdated
Comment on lines 409 to 413
match session.async_get_merging_chain_block_info(request.hash).await {
Ok(Some(info)) => Ok(GetCurrentBlockColorResponse { blue: info.is_blue }),
Ok(None) | Err(_) => Err(RpcError::MergerNotFound(request.hash)),
}
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async_get_merging_chain_block_info intended to replace async_get_current_block_color. I cannot do full cleanup as a small quick pass as the bridge depends on async_get_current_block_color

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the bridge using it for? I would only assume it'd need the info to also determine rewards.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used for nearly the same thing: after submitting a block, to track metrics of accepted block. But the logic looks wrong, after submitting it assumes that block is accepted if it's blue (one time check). So it should be revisited, but it's out of scope of the current task.

Used by:

  1. rkstratum cpu miner (bridge\src\rkstratum_cpu_miner.rs):
  2. bridge share handler (bridge\src\share_handler.rs)

Comment on lines +443 to +448
let queried_blue_index = merging_ghostdag.mergeset_blues.iter().position(|hash| *hash == request.hash).ok_or_else(|| {
RpcError::General(format!(
"blue block {} was not found in merging chain block {} mergeset blues",
request.hash, merging_chain_block_hash
))
})?;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unreachable?

Comment thread rpc/service/src/service.rs
Comment thread rpc/service/src/service.rs
@IzioDev IzioDev force-pushed the feat/block-reward branch 2 times, most recently from a9242cf to ddcb3d7 Compare March 25, 2026 20:41
@IzioDev IzioDev force-pushed the feat/block-reward branch from ddcb3d7 to 485dd86 Compare March 25, 2026 23:06
@IzioDev IzioDev marked this pull request as ready for review March 25, 2026 23:06
Comment on lines +474 to +475
.checked_add(own_red_reward_output.value)
.ok_or_else(|| RpcError::General("total reward amount overflowed u64 while adding own red reward".to_string()))?;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tempted to simply add

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use saturating_add

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should never happen, or there is a reward bug, right?

Comment on lines +455 to +460
.ok_or_else(|| {
RpcError::General(format!(
"missing reward output {} in merging chain block {}",
queried_blue_index, merging_chain_block_hash
))
})?
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tempted to assume unreachable

Comment on lines +474 to +475
.checked_add(own_red_reward_output.value)
.ok_or_else(|| RpcError::General("total reward amount overflowed u64 while adding own red reward".to_string()))?;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use saturating_add


kaspa.initConsolePanicHook();

const BLOCK_HASH =
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be pruned?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but on the other hand, there is no interest (?) into listening for live rewards, i assume an executor of this script will want to put a hash parameter here.

Alternatively, the script could take argument from argv

Comment thread rpc/service/src/service.rs Outdated
Comment on lines 409 to 413
match session.async_get_merging_chain_block_info(request.hash).await {
Ok(Some(info)) => Ok(GetCurrentBlockColorResponse { blue: info.is_blue }),
Ok(None) | Err(_) => Err(RpcError::MergerNotFound(request.hash)),
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the bridge using it for? I would only assume it'd need the info to also determine rewards.

let queried_ghostdag = session.async_get_ghostdag_data(request.hash).await?;
let queried_coinbase = &queried_block.transactions[COINBASE_TRANSACTION_INDEX];

// only a chain block has its own red-reward coinbase output, as an exrta output
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: exrta -> extra


for child in self.get_block_children(hash).unwrap() {
if visited.insert(child) {
let blue_work = self.ghostdag_store.get_blue_work(child).unwrap();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to get the value blue work value from header store

}
}

while let Some(Reverse(SortableBlock { hash: decedent, .. })) = heap.pop() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decedent -> descendant


for child in self.get_block_children(decedent).unwrap() {
if visited.insert(child) {
let blue_work = self.ghostdag_store.get_blue_work(child).unwrap();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use header store to get blue work here


/// If block hash doesn't exist, returns Err
///
/// For a given block hash, try to find its `MergingBlockContext`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be explained further. What is a MergingBlockContext? Add that documentation here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants