From 7df208eb88e99bd88f6a6044aabb8d200cf1bbe6 Mon Sep 17 00:00:00 2001 From: Jonathan Moody <103143855+moodyjon@users.noreply.github.com> Date: Thu, 21 Jul 2022 14:27:06 -0400 Subject: [PATCH] [rpc blockchain] Add support for mediantime, chainwork to RPC getblock. --- blockchain/chain.go | 49 ++++++++++++++++++++++++++++++++++++++ btcjson/chainsvrresults.go | 2 ++ rpcserver.go | 44 +++++++++++++++------------------- rpcserverhelp.go | 2 ++ 4 files changed, 72 insertions(+), 25 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index 9a6ec216..ef05d947 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -8,6 +8,7 @@ package blockchain import ( "container/list" "fmt" + "math/big" "sync" "time" @@ -1373,6 +1374,54 @@ func (b *BlockChain) BlockHashByHeight(blockHeight int32) (*chainhash.Hash, erro return &node.hash, nil } +// BlockAttributes desribes a Block in relation to others on the main chain. +type BlockAttributes struct { + Height int32 + Confirmations int32 + MedianTime time.Time + ChainWork *big.Int + PrevHash *chainhash.Hash + NextHash *chainhash.Hash +} + +// BlockAttributesByHash returns BlockAttributes for the block with the given hash +// relative to other blocks in the main chain. A BestState snapshot describing +// the main chain is also returned for convenience. +// +// This function is safe for concurrent access. +func (b *BlockChain) BlockAttributesByHash(hash *chainhash.Hash, prevHash *chainhash.Hash) ( + attrs *BlockAttributes, best *BestState, err error) { + best = b.BestSnapshot() + node := b.index.LookupNode(hash) + if node == nil || !b.bestChain.Contains(node) { + str := fmt.Sprintf("block %s is not in the main chain", hash) + return nil, best, errNotInMainChain(str) + } + + attrs = &BlockAttributes{ + Height: node.height, + Confirmations: 1 + best.Height - node.height, + MedianTime: node.CalcPastMedianTime(), + ChainWork: node.workSum, + } + + // Populate prev block hash if there is one. + if node.height > 0 { + attrs.PrevHash = prevHash + } + + // Populate next block hash if there is one. + if node.height < best.Height { + nextHash, err := b.BlockHashByHeight(node.height + 1) + if err != nil { + return nil, best, err + } + attrs.NextHash = nextHash + } + + return attrs, best, nil +} + // HeightRange returns a range of block hashes for the given start and end // heights. It is inclusive of the start height and exclusive of the end // height. The end height will be limited to the current main chain height. diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 6a918eb1..c6a5d550 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -77,9 +77,11 @@ type GetBlockVerboseResultBase struct { VersionHex string `json:"versionHex"` MerkleRoot string `json:"merkleroot"` Time int64 `json:"time"` + MedianTime int64 `json:"mediantime"` Nonce uint32 `json:"nonce"` Bits string `json:"bits"` Difficulty float64 `json:"difficulty"` + ChainWork string `json:"chainwork"` PreviousHash string `json:"previousblockhash,omitempty"` NextHash string `json:"nextblockhash,omitempty"` diff --git a/rpcserver.go b/rpcserver.go index 1fc32a06..96f92201 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1209,31 +1209,23 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i return nil, internalRPCError(err.Error(), context) } - // Get the block height from chain. - blockHeight, err := s.cfg.Chain.BlockHeightByHash(hash) - if err != nil { - context := "Failed to obtain block height" - return nil, internalRPCError(err.Error(), context) - } - blk.SetHeight(blockHeight) - best := s.cfg.Chain.BestSnapshot() - - // Get next block hash unless there are none. - var nextHashString string - if blockHeight < best.Height { - nextHash, err := s.cfg.Chain.BlockHashByHeight(blockHeight + 1) - if err != nil { - context := "No next block" - return nil, internalRPCError(err.Error(), context) - } - nextHashString = nextHash.String() - } - params := s.cfg.ChainParams blockHeader := &blk.MsgBlock().Header + + // Get further details (height, confirmations, nexthash, mediantime, etc.) from chain. + attrs, best, err := s.cfg.Chain.BlockAttributesByHash(hash, &blockHeader.PrevBlock) + if err != nil { + context := "Failed to obtain block details" + return nil, internalRPCError(err.Error(), context) + } + var prevHashString string - if blockHeight > 0 { - prevHashString = blockHeader.PrevBlock.String() + if attrs.PrevHash != nil { + prevHashString = attrs.PrevHash.String() + } + var nextHashString string + if attrs.NextHash != nil { + nextHashString = attrs.NextHash.String() } base := btcjson.GetBlockVerboseResultBase{ @@ -1244,13 +1236,15 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i PreviousHash: prevHashString, Nonce: blockHeader.Nonce, Time: blockHeader.Timestamp.Unix(), - Confirmations: int64(1 + best.Height - blockHeight), - Height: int64(blockHeight), + MedianTime: attrs.MedianTime.Unix(), + Confirmations: int64(attrs.Confirmations), + Height: int64(attrs.Height), Size: int32(len(blkBytes)), StrippedSize: int32(blk.MsgBlock().SerializeSizeStripped()), Weight: int32(blockchain.GetBlockWeight(blk)), Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), Difficulty: getDifficultyRatio(blockHeader.Bits, params), + ChainWork: attrs.ChainWork.Text(16), NextHash: nextHashString, ClaimTrie: blockHeader.ClaimTrie.String(), } @@ -1275,7 +1269,7 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i for i, tx := range txns { rawTxn, err := createTxRawResult(params, tx.MsgTx(), tx.Hash().String(), blockHeader, hash.String(), - blockHeight, best.Height) + attrs.Height, best.Height) if err != nil { return nil, err } diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 3a6eb87b..2be322ce 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -260,9 +260,11 @@ var helpDescsEnUS = map[string]string{ "getblockverboseresult-tx": "The transaction hashes (only when verbosity=1)", "getblockverboseresult-nTx": "The number of transactions (aka, count of TX)", "getblockverboseresult-time": "The block time in seconds since 1 Jan 1970 GMT", + "getblockverboseresult-mediantime": "The median block time in seconds since 1 Jan 1970 GMT", "getblockverboseresult-nonce": "The block nonce", "getblockverboseresult-bits": "The bits which represent the block difficulty", "getblockverboseresult-difficulty": "The proof-of-work difficulty as a multiple of the minimum difficulty", + "getblockverboseresult-chainwork": "Expected number of hashes required to produce the chain up to this block (in hex)", "getblockverboseresult-previousblockhash": "The hash of the previous block", "getblockverboseresult-nextblockhash": "The hash of the next block (only if there is one)", "getblockverboseresult-strippedsize": "The size of the block without witness data",