-
Notifications
You must be signed in to change notification settings - Fork 293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
multi: Add getblockchaininfo rpc. #1479
Conversation
540343e
to
e52a86e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small naming issue.
I'm also seeing something weird while syncing a node in mainnet:
https://gist.github.com/matheusd/62bb07d9dfc279aa6d5bd0273f83e288
This node already had a blochain and started syncing at around block 279436.
Notice as the syncing is progressing, the "since" value of sdiff deployment is changing. Is this supposed to be happening?
blockmanager.go
Outdated
@@ -352,6 +357,13 @@ func (b *blockManager) resetHeaderState(newestHash *chainhash.Hash, newestHeight | |||
} | |||
} | |||
|
|||
// SyncHeight returns the current synced block height from peers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd reword this slightly, because reading it, it seems that this is the block height the local node has synced, while this is meant to be the block height the local node is trying to sync to (ie: the "target" sync height). The semantics for this "syncHeight" are also slighly tricky, since it's either the current best height (on a node that has just loaded up and hasn't tried syncing to anything yet or that has lost all peers) or it's the height it's trying to sync to.
This would also be useful information returned in getblockchaininfo
, since this rpc already seems to include info that is not strictly from the node's chain.
The value for |
e52a86e
to
3b50153
Compare
@matheusd issues raised should be resolved. Here's some test output on mainnet:
|
9baf408
to
3e42c2e
Compare
Those since values do not appear to be correct. DCP0001 became active at height 149248 |
blockchain/thresholdstate.go
Outdated
return 0, err | ||
} | ||
|
||
return stateChangeNode.height + 1, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is incorrect. The function which determines the node in question should be returning the correct node. This would return 2 in the case the genesis block node was returning which is clearly not correct!
blockchain/thresholdstate.go
Outdated
// StateActivationHeight returns the height of the first block where the | ||
// current state of consensus deployment agenda activated. | ||
func (b *BlockChain) StateActivationHeight(hash *chainhash.Hash, version uint32, deploymentID string) (int64, error) { | ||
// Fetch the block node of the provided hash. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please include the note as described in ThresholdState
as to why the node status is required to be known valid.
blockmanager.go
Outdated
@@ -334,6 +334,11 @@ type blockManager struct { | |||
// The following fields are used to filter duplicate block announcements. | |||
announcedBlockMtx sync.Mutex | |||
announcedBlock *chainhash.Hash | |||
|
|||
// The following fields are used to track the current synced block height |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really correct. It's used to track the height that is being synced to, not the currently synced one.
dcrjson/chainsvrresults.go
Outdated
@@ -89,16 +89,29 @@ type GetBlockVerboseResult struct { | |||
NextHash string `json:"nextblockhash,omitempty"` | |||
} | |||
|
|||
// AgendaInfo provides an overview of an agenda in a consensus deployment. | |||
type AgendaInfo struct { | |||
Version uint32 `json:"version"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced this should be included. It only has to do with voting, and the main purpose of including the deployment information in the RPC is to report the status of historical deployments.
Also, it doesn't make sense in certain circumstances, such as simnet
and new versions of testnet
, where changes caused by historical votes on mainnet
are rolled into their respective genesis blocks and thus were never voted on for that network.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noted, updating. I initially included it for ease of use with getvoteinfo
, that requires the development id and the version. getblockchaininfo
would be requested first and with that the caller would have all the info needed to take a closer look at each vote since the response has the deployment id and the version.
9465fa8
to
df5ddac
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation specifies that the new command is available to limited users (which it should be), but you didn't include the command in the map which permits its use by a limited user.
rpcserverhelp.go
Outdated
|
||
// GetBlockchainInfoResult help. | ||
"getblockchaininforesult-chain": "The current network name.", | ||
"getblockchaininforesult-blocks": "The block count.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest something a little more descriptive.
Maybe:
The number of blocks in the current best chain.
rpcserverhelp.go
Outdated
// GetBlockchainInfoResult help. | ||
"getblockchaininforesult-chain": "The current network name.", | ||
"getblockchaininforesult-blocks": "The block count.", | ||
"getblockchaininforesult-headers": "The block header count.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps:
The number of validated block headers that comprise the target best chain.
I'm making this distinction because, while it's not currently the case, the plan is to move to a full headers-first download mode where all of the headers will be downloaded first followed by downloading all of the actual block data in parallel.
rpcserverhelp.go
Outdated
"getblockchaininforesult-difficulty": "The current network difficulty.", | ||
"getblockchaininforesult-verificationprogress": "The chain verification progress estimate.", | ||
"getblockchaininforesult-chainwork": "Hex encoded total work done for the chain.", | ||
"getblockchaininforesult-initialblockdownload": "Syncing status", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This description doesn't seem to be helpful. I suggest:
Best guess of whether this node is in the initial block download mode used to catch up the chain when it is far behind
rpcserverhelp.go
Outdated
"getblockchaininforesult-initialblockdownload": "Syncing status", | ||
"getblockchaininforesult-maxblocksize": "The maximum allowed block size.", | ||
"getblockchaininforesult-deployments": "Network consensus deployments.", | ||
"getblockchaininforesult-deployments--desc": "Nonsensus deployment agendas.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Nonsensus/Consensus/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the RPC server help for the AgendaInfo
is missing.
blockchain/thresholdstate.go
Outdated
@@ -465,6 +465,91 @@ func (b *BlockChain) deploymentState(prevNode *blockNode, version uint32, deploy | |||
return invalidState, DeploymentError(deploymentID) | |||
} | |||
|
|||
// stateActivationNode returns the node at which the provided consensus | |||
// deployment agenda activated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is improperly named and the comment is incorrect. It is not when it activated, rather when the state last changed.
blockchain/thresholdstate.go
Outdated
|
||
// StateActivationHeight returns the height of the first block where the | ||
// current state of consensus deployment agenda activated. | ||
func (b *BlockChain) StateActivationHeight(hash *chainhash.Hash, version uint32, deploymentID string) (int64, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong too. It's not returning when it activated, rather the height at which the state last changed. For example, try calling this with the hash at height 140000.
5ff9e73
to
cc447b6
Compare
blockchain/thresholdstate.go
Outdated
// the state is the same for all blocks within a given window. | ||
wantHeight := calcWantHeight(svh, confirmationWindow, prevNode.height+1) | ||
prevNode = prevNode.Ancestor(wantHeight) | ||
firstNodeLastConfWindow := *prevNode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to make a copy of the node here. Just keep the pointer to it.
blockchain/thresholdstate.go
Outdated
} | ||
|
||
if state.State != curState.State { | ||
stateActivationNode := firstNodeLastConfWindow.Ancestor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't always be correct. On the first iteration, there is no guarantee that firstNodeLastConfWindow
will actually have gone back more than confirmationWindow + 1
, so it would return nil in that case and end up crashing later.
blockchain/thresholdstate.go
Outdated
"deployment id (%s) not found", deploymentID) | ||
} | ||
|
||
aNode, err := b.stateLastChanged(version, node, checker, cache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aNode
implies activation node. Maybe stateNode
?
blockchain/thresholdstate.go
Outdated
// Return the threshold state for the window that contains the genesis | ||
// block if the chain is not past stake validation height. | ||
if prevNode == nil || prevNode.height+1 < svh+confirmationWindow { | ||
return b.bestChain.Genesis(), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accessing bestChain
requires the chain lock which is not taken. I suggest returning nil instead and having the caller check it so the lock can be avoided altogether.
blockchain/thresholdstate.go
Outdated
@@ -465,6 +465,90 @@ func (b *BlockChain) deploymentState(prevNode *blockNode, version uint32, deploy | |||
return invalidState, DeploymentError(deploymentID) | |||
} | |||
|
|||
// stateLastChanged returns the node at which the provided consensus | |||
// deployment agenda last changed state. | |||
func (b *BlockChain) stateLastChanged(version uint32, prevNode *blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) (*blockNode, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prevNode
implies it is calculating something for the next node, but that is not the case here. This should just be node
.
Since we've gone back and forth several times now regarding the since heights, here is fully commented code that is tested and works properly: // stateLastChanged returns the node at which the provided consensus deployment
// agenda last changed state. The function will return nil if the state has
// never changed.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) stateLastChanged(version uint32, node *blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) (*blockNode, error) {
// No state changes are possible if the chain is not yet past stake
// validation height and had a full interval to change.
confirmationInterval := int64(checker.RuleChangeActivationInterval())
svh := checker.StakeValidationHeight()
if node == nil || node.height < svh+confirmationInterval {
return nil, nil
}
// Determine the current state. Notice that thresholdState always
// calculates the state for the block after the provided one, so use the
// parent to get the state for the requested block.
curState, err := b.thresholdState(version, node.parent, checker, cache)
if err != nil {
return nil, err
}
// Determine the first block of the current confirmation interval in order
// to determine block at which the state possibly changed. Since the state
// can only change at an interval boudnary, loop backwards one interval at
// a time to determine when (and if) the state changed.
finalNodeHeight := calcWantHeight(svh, confirmationInterval, node.height)
node = node.Ancestor(finalNodeHeight + 1)
priorStateChangeNode := node
for node != nil && node.parent != nil {
// As previously mentioned, thresholdState always calculates the state
// for the block after the provided one, so use the parent to get the
// state of the block itself.
state, err := b.thresholdState(version, node.parent, checker, cache)
if err != nil {
return nil, err
}
if state.State != curState.State {
return priorStateChangeNode, nil
}
// Get the ancestor that is the first block of the previous confirmation
// interval.
priorStateChangeNode = node
node = node.RelativeAncestor(confirmationInterval)
}
return nil, nil
}
// StateLastChangedHeight returns the height at which the provided consensus
// deployment agenda last changed state. Note that, unlike the ThresholdState
// function, this function returns the information as of the passed block hash.
//
// This function is safe for concurrent access.
func (b *BlockChain) StateLastChangedHeight(hash *chainhash.Hash, version uint32, deploymentID string) (int64, error) {
// NOTE: The requirement for the node being fully validated here is strictly
// stronger than what is actually required. In reality, all that is needed
// is for the block data for the node and all of its ancestors to be
// available, but there is not currently any tracking to be able to
// efficiently determine that state.
node := b.index.LookupNode(hash)
if node == nil || !b.index.NodeStatus(node).KnownValid() {
return 0, HashError(hash.String())
}
// Fetch the treshold state cache for the provided deployment id as well as
// the condition checker.
var cache *thresholdStateCache
var checker thresholdConditionChecker
for k := range b.chainParams.Deployments[version] {
if b.chainParams.Deployments[version][k].Vote.Id == deploymentID {
checker = deploymentChecker{
deployment: &b.chainParams.Deployments[version][k],
chain: b,
}
cache = &b.deploymentCaches[version][k]
break
}
}
if cache == nil {
return 0, fmt.Errorf("threshold state cache for agenda with "+
"deployment id (%s) not found", deploymentID)
}
// Find the node at which the current state changed.
b.chainLock.Lock()
stateNode, err := b.stateLastChanged(version, node, checker, cache)
b.chainLock.Unlock()
if err != nil {
return 0, err
}
var height int64
if stateNode != nil {
height = stateNode.height
}
return height, nil
} In order to prove correctness of this code, I printed the information for several block heights around the interval changes and before SVH. Results:
|
b9f7635
to
fd68c4b
Compare
rpcserver.go
Outdated
Blocks: best.Height, | ||
Headers: best.Height, | ||
SyncHeight: syncHeight, | ||
ChainWork: fmt.Sprintf("%#x", chainWork), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The result should not include the 0x
prefix and should correctly not that work is in terms of a uint256.
Thus, it should be fmt.Sprintf("%064x", chainWork)
blockchain/thresholdstate.go
Outdated
node = node.Ancestor(finalNodeHeight + 1) | ||
priorStateChangeNode := node | ||
for node != nil && node.parent != nil { | ||
// As previously mentioned, nextRhresholdState always calculates the state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/nextRhresholdState/nextThresholdState/
rpcserver.go
Outdated
func handleGetBlockchainInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { | ||
params := s.server.chainParams | ||
best := s.chain.BestSnapshot() | ||
dInfo := make(map[string]dcrjson.AgendaInfo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason to create this so early before errors might end up making it unnecessary to create at all. Prefer defining just before first use. So just before the for loop (after the comment for the section).
Same goes for params above.
rpcserver.go
Outdated
} | ||
|
||
// Guess the syncing status of the node. | ||
initialBlockDownload := !s.chain.IsCurrent() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should just be done directly in the response below. It's not used anywhere else.
rpcserver.go
Outdated
// Estimate the verification progress of the node. | ||
syncHeight := s.server.blockManager.SyncHeight() | ||
var verifyProgress float64 | ||
switch syncHeight > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why using a switch for a single condition? Just use the zero value and do:
var verifyProgress float64
if syncHeight > 0 {
verifyProgress = float64(best.Height) / float64(syncHeight)
}
It's 4 lines versus 7 and more consistent with the rest of the code.
rpcserver.go
Outdated
"for agenda with id (%v).", agenda.Vote.Id)) | ||
} | ||
|
||
if state.State != blockchain.ThresholdDefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason to get the state and avoid calling StateLastChangedHeight
if the state is defined. The function will just return 0 in that case, which is the same result.
248af31
to
2928ce2
Compare
2928ce2
to
3612910
Compare
Some results during initial sync with the latest:
|
This resolves #1475.
Thanks to @davecgh for his help & @matheusd for testing/review.