Skip to content

Commit 95034ac

Browse files
feat: add grpc updates (#133)
1 parent f12cf72 commit 95034ac

File tree

4 files changed

+473
-95
lines changed

4 files changed

+473
-95
lines changed

services/rpc/bsvjson/chainsvrresults.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ type GetBlockHeaderVerboseResult struct {
2222
Difficulty float64 `json:"difficulty"`
2323
PreviousHash string `json:"previousblockhash,omitempty"`
2424
NextHash string `json:"nextblockhash,omitempty"`
25+
Size int32 `json:"size"`
26+
NumTx int `json:"num_tx"`
27+
MedianTime int64 `json:"mediantime"`
28+
ChainWork string `json:"chainwork"`
29+
Status string `json:"status"`
2530
}
2631

2732
// GetBlockBaseVerboseResult models the common data from the getblock command when
@@ -41,20 +46,25 @@ type GetBlockBaseVerboseResult struct {
4146
Difficulty float64 `json:"difficulty"`
4247
PreviousHash string `json:"previousblockhash"`
4348
NextHash string `json:"nextblockhash,omitempty"`
49+
NumTx int `json:"num_tx"`
50+
MedianTime int64 `json:"mediantime"`
51+
ChainWork string `json:"chainwork"`
4452
}
4553

4654
// GetBlockVerboseResult models the data from the getblock command when the
4755
// verbose flag is set to 1 (default).
56+
// Note: Tx field is returned as empty array for performance reasons to avoid large response bodies.
4857
type GetBlockVerboseResult struct {
4958
*GetBlockBaseVerboseResult
50-
Tx []string `json:"tx,omitempty"`
59+
Tx []string `json:"tx"`
5160
}
5261

5362
// GetBlockVerboseTxResult models the data from the getblock command when the
5463
// verbose flag is set to 2.
64+
// Note: Tx field is returned as empty array for performance reasons to avoid large response bodies.
5565
type GetBlockVerboseTxResult struct {
5666
*GetBlockBaseVerboseResult
57-
Tx []TxRawResult `json:"tx,omitempty"`
67+
Tx []TxRawResult `json:"tx"`
5868
}
5969

6070
// AddMultisigAddressResult models the data returned from the addmultisigaddress

services/rpc/handlers.go

Lines changed: 172 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -269,17 +269,67 @@ func handleGetBlockHeader(ctx context.Context, s *RPCServer, cmd interface{}, _
269269

270270
diff := b.Bits.CalculateDifficulty()
271271
diffFloat, _ := diff.Float64()
272+
273+
// Get best block header for confirmations calculation
274+
_, bestBlockMeta, err := s.blockchainClient.GetBestBlockHeader(ctx)
275+
if err != nil {
276+
return nil, err
277+
}
278+
279+
// Calculate median time for this block
280+
medianTime, err := calculateMedianTime(ctx, s.blockchainClient, b.Hash())
281+
if err != nil {
282+
// If we can't calculate median time, use block time
283+
s.logger.Warnf("Failed to calculate median time for block %s: %v, falling back to block timestamp", b.Hash(), err)
284+
medianTime = b.Timestamp
285+
}
286+
287+
// Handle previousblockhash for genesis block
288+
previousBlockHash := b.HashPrevBlock.String()
289+
if meta.Height == 0 {
290+
// For genesis block, return empty string instead of zeros
291+
previousBlockHash = ""
292+
}
293+
294+
// Get next block hash unless there are none
295+
nextBlock, err := s.blockchainClient.GetBlockByHeight(ctx, meta.Height+1)
296+
var nextBlockHash string
297+
if err == nil && nextBlock != nil {
298+
nextBlockHash = nextBlock.Hash().String()
299+
}
300+
301+
// Get block size
302+
blockBytes := b.Bytes()
303+
blockSizeInt32, err := safeconversion.IntToInt32(len(blockBytes))
304+
if err != nil {
305+
return nil, err
306+
}
307+
308+
// Get transaction count from the block at this height
309+
block, err := s.blockchainClient.GetBlockByHeight(ctx, meta.Height)
310+
var numTx int
311+
if err == nil && block != nil {
312+
numTx = int(block.TransactionCount)
313+
}
314+
272315
headerReply := &bsvjson.GetBlockHeaderVerboseResult{
273-
Hash: b.Hash().String(),
274-
Version: versionInt32,
275-
VersionHex: fmt.Sprintf("%08x", b.Version),
276-
PreviousHash: b.HashPrevBlock.String(),
277-
Nonce: nonceUint64,
278-
Time: timeInt64,
279-
Bits: b.Bits.String(),
280-
Difficulty: diffFloat,
281-
MerkleRoot: b.HashMerkleRoot.String(),
282-
Height: heightInt32,
316+
Hash: b.Hash().String(),
317+
Version: versionInt32,
318+
VersionHex: fmt.Sprintf("%08x", b.Version),
319+
PreviousHash: previousBlockHash,
320+
Nonce: nonceUint64,
321+
Time: timeInt64,
322+
Bits: b.Bits.String(),
323+
Difficulty: diffFloat,
324+
MerkleRoot: b.HashMerkleRoot.String(),
325+
Confirmations: int64(1 + bestBlockMeta.Height - meta.Height),
326+
Height: heightInt32,
327+
Size: blockSizeInt32,
328+
NumTx: numTx,
329+
MedianTime: int64(medianTime),
330+
ChainWork: hex.EncodeToString(meta.ChainWork),
331+
NextHash: nextBlockHash,
332+
Status: "active",
283333
}
284334

285335
// Check if this block is on the main chain
@@ -293,13 +343,6 @@ func handleGetBlockHeader(ctx context.Context, s *RPCServer, cmd interface{}, _
293343
return headerReply, nil
294344
}
295345

296-
// Block is on the main chain, calculate confirmations
297-
_, bestBlockMeta, err := s.blockchainClient.GetBestBlockHeader(ctx)
298-
if err != nil {
299-
return nil, err
300-
}
301-
headerReply.Confirmations = 1 + int64(bestBlockMeta.Height) - int64(meta.Height)
302-
303346
return headerReply, nil
304347
}
305348

@@ -333,6 +376,12 @@ func (s *RPCServer) blockToJSON(ctx context.Context, b *model.Block, verbosity u
333376
return nil, err
334377
}
335378

379+
// Get block metadata for this specific block to retrieve its chain work
380+
_, blockMeta, err := s.blockchainClient.GetBlockHeader(ctx, b.Hash())
381+
if err != nil {
382+
return nil, err
383+
}
384+
336385
// Get next block hash unless there are none.
337386
nextBlock, err := s.blockchainClient.GetBlockByHeight(ctx, b.Height+1)
338387
if err != nil && !errors.Is(err, errors.ErrBlockNotFound) {
@@ -362,12 +411,28 @@ func (s *RPCServer) blockToJSON(ctx context.Context, b *model.Block, verbosity u
362411
return nil, err
363412
}
364413

414+
// Calculate median time for this block
415+
medianTime, err := calculateMedianTime(ctx, s.blockchainClient, b.Hash())
416+
if err != nil {
417+
// If we can't calculate median time, use block time
418+
s.logger.Warnf("Failed to calculate median time for block %s: %v, falling back to block timestamp", b.Hash(), err)
419+
medianTime = b.Header.Timestamp
420+
}
421+
422+
// Handle previousblockhash for genesis block
423+
previousBlockHash := b.Header.HashPrevBlock.String()
424+
if b.Height == 0 {
425+
// For genesis block, return empty string instead of zeros
426+
previousBlockHash = ""
427+
}
428+
429+
// Create the base block reply with all required fields
365430
baseBlockReply := &bsvjson.GetBlockBaseVerboseResult{
366431
Hash: b.Hash().String(),
367432
Version: versionInt32,
368433
VersionHex: fmt.Sprintf("%08x", b.Header.Version),
369434
MerkleRoot: b.Header.HashMerkleRoot.String(),
370-
PreviousHash: b.Header.HashPrevBlock.String(),
435+
PreviousHash: previousBlockHash,
371436
Nonce: b.Header.Nonce,
372437
Time: int64(b.Header.Timestamp),
373438
Confirmations: 1 + int64(bestBlockMeta.Height) - int64(b.Height),
@@ -376,42 +441,19 @@ func (s *RPCServer) blockToJSON(ctx context.Context, b *model.Block, verbosity u
376441
Bits: b.Header.Bits.String(),
377442
Difficulty: diff,
378443
NextHash: nextBlockHash,
444+
NumTx: int(b.TransactionCount),
445+
MedianTime: int64(medianTime),
446+
ChainWork: hex.EncodeToString(blockMeta.ChainWork),
379447
}
380448

381-
// TODO: we can't add the txs to the block as there could be too many.
382-
// A breaking change would be to add the subtrees.
383-
384-
// If verbose level does not match 0 or 1
385-
// we can consider it 2 (current bitcoin core behavior)
386-
if verbosity == 1 { //nolint:wsl
387-
// transactions := blk.Transactions()
388-
// txNames := make([]string, len(transactions))
389-
// for i, tx := range transactions {
390-
// txNames[i] = tx.Hash().String()
391-
// }
449+
// For verbosity 1 and above, return the JSON object
450+
// Note: Tx field is intentionally kept empty for performance reasons
451+
// to avoid large response bodies with potentially millions of transactions
452+
s.logger.Debugf("Returning block %s with empty tx array (num_tx=%d) for performance reasons", b.Hash(), b.TransactionCount)
392453

393-
// blockReply = bsvjson.GetBlockVerboseResult{
394-
// GetBlockBaseVerboseResult: baseBlockReply,
395-
396-
// Tx: txNames,
397-
// }
398-
// } else {
399-
// txns := blk.Transactions()
400-
// rawTxns := make([]bsvjson.TxRawResult, len(txns))
401-
// for i, tx := range txns {
402-
// rawTxn, err := createTxRawResult(params, tx.MsgTx(),
403-
// tx.Hash().String(), blockHeader, hash.String(),
404-
// blockHeight, best.Height)
405-
// if err != nil {
406-
// return nil, err
407-
// }
408-
// rawTxns[i] = *rawTxn
409-
// }
410-
blockReply = &bsvjson.GetBlockVerboseTxResult{
411-
GetBlockBaseVerboseResult: baseBlockReply,
412-
413-
// Tx: rawTxns,
414-
}
454+
blockReply = &bsvjson.GetBlockVerboseResult{
455+
GetBlockBaseVerboseResult: baseBlockReply,
456+
Tx: []string{}, // Empty array for backward compatibility
415457
}
416458

417459
return blockReply, nil
@@ -873,15 +915,8 @@ func handleGenerate(ctx context.Context, s *RPCServer, cmd interface{}, _ <-chan
873915
return nil, err
874916
}
875917

876-
err = s.blockAssemblyClient.GenerateBlocks(ctx, &blockassembly_api.GenerateBlocksRequest{Count: numblocksInt32})
877-
if err != nil {
878-
return nil, &bsvjson.RPCError{
879-
Code: bsvjson.ErrRPCInternal.Code,
880-
Message: errors.NewServiceError("RPC blockassembly client", err).Error(),
881-
}
882-
}
883-
884-
return nil, nil
918+
// Generate blocks and return their hashes
919+
return s.generateBlocksAndReturnHashes(ctx, numblocksInt32, nil, nil)
885920
}
886921

887922
// handleGenerateToAddress implements the generatetoaddress command, which instructs the node to
@@ -950,15 +985,8 @@ func handleGenerateToAddress(ctx context.Context, s *RPCServer, cmd interface{},
950985
}
951986
}
952987

953-
err = s.blockAssemblyClient.GenerateBlocks(ctx, &blockassembly_api.GenerateBlocksRequest{Count: c.NumBlocks, Address: &c.Address, MaxTries: c.MaxTries})
954-
if err != nil {
955-
return nil, &bsvjson.RPCError{
956-
Code: bsvjson.ErrRPCInternal.Code,
957-
Message: err.Error(),
958-
}
959-
}
960-
961-
return nil, nil
988+
// Generate blocks and return their hashes
989+
return s.generateBlocksAndReturnHashes(ctx, c.NumBlocks, &c.Address, c.MaxTries)
962990
}
963991

964992
// handleGetMiningCandidate implements the getminingcandidate command, which provides
@@ -1017,6 +1045,10 @@ func handleGetMiningCandidate(ctx context.Context, s *RPCServer, cmd interface{}
10171045
return nil, err
10181046
}
10191047

1048+
// Calculate difficulty from nBits
1049+
difficulty := nBits.CalculateDifficulty()
1050+
difficultyFloat, _ := difficulty.Float64()
1051+
10201052
merkleProofStrings := make([]string, len(mc.MerkleProof))
10211053

10221054
for i, hash := range mc.MerkleProof {
@@ -1034,6 +1066,7 @@ func handleGetMiningCandidate(ctx context.Context, s *RPCServer, cmd interface{}
10341066
"num_tx": mc.NumTxs,
10351067
"sizeWithoutCoinbase": mc.SizeWithoutCoinbase,
10361068
"merkleProof": merkleProofStrings,
1069+
"difficulty": difficultyFloat,
10371070
}
10381071

10391072
if c.ProvideCoinbaseTx != nil && *c.ProvideCoinbaseTx {
@@ -1364,17 +1397,15 @@ func handleGetblockchaininfo(ctx context.Context, s *RPCServer, cmd interface{},
13641397
return map[string]interface{}{}, errors.NewProcessingError("error calculating median time: %v", err)
13651398
}
13661399

1367-
// Calculate verification progress based on blockchain statistics
1400+
// Calculate verification progress based on Bitcoin SV's GuessVerificationProgress function
13681401
verificationProgress, err := calculateVerificationProgress(ctx, s.blockchainClient, bestBlockMeta.Height)
13691402
if err != nil {
13701403
// If we can't calculate verification progress, default to 1.0 (assume fully synced)
13711404
verificationProgress = 1.0
13721405
}
13731406

1374-
chainWorkHash, err := chainhash.NewHash(bestBlockMeta.ChainWork)
1375-
if err != nil {
1376-
return map[string]interface{}{}, errors.NewProcessingError("error creating chain work hash: %v", err)
1377-
}
1407+
// Convert chainwork bytes to little endian hex string
1408+
chainWorkHex := hex.EncodeToString(bestBlockMeta.ChainWork)
13781409

13791410
difficultyBigFloat := bestBlockHeader.Bits.CalculateDifficulty()
13801411
difficulty, _ := difficultyBigFloat.Float64()
@@ -1387,7 +1418,7 @@ func handleGetblockchaininfo(ctx context.Context, s *RPCServer, cmd interface{},
13871418
"difficulty": difficulty, // Return as float64 to match Bitcoin SV
13881419
"mediantime": medianTime,
13891420
"verificationprogress": verificationProgress,
1390-
"chainwork": chainWorkHash.String(),
1421+
"chainwork": chainWorkHex,
13911422
"pruned": false, // the minimum relay fee for non-free transactions in BSV/KB
13921423
"softforks": []interface{}{},
13931424
}
@@ -1399,8 +1430,8 @@ func handleGetblockchaininfo(ctx context.Context, s *RPCServer, cmd interface{},
13991430
return jsonMap, nil
14001431
}
14011432

1402-
// calculateVerificationProgress implements Bitcoin SV's GuessVerificationProgress function.
1403-
// This is a direct translation of the Bitcoin SV code from validation.cpp:
1433+
// calculateVerificationProgress follows the pattern of Bitcoin SV's GuessVerificationProgress function.
1434+
// This is a translation of the Bitcoin SV code from validation.cpp:
14041435
//
14051436
// double GuessVerificationProgress(const ChainTxData &data, const CBlockIndex *pindex) {
14061437
// if (pindex == nullptr) return 0.0;
@@ -1606,7 +1637,12 @@ func handleGetInfo(ctx context.Context, s *RPCServer, cmd interface{}, _ <-chan
16061637
"difficulty": difficulty, // the current target difficulty
16071638
"testnet": s.settings.ChainCfgParams.Net == wire.TestNet, // whether or not server is using testnet
16081639
"stn": s.settings.ChainCfgParams.Net == wire.STN, // whether or not server is using stn
1609-
1640+
"policy": map[string]interface{}{
1641+
"maxblocksize": s.settings.Policy.ExcessiveBlockSize, // maximum block size
1642+
"maxminedblocksize": s.settings.Policy.BlockMaxSize, // maximum mined block size
1643+
"maxstackmemoryusagepolicy": s.settings.Policy.MaxStackMemoryUsagePolicy, // max stack memory usage policy
1644+
"maxstackmemoryusageconsensus": s.settings.Policy.MaxStackMemoryUsageConsensus, // max stack memory usage consensus
1645+
},
16101646
}
16111647

16121648
if s.settings.RPC.CacheEnabled {
@@ -2694,3 +2730,61 @@ func handleGetchaintips(ctx context.Context, s *RPCServer, cmd interface{}, _ <-
26942730

26952731
return result, nil
26962732
}
2733+
2734+
// generateBlocksAndReturnHashes is a shared helper method that generates blocks and returns their hashes.
2735+
// This method is used by both handleGenerate and handleGenerateToAddress to avoid code duplication.
2736+
//
2737+
// Parameters:
2738+
// - ctx: Context for cancellation and tracing
2739+
// - s: The RPC server instance providing access to service clients
2740+
// - count: Number of blocks to generate
2741+
// - address: Optional address for mining rewards (nil for generate command)
2742+
// - maxTries: Optional maximum number of attempts (nil for generate command)
2743+
//
2744+
// Returns:
2745+
// - []string: Array of block hashes of the generated blocks
2746+
// - error: Any error encountered during block generation
2747+
func (s *RPCServer) generateBlocksAndReturnHashes(ctx context.Context, count int32, address *string, maxTries *int32) ([]string, error) {
2748+
// Get the current best block hash before generation
2749+
_, bestBlockMeta, err := s.blockchainClient.GetBestBlockHeader(ctx)
2750+
if err != nil {
2751+
return nil, &bsvjson.RPCError{
2752+
Code: bsvjson.ErrRPCInternal.Code,
2753+
Message: errors.NewServiceError("RPC blockchain client", err).Error(),
2754+
}
2755+
}
2756+
startHeight := bestBlockMeta.Height
2757+
2758+
// Generate the blocks
2759+
err = s.blockAssemblyClient.GenerateBlocks(ctx, &blockassembly_api.GenerateBlocksRequest{
2760+
Count: count,
2761+
Address: address,
2762+
MaxTries: maxTries,
2763+
})
2764+
if err != nil {
2765+
return nil, &bsvjson.RPCError{
2766+
Code: bsvjson.ErrRPCInternal.Code,
2767+
Message: errors.NewServiceError("RPC blockassembly client", err).Error(),
2768+
}
2769+
}
2770+
2771+
// Collect the block hashes of the generated blocks
2772+
// Note: There may be a brief delay between block generation and availability in blockchain client
2773+
var blockHashes []string
2774+
for i := int32(1); i <= count; i++ {
2775+
targetHeight := startHeight + uint32(i)
2776+
2777+
// Get the block at the target height
2778+
block, err := s.blockchainClient.GetBlockByHeight(ctx, targetHeight)
2779+
if err != nil {
2780+
return nil, &bsvjson.RPCError{
2781+
Code: bsvjson.ErrRPCInternal.Code,
2782+
Message: errors.NewServiceError("RPC blockchain client", err).Error(),
2783+
}
2784+
}
2785+
2786+
blockHashes = append(blockHashes, block.Header.Hash().String())
2787+
}
2788+
2789+
return blockHashes, nil
2790+
}

0 commit comments

Comments
 (0)