blockchain: Remove threshold state db cache.
This completely removes the threshold state database caching code since it can very quickly be calculated at startup now that the entire block index is loaded first.
This commit is contained in:
parent
296fa0a5a0
commit
349450379f
4 changed files with 59 additions and 650 deletions
|
@ -637,17 +637,12 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U
|
|||
}
|
||||
}
|
||||
|
||||
// Update the cached threshold states in the database as needed.
|
||||
return b.putThresholdCaches(dbTx)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark all modified entries in the threshold caches as flushed now that
|
||||
// they have been committed to the database.
|
||||
b.markThresholdCachesFlushed()
|
||||
|
||||
// Prune fully spent entries and mark all entries in the view unmodified
|
||||
// now that the modifications have been committed to the database.
|
||||
view.commit()
|
||||
|
@ -1352,10 +1347,7 @@ func New(config *Config) (*BlockChain, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize rule change threshold state caches from the passed
|
||||
// database. When the db does not yet contains any cached information
|
||||
// for a given threshold cache, the threshold states will be calculated
|
||||
// using the chain state.
|
||||
// Initialize rule change threshold state caches.
|
||||
if err := b.initThresholdCaches(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
@ -40,27 +39,6 @@ var (
|
|||
// unspent transaction output set.
|
||||
utxoSetBucketName = []byte("utxoset")
|
||||
|
||||
// thresholdBucketName is the name of the db bucket used to house cached
|
||||
// threshold states.
|
||||
thresholdBucketName = []byte("thresholdstate")
|
||||
|
||||
// numDeploymentsKeyName is the name of the db key used to store the
|
||||
// number of saved deployment caches.
|
||||
numDeploymentsKeyName = []byte("numdeployments")
|
||||
|
||||
// deploymentBucketName is the name of the db bucket used to house the
|
||||
// cached threshold states for the actively defined rule deployments.
|
||||
deploymentBucketName = []byte("deploymentcache")
|
||||
|
||||
// deploymentStateKeyName is the name of the db key used to store the
|
||||
// deployment state associated with the threshold cache for a given rule
|
||||
// deployment.
|
||||
deploymentStateKeyName = []byte("deploymentstate")
|
||||
|
||||
// warningBucketName is the name of the db bucket used to house the
|
||||
// cached threshold states for unknown rule deployments.
|
||||
warningBucketName = []byte("warningcache")
|
||||
|
||||
// byteOrder is the preferred byte order used for serializing numeric
|
||||
// fields for storage in the database.
|
||||
byteOrder = binary.LittleEndian
|
||||
|
@ -1454,529 +1432,3 @@ func (b *BlockChain) HeightRange(startHeight, endHeight int32) ([]chainhash.Hash
|
|||
})
|
||||
return hashList, err
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The threshold state consists of individual threshold cache buckets for each
|
||||
// cache id under one main threshold state bucket. Each threshold cache bucket
|
||||
// contains entries keyed by the block hash for the final block in each window
|
||||
// and their associated threshold states as well as the associated deployment
|
||||
// parameters.
|
||||
//
|
||||
// The serialized value format is for each cache entry keyed by hash is:
|
||||
//
|
||||
// <thresholdstate>
|
||||
//
|
||||
// Field Type Size
|
||||
// threshold state uint8 1 byte
|
||||
//
|
||||
//
|
||||
// In addition, the threshold cache buckets for deployments contain the specific
|
||||
// deployment parameters they were created with. This allows the cache
|
||||
// invalidation when there any changes to their definitions.
|
||||
//
|
||||
// The serialized value format for the deployment parameters is:
|
||||
//
|
||||
// <bit number><start time><expire time>
|
||||
//
|
||||
// Field Type Size
|
||||
// bit number uint8 1 byte
|
||||
// start time uint64 8 bytes
|
||||
// expire time uint64 8 bytes
|
||||
//
|
||||
//
|
||||
// Finally, the main threshold bucket also contains the number of stored
|
||||
// deployment buckets as described above.
|
||||
//
|
||||
// The serialized value format for the number of stored deployment buckets is:
|
||||
//
|
||||
// <num deployments>
|
||||
//
|
||||
// Field Type Size
|
||||
// num deployments uint32 4 bytes
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// serializeDeploymentCacheParams serializes the parameters for the passed
|
||||
// deployment into a single byte slice according to the format described in
|
||||
// detail above.
|
||||
func serializeDeploymentCacheParams(deployment *chaincfg.ConsensusDeployment) []byte {
|
||||
serialized := make([]byte, 1+8+8)
|
||||
serialized[0] = deployment.BitNumber
|
||||
byteOrder.PutUint64(serialized[1:], deployment.StartTime)
|
||||
byteOrder.PutUint64(serialized[9:], deployment.ExpireTime)
|
||||
return serialized
|
||||
}
|
||||
|
||||
// deserializeDeploymentCacheParams deserializes the passed serialized
|
||||
// deployment cache parameters into a deployment struct.
|
||||
func deserializeDeploymentCacheParams(serialized []byte) (chaincfg.ConsensusDeployment, error) {
|
||||
// Ensure the serialized data has enough bytes to properly deserialize
|
||||
// the bit number, start time, and expire time.
|
||||
if len(serialized) != 1+8+8 {
|
||||
return chaincfg.ConsensusDeployment{}, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: "corrupt deployment cache state",
|
||||
}
|
||||
}
|
||||
|
||||
var deployment chaincfg.ConsensusDeployment
|
||||
deployment.BitNumber = serialized[0]
|
||||
deployment.StartTime = byteOrder.Uint64(serialized[1:])
|
||||
deployment.ExpireTime = byteOrder.Uint64(serialized[9:])
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
// dbPutDeploymentCacheParams uses an existing database transaction to update
|
||||
// the deployment cache params with the given values.
|
||||
func dbPutDeploymentCacheParams(bucket database.Bucket, deployment *chaincfg.ConsensusDeployment) error {
|
||||
serialized := serializeDeploymentCacheParams(deployment)
|
||||
return bucket.Put(deploymentStateKeyName, serialized)
|
||||
}
|
||||
|
||||
// dbFetchDeploymentCacheParams uses an existing database transaction to
|
||||
// retrieve the deployment parameters from the given bucket, deserialize them,
|
||||
// and returns the resulting deployment struct.
|
||||
func dbFetchDeploymentCacheParams(bucket database.Bucket) (chaincfg.ConsensusDeployment, error) {
|
||||
serialized := bucket.Get(deploymentStateKeyName)
|
||||
return deserializeDeploymentCacheParams(serialized)
|
||||
}
|
||||
|
||||
// serializeNumDeployments serializes the parameters for the passed number of
|
||||
// deployments into a single byte slice according to the format described in
|
||||
// detail above.
|
||||
func serializeNumDeployments(numDeployments uint32) []byte {
|
||||
serialized := make([]byte, 4)
|
||||
byteOrder.PutUint32(serialized, numDeployments)
|
||||
return serialized
|
||||
}
|
||||
|
||||
// deserializeDeploymentCacheParams deserializes the passed serialized
|
||||
// number of deployments.
|
||||
func deserializeNumDeployments(serialized []byte) (uint32, error) {
|
||||
if len(serialized) != 4 {
|
||||
return 0, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: "corrupt stored number of deployments",
|
||||
}
|
||||
}
|
||||
return byteOrder.Uint32(serialized), nil
|
||||
}
|
||||
|
||||
// dbPutNumDeployments uses an existing database transaction to update the
|
||||
// number of deployments to the given value.
|
||||
func dbPutNumDeployments(bucket database.Bucket, numDeployments uint32) error {
|
||||
serialized := serializeNumDeployments(numDeployments)
|
||||
return bucket.Put(numDeploymentsKeyName, serialized)
|
||||
}
|
||||
|
||||
// dbFetchNumDeployments uses an existing database transaction to retrieve the
|
||||
// number of deployments, deserialize it, and returns the result.
|
||||
func dbFetchNumDeployments(bucket database.Bucket) (uint32, error) {
|
||||
// Ensure the serialized data has enough bytes to properly deserialize
|
||||
// the number of stored deployments.
|
||||
serialized := bucket.Get(numDeploymentsKeyName)
|
||||
return deserializeNumDeployments(serialized)
|
||||
}
|
||||
|
||||
// thresholdCacheBucket returns the serialized bucket name to use for a
|
||||
// threshold cache given a prefix and an ID.
|
||||
func thresholdCacheBucket(prefix []byte, id uint32) []byte {
|
||||
bucketName := make([]byte, len(prefix)+4)
|
||||
copy(bucketName, prefix)
|
||||
byteOrder.PutUint32(bucketName[len(bucketName)-4:], id)
|
||||
return bucketName
|
||||
}
|
||||
|
||||
// dbPutThresholdState uses an existing database transaction to update or add
|
||||
// the rule change threshold state for the provided block hash.
|
||||
func dbPutThresholdState(bucket database.Bucket, hash chainhash.Hash, state ThresholdState) error {
|
||||
// Add the block hash to threshold state mapping.
|
||||
var serializedState [1]byte
|
||||
serializedState[0] = byte(state)
|
||||
return bucket.Put(hash[:], serializedState[:])
|
||||
}
|
||||
|
||||
// dbPutThresholdCaches uses an existing database transaction to update the
|
||||
// provided threshold state caches using the given bucket prefix.
|
||||
func dbPutThresholdCaches(dbTx database.Tx, caches []thresholdStateCache, bucketPrefix []byte) error {
|
||||
// Loop through each of the defined cache IDs in the provided cache and
|
||||
// populate the associated bucket with all of the block hash to
|
||||
// threshold state mappings for it.
|
||||
cachesBucket := dbTx.Metadata().Bucket(thresholdBucketName)
|
||||
for i := uint32(0); i < uint32(len(caches)); i++ {
|
||||
cache := &caches[i]
|
||||
if len(cache.dbUpdates) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
cacheIDBucketName := thresholdCacheBucket(bucketPrefix, i)
|
||||
bucket := cachesBucket.Bucket(cacheIDBucketName)
|
||||
for blockHash, state := range cache.dbUpdates {
|
||||
err := dbPutThresholdState(bucket, blockHash, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// putThresholdCaches uses an existing database transaction to update the
|
||||
// threshold state caches.
|
||||
func (b *BlockChain) putThresholdCaches(dbTx database.Tx) error {
|
||||
err := dbPutThresholdCaches(dbTx, b.deploymentCaches,
|
||||
deploymentBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbPutThresholdCaches(dbTx, b.warningCaches, warningBucketName)
|
||||
}
|
||||
|
||||
// markThresholdCachesFlushed clears any pending updates to be written from
|
||||
// threshold state caches. Callers are intended to call this after the pending
|
||||
// updates have been successfully written to the database via the
|
||||
// putThresholdCaches function and its associated database transation is closed.
|
||||
// This approach is taken to ensure the memory state is not updated until after
|
||||
// the atomic database update was successful.
|
||||
func (b *BlockChain) markThresholdCachesFlushed() {
|
||||
for i := 0; i < len(b.deploymentCaches); i++ {
|
||||
b.deploymentCaches[i].MarkFlushed()
|
||||
}
|
||||
for i := 0; i < len(b.warningCaches); i++ {
|
||||
b.warningCaches[i].MarkFlushed()
|
||||
}
|
||||
}
|
||||
|
||||
// dbFetchThresholdCaches uses an existing database transaction to retrieve
|
||||
// the threshold state caches from the provided bucket prefix into the given
|
||||
// cache parameter. When the db does not contain any information for a specific
|
||||
// id within that cache, that entry will simply be empty.
|
||||
func dbFetchThresholdCaches(dbTx database.Tx, caches []thresholdStateCache, bucketPrefix []byte) error {
|
||||
// Nothing to load if the main threshold state caches bucket
|
||||
// doesn't exist.
|
||||
cachesBucket := dbTx.Metadata().Bucket(thresholdBucketName)
|
||||
if cachesBucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop through each of the cache IDs and load any saved threshold
|
||||
// states.
|
||||
for i := 0; i < len(caches); i++ {
|
||||
// Nothing to do for this cache ID if there is no bucket for it.
|
||||
cacheIDBucketName := thresholdCacheBucket(bucketPrefix, uint32(i))
|
||||
cacheIDBucket := cachesBucket.Bucket(cacheIDBucketName[:])
|
||||
if cacheIDBucket == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Load all of the cached block hash to threshold state mappings
|
||||
// from the bucket.
|
||||
err := cacheIDBucket.ForEach(func(k, v []byte) error {
|
||||
// Skip non-hash entries.
|
||||
if len(k) != chainhash.HashSize {
|
||||
return nil
|
||||
}
|
||||
|
||||
var hash chainhash.Hash
|
||||
copy(hash[:], k)
|
||||
caches[i].entries[hash] = ThresholdState(v[0])
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// invalidateThresholdCaches removes any threshold state caches that are no
|
||||
// longer valid. This can happen if a deployment ID is changed such as when it
|
||||
// is reused, or if it is reordered in the parameter definitions. It is also
|
||||
// necessary for specific bits in the warning cache when deployment definitions
|
||||
// are added and removed since it could change the expected block versions and
|
||||
// hence potentially change the result of the warning states for that bit.
|
||||
func (b *BlockChain) invalidateThresholdCaches(cachesBucket database.Bucket) error {
|
||||
deployments := b.chainParams.Deployments[:]
|
||||
|
||||
// Remove any stored deployments that are no longer defined along with
|
||||
// the warning cache associated with their bits.
|
||||
numStoredDeployments, err := dbFetchNumDeployments(cachesBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
definedDeployments := uint32(len(deployments))
|
||||
for i := definedDeployments; i < numStoredDeployments; i++ {
|
||||
// Nothing to do when nothing is stored for the deployment.
|
||||
deployBucketKey := thresholdCacheBucket(deploymentBucketName, i)
|
||||
deployBucket := cachesBucket.Bucket(deployBucketKey)
|
||||
if deployBucket == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Load the deployment details the cache was created for from
|
||||
// the database.
|
||||
stored, err := dbFetchDeploymentCacheParams(deployBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the warning cache for the bit associated with the old
|
||||
// deployment definition.
|
||||
oldBit := uint32(stored.BitNumber)
|
||||
bn := thresholdCacheBucket(warningBucketName, oldBit)
|
||||
err = cachesBucket.DeleteBucket(bn)
|
||||
if err != nil && !isDbBucketNotFoundErr(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove deployment state and cache.
|
||||
err = cachesBucket.DeleteBucket(deployBucketKey)
|
||||
if err != nil && !isDbBucketNotFoundErr(err) {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Removed threshold state caches for deployment %d "+
|
||||
"and warning bit %d", i, oldBit)
|
||||
}
|
||||
|
||||
// Remove any deployment caches that no longer match the associated
|
||||
// deployment definition.
|
||||
for i := uint32(0); i < uint32(len(deployments)); i++ {
|
||||
// Remove the warning cache for the bit associated with the new
|
||||
// deployment definition if nothing is already stored for the
|
||||
// deployment.
|
||||
deployBucketKey := thresholdCacheBucket(deploymentBucketName, i)
|
||||
deployBucket := cachesBucket.Bucket(deployBucketKey)
|
||||
if deployBucket == nil {
|
||||
// Remove the warning cache for the bit associated with
|
||||
// the new deployment definition.
|
||||
newBit := uint32(deployments[i].BitNumber)
|
||||
bn := thresholdCacheBucket(warningBucketName, newBit)
|
||||
err = cachesBucket.DeleteBucket(bn)
|
||||
if err != nil && !isDbBucketNotFoundErr(err) {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Removed threshold state cache for warning "+
|
||||
"bit %d ", newBit)
|
||||
continue
|
||||
}
|
||||
|
||||
// Load the deployment details the cache was created for from
|
||||
// the database, compare them against the currently defined
|
||||
// deployment, and invalidate the relevant caches if they don't
|
||||
// match.
|
||||
stored, err := dbFetchDeploymentCacheParams(deployBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stored != deployments[i] {
|
||||
// Remove deployment state and cache.
|
||||
err := cachesBucket.DeleteBucket(deployBucketKey)
|
||||
if err != nil && !isDbBucketNotFoundErr(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the warning cache for the bit associated with
|
||||
// the new deployment definition.
|
||||
newBit := uint32(deployments[i].BitNumber)
|
||||
bn := thresholdCacheBucket(warningBucketName, newBit)
|
||||
err = cachesBucket.DeleteBucket(bn)
|
||||
if err != nil && !isDbBucketNotFoundErr(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the warning cache for the bit associated with
|
||||
// the old deployment definition if it is different than
|
||||
// the new one.
|
||||
oldBit := uint32(stored.BitNumber)
|
||||
if oldBit == newBit {
|
||||
log.Debugf("Removed threshold state caches for "+
|
||||
"deployment %d and warning bit %d", i,
|
||||
newBit)
|
||||
continue
|
||||
}
|
||||
bn = thresholdCacheBucket(warningBucketName, oldBit)
|
||||
err = cachesBucket.DeleteBucket(bn)
|
||||
if err != nil && !isDbBucketNotFoundErr(err) {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Removed threshold state caches for "+
|
||||
"deployment %d and warning bits %d and %d", i,
|
||||
oldBit, newBit)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initThresholdCacheBuckets creates any missing buckets needed for the defined
|
||||
// threshold caches and populates them with state-related details so they can
|
||||
// be invalidated as needed.
|
||||
func (b *BlockChain) initThresholdCacheBuckets(meta database.Bucket) error {
|
||||
// Create overall bucket that houses all of the threshold caches and
|
||||
// their related state as needed.
|
||||
cachesBucket, err := meta.CreateBucketIfNotExists(thresholdBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the number of stored deployment as needed.
|
||||
definedDeployments := uint32(len(b.deploymentCaches))
|
||||
storedDeployments, err := dbFetchNumDeployments(cachesBucket)
|
||||
if err != nil || storedDeployments != definedDeployments {
|
||||
err := dbPutNumDeployments(cachesBucket, definedDeployments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create buckets for each of the deployment caches as needed, and
|
||||
// populate the created buckets with the specific deployment details so
|
||||
// that the cache(s) can be invalidated properly with future updates.
|
||||
for i := uint32(0); i < definedDeployments; i++ {
|
||||
name := thresholdCacheBucket(deploymentBucketName, i)
|
||||
if bucket := cachesBucket.Bucket(name); bucket != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
deployBucket, err := cachesBucket.CreateBucket(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deployment := &b.chainParams.Deployments[i]
|
||||
err = dbPutDeploymentCacheParams(deployBucket, deployment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create buckets for each of the warning caches as needed.
|
||||
for i := uint32(0); i < uint32(len(b.warningCaches)); i++ {
|
||||
name := thresholdCacheBucket(warningBucketName, i)
|
||||
_, err := cachesBucket.CreateBucketIfNotExists(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initThresholdCaches initializes the threshold state caches from the database.
|
||||
// When the db does not yet contain any information for a specific threshold
|
||||
// cache or a given id within that cache, it will simply be empty which will
|
||||
// lead to it being calculated as needed.
|
||||
func (b *BlockChain) initThresholdCaches() error {
|
||||
// Create and initialize missing threshold state cache buckets and
|
||||
// remove any that are no longer valid.
|
||||
err := b.db.Update(func(dbTx database.Tx) error {
|
||||
meta := dbTx.Metadata()
|
||||
cachesBucket := meta.Bucket(thresholdBucketName)
|
||||
if cachesBucket != nil {
|
||||
err := b.invalidateThresholdCaches(cachesBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create all cache buckets as needed.
|
||||
return b.initThresholdCacheBuckets(meta)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the deployment caches.
|
||||
err = b.db.View(func(dbTx database.Tx) error {
|
||||
// Load the deployment threshold states.
|
||||
err := dbFetchThresholdCaches(dbTx, b.deploymentCaches,
|
||||
deploymentBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the warning threshold states.
|
||||
return dbFetchThresholdCaches(dbTx, b.warningCaches,
|
||||
warningBucketName)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Inform the user the states might take a while to recalculate if any
|
||||
// of the threshold state caches aren't populated.
|
||||
var showMsg bool
|
||||
for i := 0; i < len(b.warningCaches); i++ {
|
||||
if len(b.warningCaches[i].entries) == 0 {
|
||||
showMsg = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !showMsg {
|
||||
for i := 0; i < len(b.deploymentCaches); i++ {
|
||||
if len(b.deploymentCaches[i].entries) == 0 {
|
||||
showMsg = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if showMsg {
|
||||
log.Info("Recalculating threshold states due to definition " +
|
||||
"change. This might take a while...")
|
||||
}
|
||||
|
||||
// Initialize the warning and deployment caches by calculating the
|
||||
// threshold state for each of them. This will ensure the caches are
|
||||
// populated and any states that needed to be recalculated due to
|
||||
// definition changes is done now.
|
||||
prevNode := b.bestNode.parent
|
||||
for bit := uint32(0); bit < vbNumBits; bit++ {
|
||||
checker := bitConditionChecker{bit: bit, chain: b}
|
||||
cache := &b.warningCaches[bit]
|
||||
_, err := b.thresholdState(prevNode, checker, cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for id := 0; id < len(b.chainParams.Deployments); id++ {
|
||||
deployment := &b.chainParams.Deployments[id]
|
||||
cache := &b.deploymentCaches[id]
|
||||
checker := deploymentChecker{deployment: deployment, chain: b}
|
||||
_, err := b.thresholdState(prevNode, checker, cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// No warnings about unknown rules or versions until the chain is
|
||||
// current.
|
||||
if b.isCurrent() {
|
||||
// Warn if a high enough percentage of the last blocks have
|
||||
// unexpected versions.
|
||||
if err := b.warnUnknownVersions(b.bestNode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Warn if any unknown new rules are either about to activate or
|
||||
// have already been activated.
|
||||
if err := b.warnUnknownRuleActivations(b.bestNode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update the cached threshold states in the database as needed.
|
||||
err = b.db.Update(func(dbTx database.Tx) error {
|
||||
return b.putThresholdCaches(dbTx)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark all modified entries in the threshold caches as flushed now that
|
||||
// they have been committed to the database.
|
||||
b.markThresholdCachesFlushed()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,37 +15,34 @@ import (
|
|||
type ThresholdState byte
|
||||
|
||||
// These constants are used to identify specific threshold states.
|
||||
//
|
||||
// NOTE: This section specifically does not use iota for the individual states
|
||||
// since these values are serialized and must be stable for long-term storage.
|
||||
const (
|
||||
// ThresholdDefined is the first state for each deployment and is the
|
||||
// state for the genesis block has by definition for all deployments.
|
||||
ThresholdDefined ThresholdState = 0
|
||||
ThresholdDefined ThresholdState = iota
|
||||
|
||||
// ThresholdStarted is the state for a deployment once its start time
|
||||
// has been reached.
|
||||
ThresholdStarted ThresholdState = 1
|
||||
ThresholdStarted
|
||||
|
||||
// ThresholdLockedIn is the state for a deployment during the retarget
|
||||
// period which is after the ThresholdStarted state period and the
|
||||
// number of blocks that have voted for the deployment equal or exceed
|
||||
// the required number of votes for the deployment.
|
||||
ThresholdLockedIn ThresholdState = 2
|
||||
ThresholdLockedIn
|
||||
|
||||
// ThresholdActive is the state for a deployment for all blocks after a
|
||||
// retarget period in which the deployment was in the ThresholdLockedIn
|
||||
// state.
|
||||
ThresholdActive ThresholdState = 3
|
||||
ThresholdActive
|
||||
|
||||
// ThresholdFailed is the state for a deployment once its expiration
|
||||
// time has been reached and it did not reach the ThresholdLockedIn
|
||||
// state.
|
||||
ThresholdFailed ThresholdState = 4
|
||||
ThresholdFailed
|
||||
|
||||
// numThresholdsStates is the maximum number of threshold states used in
|
||||
// tests.
|
||||
numThresholdsStates = iota
|
||||
numThresholdsStates
|
||||
)
|
||||
|
||||
// thresholdStateStrings is a map of ThresholdState values back to their
|
||||
|
@ -94,11 +91,9 @@ type thresholdConditionChecker interface {
|
|||
}
|
||||
|
||||
// thresholdStateCache provides a type to cache the threshold states of each
|
||||
// threshold window for a set of IDs. It also keeps track of which entries have
|
||||
// been modified and therefore need to be written to the database.
|
||||
// threshold window for a set of IDs.
|
||||
type thresholdStateCache struct {
|
||||
dbUpdates map[chainhash.Hash]ThresholdState
|
||||
entries map[chainhash.Hash]ThresholdState
|
||||
entries map[chainhash.Hash]ThresholdState
|
||||
}
|
||||
|
||||
// Lookup returns the threshold state associated with the given hash along with
|
||||
|
@ -109,33 +104,18 @@ func (c *thresholdStateCache) Lookup(hash *chainhash.Hash) (ThresholdState, bool
|
|||
}
|
||||
|
||||
// Update updates the cache to contain the provided hash to threshold state
|
||||
// mapping while properly tracking needed updates flush changes to the database.
|
||||
// mapping.
|
||||
func (c *thresholdStateCache) Update(hash *chainhash.Hash, state ThresholdState) {
|
||||
if existing, ok := c.entries[*hash]; ok && existing == state {
|
||||
return
|
||||
}
|
||||
|
||||
c.dbUpdates[*hash] = state
|
||||
c.entries[*hash] = state
|
||||
}
|
||||
|
||||
// MarkFlushed marks all of the current udpates as flushed to the database.
|
||||
// This is useful so the caller can ensure the needed database updates are not
|
||||
// lost until they have successfully been written to the database.
|
||||
func (c *thresholdStateCache) MarkFlushed() {
|
||||
for hash := range c.dbUpdates {
|
||||
delete(c.dbUpdates, hash)
|
||||
}
|
||||
}
|
||||
|
||||
// newThresholdCaches returns a new array of caches to be used when calculating
|
||||
// threshold states.
|
||||
func newThresholdCaches(numCaches uint32) []thresholdStateCache {
|
||||
caches := make([]thresholdStateCache, numCaches)
|
||||
for i := 0; i < len(caches); i++ {
|
||||
caches[i] = thresholdStateCache{
|
||||
entries: make(map[chainhash.Hash]ThresholdState),
|
||||
dbUpdates: make(map[chainhash.Hash]ThresholdState),
|
||||
entries: make(map[chainhash.Hash]ThresholdState),
|
||||
}
|
||||
}
|
||||
return caches
|
||||
|
@ -327,3 +307,49 @@ func (b *BlockChain) deploymentState(prevNode *blockNode, deploymentID uint32) (
|
|||
|
||||
return b.thresholdState(prevNode, checker, cache)
|
||||
}
|
||||
|
||||
// initThresholdCaches initializes the threshold state caches for each warning
|
||||
// bit and defined deployment and provides warnings if the chain is current per
|
||||
// the warnUnknownVersions and warnUnknownRuleActivations functions.
|
||||
func (b *BlockChain) initThresholdCaches() error {
|
||||
// Initialize the warning and deployment caches by calculating the
|
||||
// threshold state for each of them. This will ensure the caches are
|
||||
// populated and any states that needed to be recalculated due to
|
||||
// definition changes is done now.
|
||||
prevNode := b.bestNode.parent
|
||||
for bit := uint32(0); bit < vbNumBits; bit++ {
|
||||
checker := bitConditionChecker{bit: bit, chain: b}
|
||||
cache := &b.warningCaches[bit]
|
||||
_, err := b.thresholdState(prevNode, checker, cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for id := 0; id < len(b.chainParams.Deployments); id++ {
|
||||
deployment := &b.chainParams.Deployments[id]
|
||||
cache := &b.deploymentCaches[id]
|
||||
checker := deploymentChecker{deployment: deployment, chain: b}
|
||||
_, err := b.thresholdState(prevNode, checker, cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// No warnings about unknown rules or versions until the chain is
|
||||
// current.
|
||||
if b.isCurrent() {
|
||||
// Warn if a high enough percentage of the last blocks have
|
||||
// unexpected versions.
|
||||
if err := b.warnUnknownVersions(b.bestNode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Warn if any unknown new rules are either about to activate or
|
||||
// have already been activated.
|
||||
if err := b.warnUnknownRuleActivations(b.bestNode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -92,48 +92,8 @@ nextTest:
|
|||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure the update is also added to the internal
|
||||
// database updates map and its state matches.
|
||||
state, ok = cache.dbUpdates[hash]
|
||||
if !ok {
|
||||
t.Errorf("dbUpdates (%s): missing entry for "+
|
||||
"hash %v", test.name, hash)
|
||||
continue nextTest
|
||||
}
|
||||
if state != test.state {
|
||||
t.Errorf("dbUpdates (%s): state mismatch - "+
|
||||
"got %v, want %v", test.name, state,
|
||||
test.state)
|
||||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure flushing the cache removes all entries from
|
||||
// the internal database updates map.
|
||||
cache.MarkFlushed()
|
||||
if len(cache.dbUpdates) != 0 {
|
||||
t.Errorf("dbUpdates (%s): unflushed entries",
|
||||
test.name)
|
||||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure hash is still available in the cache and the
|
||||
// state is the expected value.
|
||||
state, ok = cache.Lookup(&hash)
|
||||
if !ok {
|
||||
t.Errorf("Lookup (%s): missing entry after "+
|
||||
"flush for hash %v", test.name, hash)
|
||||
continue nextTest
|
||||
}
|
||||
if state != test.state {
|
||||
t.Errorf("Lookup (%s): state mismatch after "+
|
||||
"flush - got %v, want %v", test.name,
|
||||
state, test.state)
|
||||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure adding an existing hash with the same state
|
||||
// doesn't break the existing entry and it is NOT added
|
||||
// to the database updates map.
|
||||
// doesn't break the existing entry.
|
||||
cache.Update(&hash, test.state)
|
||||
state, ok = cache.Lookup(&hash)
|
||||
if !ok {
|
||||
|
@ -148,11 +108,6 @@ nextTest:
|
|||
test.name, state, test.state)
|
||||
continue nextTest
|
||||
}
|
||||
if len(cache.dbUpdates) != 0 {
|
||||
t.Errorf("dbUpdates (%s): unflushed entries "+
|
||||
"after duplicate add", test.name)
|
||||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure adding an existing hash with a different state
|
||||
// updates the existing entry.
|
||||
|
@ -174,22 +129,6 @@ nextTest:
|
|||
test.name, state, newState)
|
||||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure the update is also added to the internal
|
||||
// database updates map and its state matches.
|
||||
state, ok = cache.dbUpdates[hash]
|
||||
if !ok {
|
||||
t.Errorf("dbUpdates (%s): missing entry after "+
|
||||
"state change for hash %v", test.name,
|
||||
hash)
|
||||
continue nextTest
|
||||
}
|
||||
if state != newState {
|
||||
t.Errorf("dbUpdates (%s): state mismatch "+
|
||||
"after state change - got %v, want %v",
|
||||
test.name, state, newState)
|
||||
continue nextTest
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue