Skip to content

Commit

Permalink
multi: Add getblockchaininfo rpc.
Browse files Browse the repository at this point in the history
This also renames the unexported and
exported versions of ThresholdState to
NextThresholdState as well as the related
test helpers.
  • Loading branch information
dnldd committed Oct 9, 2018
1 parent b413da2 commit fd68c4b
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 112 deletions.
20 changes: 18 additions & 2 deletions blockchain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package blockchain
import (
"container/list"
"fmt"
"math/big"
"sync"
"time"

Expand Down Expand Up @@ -295,12 +296,16 @@ func (b *BlockChain) GetStakeVersions(hash *chainhash.Hash, count int32) ([]Stak
return result, nil
}

// VoteInfo represents information on agendas and their respective states for
// a consensus deployment.
type VoteInfo struct {
Agendas []chaincfg.ConsensusDeployment
AgendaStatus []ThresholdStateTuple
}

// GetVoteInfo returns
// GetVoteInfo returns information on consensus deployment agendas
// and their respective states at the provided hash, for the provided
// deployment version.
func (b *BlockChain) GetVoteInfo(hash *chainhash.Hash, version uint32) (*VoteInfo, error) {
deployments, ok := b.chainParams.Deployments[version]
if !ok {
Expand All @@ -318,7 +323,7 @@ func (b *BlockChain) GetVoteInfo(hash *chainhash.Hash, version uint32) (*VoteInf
}
for _, deployment := range deployments {
vi.Agendas = append(vi.Agendas, deployment)
status, err := b.ThresholdState(hash, version, deployment.Vote.Id)
status, err := b.NextThresholdState(hash, version, deployment.Vote.Id)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -368,6 +373,17 @@ func (b *BlockChain) HaveBlock(hash *chainhash.Hash) (bool, error) {
return b.index.HaveBlock(hash) || b.IsKnownOrphan(hash), nil
}

// ChainWork returns the total work up to and including the block of the
// provided block hash.
func (b *BlockChain) ChainWork(hash *chainhash.Hash) (*big.Int, error) {
node := b.index.LookupNode(hash)
if node == nil {
return nil, fmt.Errorf("block %s is not known", hash)
}

return node.workSum, nil
}

// IsKnownOrphan returns whether the passed hash is currently a known orphan.
// Keep in mind that only a limited number of orphans are held onto for a
// limited amount of time, so this function must not be used as an absolute
Expand Down
108 changes: 102 additions & 6 deletions blockchain/thresholdstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type ThresholdStateTuple struct {
// state contains the current ThresholdState.
State ThresholdState

// coice is set to invalidChoice unless state is: ThresholdLockedIn,
// Choice is set to invalidChoice unless state is: ThresholdLockedIn,
// ThresholdFailed & ThresholdActive. choice should always be
// crosschecked with invalidChoice.
Choice uint32
Expand Down Expand Up @@ -213,12 +213,12 @@ func newThresholdCaches(params *chaincfg.Params) map[uint32][]thresholdStateCach
return caches
}

// thresholdState returns the current rule change threshold state for the block
// nextThresholdState returns the current rule change threshold state for the block
// AFTER the given node and deployment ID. The cache is used to ensure the
// threshold states for previous windows are only calculated once.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) thresholdState(version uint32, prevNode *blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) (ThresholdStateTuple, error) {
func (b *BlockChain) nextThresholdState(version uint32, prevNode *blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) (ThresholdStateTuple, error) {
// The threshold state for the window that contains the genesis block is
// defined by definition.
confirmationWindow := int64(checker.RuleChangeActivationInterval())
Expand Down Expand Up @@ -454,7 +454,7 @@ func (b *BlockChain) deploymentState(prevNode *blockNode, version uint32, deploy
chain: b,
}
cache := &b.deploymentCaches[version][k]
return b.thresholdState(version, prevNode, checker, cache)
return b.nextThresholdState(version, prevNode, checker, cache)
}
}

Expand All @@ -465,11 +465,107 @@ func (b *BlockChain) deploymentState(prevNode *blockNode, version uint32, deploy
return invalidState, DeploymentError(deploymentID)
}

// ThresholdState returns the current rule change threshold state of the given
// 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.
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 nextThresholdState 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.nextThresholdState(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 boundary, 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, nextRhresholdState 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.nextThresholdState(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.
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.
stateNode, err := b.stateLastChanged(version, node, checker, cache)
if err != nil {
return 0, err
}

var height int64
if stateNode != nil {
height = stateNode.height
}
return height, nil
}

// NextThresholdState returns the current rule change threshold state of the given
// deployment ID for the block AFTER the provided block hash.
//
// This function is safe for concurrent access.
func (b *BlockChain) ThresholdState(hash *chainhash.Hash, version uint32, deploymentID string) (ThresholdStateTuple, error) {
func (b *BlockChain) NextThresholdState(hash *chainhash.Hash, version uint32, deploymentID string) (ThresholdStateTuple, 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
Expand Down
70 changes: 35 additions & 35 deletions blockchain/thresholdstate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ var (
}
)

// TestThresholdState ensures that the threshold state function progresses
// TestNextThresholdState ensures that the threshold state function progresses
// through the states correctly.
func TestThresholdState(t *testing.T) {
func TestNextThresholdState(t *testing.T) {
// Create chain params based on simnet params, but add a specific test
// dummy deployment and set the proof-of-work difficulty readjustment
// size to a really large number so that the test chain can be generated
Expand Down Expand Up @@ -184,12 +184,12 @@ func TestThresholdState(t *testing.T) {
}
}

// testThresholdState queries the threshold state from the current
// testNextThresholdState queries the threshold state from the current
// tip block associated with the generator and expects the returned
// state and choice to match the provided values.
testThresholdState := func(id string, state ThresholdState, choice uint32) {
testNextThresholdState := func(id string, state ThresholdState, choice uint32) {
tipHash := g.Tip().BlockHash()
s, err := chain.ThresholdState(&tipHash, posVersion, id)
s, err := chain.NextThresholdState(&tipHash, posVersion, id)
if err != nil {
t.Fatalf("block %q (hash %s, height %d) unexpected "+
"error when retrieving threshold state: %v",
Expand Down Expand Up @@ -233,8 +233,8 @@ func TestThresholdState(t *testing.T) {
g.CreatePremineBlock("bp", 0)
g.AssertTipHeight(1)
accepted()
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to have mature coinbase outputs to work with.
Expand All @@ -249,8 +249,8 @@ func TestThresholdState(t *testing.T) {
accepted()
}
g.AssertTipHeight(uint32(coinbaseMaturity) + 1)
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the stake enabled height while
Expand All @@ -271,8 +271,8 @@ func TestThresholdState(t *testing.T) {
accepted()
}
g.AssertTipHeight(uint32(stakeEnabledHeight))
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the stake validation height while
Expand Down Expand Up @@ -306,8 +306,8 @@ func TestThresholdState(t *testing.T) {
accepted()
}
g.AssertTipHeight(uint32(stakeValidationHeight))
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach one block before the next stake
Expand Down Expand Up @@ -335,8 +335,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + stakeVerInterval - 1))
g.AssertBlockVersion(3)
g.AssertStakeVersion(0)
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach one block before the next rule change
Expand All @@ -363,8 +363,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval - 2))
g.AssertBlockVersion(3)
g.AssertStakeVersion(3)
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach one block before the next stake
Expand Down Expand Up @@ -394,8 +394,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + stakeVerInterval*4 - 1))
g.AssertBlockVersion(3)
g.AssertStakeVersion(3)
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
Expand Down Expand Up @@ -427,8 +427,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*2 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to achieve proof-of-work block version lockin
Expand Down Expand Up @@ -461,8 +461,8 @@ func TestThresholdState(t *testing.T) {
1 + powNumToCheck))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
testThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdDefined, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdDefined, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
Expand Down Expand Up @@ -493,8 +493,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*3 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
testThresholdState(testDummy1ID, ThresholdStarted, invalidChoice)
testThresholdState(testDummy2ID, ThresholdStarted, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdStarted, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdStarted, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
Expand Down Expand Up @@ -523,8 +523,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*4 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
testThresholdState(testDummy1ID, ThresholdStarted, invalidChoice)
testThresholdState(testDummy2ID, ThresholdStarted, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdStarted, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdStarted, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
Expand Down Expand Up @@ -559,8 +559,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*5 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
testThresholdState(testDummy1ID, ThresholdStarted, invalidChoice)
testThresholdState(testDummy2ID, ThresholdStarted, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdStarted, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdStarted, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
Expand Down Expand Up @@ -603,8 +603,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*6 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
testThresholdState(testDummy1ID, ThresholdStarted, invalidChoice)
testThresholdState(testDummy2ID, ThresholdStarted, invalidChoice)
testNextThresholdState(testDummy1ID, ThresholdStarted, invalidChoice)
testNextThresholdState(testDummy2ID, ThresholdStarted, invalidChoice)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
Expand Down Expand Up @@ -634,8 +634,8 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*7 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
testThresholdState(testDummy1ID, ThresholdLockedIn, testDummy1YesIndex)
testThresholdState(testDummy2ID, ThresholdFailed, testDummy2NoIndex)
testNextThresholdState(testDummy1ID, ThresholdLockedIn, testDummy1YesIndex)
testNextThresholdState(testDummy2ID, ThresholdFailed, testDummy2NoIndex)

// ---------------------------------------------------------------------
// Generate enough blocks to reach the next rule change interval with
Expand Down Expand Up @@ -667,6 +667,6 @@ func TestThresholdState(t *testing.T) {
g.AssertTipHeight(uint32(stakeValidationHeight + ruleChangeInterval*8 - 1))
g.AssertBlockVersion(4)
g.AssertStakeVersion(4)
testThresholdState(testDummy1ID, ThresholdActive, testDummy1YesIndex)
testThresholdState(testDummy2ID, ThresholdFailed, testDummy2NoIndex)
testNextThresholdState(testDummy1ID, ThresholdActive, testDummy1YesIndex)
testNextThresholdState(testDummy2ID, ThresholdFailed, testDummy2NoIndex)
}
Loading

0 comments on commit fd68c4b

Please sign in to comment.