mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-05 21:56:50 +00:00
When the pruning point moves, update its UTXO set outside of a database transaction (#1444)
* Remove pruningPointUTXOSetStaging and implement UpdatePruningPointUTXOSet. * Implement StageStartSavingNewPruningPointUTXOSet, HadStartedSavingNewPruningPointUTXOSet, and FinishSavingNewPruningPointUTXOSet. * Fix a bad return. * Implement UpdatePruningPointUTXOSetIfRequired. * Call UpdatePruningPointUTXOSetIfRequired on consensus creation. * Rename savingNewPruningPointUTXOSetKey to updatingPruningPointUTXOSet. * Add a log. * Add calls to runtime.GC() at its start and end. * Rename a variable. * Replace calls to runtime.GC to calls to LogMemoryStats. * Wrap the contents of LogMemoryStats in a log closure.
This commit is contained in:
parent
9a17198e7d
commit
ca04c049ab
@ -11,6 +11,7 @@ import (
|
|||||||
var pruningBlockHashKey = database.MakeBucket(nil).Key([]byte("pruning-block-hash"))
|
var pruningBlockHashKey = database.MakeBucket(nil).Key([]byte("pruning-block-hash"))
|
||||||
var candidatePruningPointHashKey = database.MakeBucket(nil).Key([]byte("candidate-pruning-point-hash"))
|
var candidatePruningPointHashKey = database.MakeBucket(nil).Key([]byte("candidate-pruning-point-hash"))
|
||||||
var pruningPointUTXOSetBucket = database.MakeBucket([]byte("pruning-point-utxo-set"))
|
var pruningPointUTXOSetBucket = database.MakeBucket([]byte("pruning-point-utxo-set"))
|
||||||
|
var updatingPruningPointUTXOSetKey = database.MakeBucket(nil).Key([]byte("updating-pruning-point-utxo-set"))
|
||||||
|
|
||||||
// pruningStore represents a store for the current pruning state
|
// pruningStore represents a store for the current pruning state
|
||||||
type pruningStore struct {
|
type pruningStore struct {
|
||||||
@ -19,7 +20,7 @@ type pruningStore struct {
|
|||||||
pruningPointCandidateStaging *externalapi.DomainHash
|
pruningPointCandidateStaging *externalapi.DomainHash
|
||||||
pruningPointCandidateCache *externalapi.DomainHash
|
pruningPointCandidateCache *externalapi.DomainHash
|
||||||
|
|
||||||
pruningPointUTXOSetStaging model.ReadOnlyUTXOSetIterator
|
startUpdatingPruningPointUTXOSetStaging bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New instantiates a new PruningStore
|
// New instantiates a new PruningStore
|
||||||
@ -70,17 +71,13 @@ func (ps *pruningStore) StagePruningPoint(pruningPointBlockHash *externalapi.Dom
|
|||||||
ps.pruningPointStaging = pruningPointBlockHash
|
ps.pruningPointStaging = pruningPointBlockHash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *pruningStore) StagePruningPointUTXOSet(pruningPointUTXOSetIterator model.ReadOnlyUTXOSetIterator) {
|
|
||||||
ps.pruningPointUTXOSetStaging = pruningPointUTXOSetIterator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *pruningStore) IsStaged() bool {
|
func (ps *pruningStore) IsStaged() bool {
|
||||||
return ps.pruningPointStaging != nil || ps.pruningPointUTXOSetStaging != nil
|
return ps.pruningPointStaging != nil || ps.startUpdatingPruningPointUTXOSetStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *pruningStore) Discard() {
|
func (ps *pruningStore) Discard() {
|
||||||
ps.pruningPointStaging = nil
|
ps.pruningPointStaging = nil
|
||||||
ps.pruningPointUTXOSetStaging = nil
|
ps.startUpdatingPruningPointUTXOSetStaging = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *pruningStore) Commit(dbTx model.DBTransaction) error {
|
func (ps *pruningStore) Commit(dbTx model.DBTransaction) error {
|
||||||
@ -108,49 +105,60 @@ func (ps *pruningStore) Commit(dbTx model.DBTransaction) error {
|
|||||||
ps.pruningPointCandidateCache = ps.pruningPointCandidateStaging
|
ps.pruningPointCandidateCache = ps.pruningPointCandidateStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
if ps.pruningPointUTXOSetStaging != nil {
|
if ps.startUpdatingPruningPointUTXOSetStaging {
|
||||||
// Delete all the old UTXOs from the database
|
err := dbTx.Put(updatingPruningPointUTXOSetKey, []byte{0})
|
||||||
deleteCursor, err := dbTx.Cursor(pruningPointUTXOSetBucket)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
|
|
||||||
key, err := deleteCursor.Key()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = dbTx.Delete(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert all the new UTXOs into the database
|
|
||||||
for ok := ps.pruningPointUTXOSetStaging.First(); ok; ok = ps.pruningPointUTXOSetStaging.Next() {
|
|
||||||
outpoint, entry, err := ps.pruningPointUTXOSetStaging.Get()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
serializedOutpoint, err := serializeOutpoint(outpoint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key := pruningPointUTXOSetBucket.Key(serializedOutpoint)
|
|
||||||
serializedUTXOEntry, err := serializeUTXOEntry(entry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = dbTx.Put(key, serializedUTXOEntry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.Discard()
|
ps.Discard()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ps *pruningStore) UpdatePruningPointUTXOSet(dbContext model.DBWriter,
|
||||||
|
utxoSetIterator model.ReadOnlyUTXOSetIterator) error {
|
||||||
|
|
||||||
|
// Delete all the old UTXOs from the database
|
||||||
|
deleteCursor, err := dbContext.Cursor(pruningPointUTXOSetBucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
|
||||||
|
key, err := deleteCursor.Key()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dbContext.Delete(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert all the new UTXOs into the database
|
||||||
|
for ok := utxoSetIterator.First(); ok; ok = utxoSetIterator.Next() {
|
||||||
|
outpoint, entry, err := utxoSetIterator.Get()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
serializedOutpoint, err := serializeOutpoint(outpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := pruningPointUTXOSetBucket.Key(serializedOutpoint)
|
||||||
|
serializedUTXOEntry, err := serializeUTXOEntry(entry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dbContext.Put(key, serializedUTXOEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// PruningPoint gets the current pruning point
|
// PruningPoint gets the current pruning point
|
||||||
func (ps *pruningStore) PruningPoint(dbContext model.DBReader) (*externalapi.DomainHash, error) {
|
func (ps *pruningStore) PruningPoint(dbContext model.DBReader) (*externalapi.DomainHash, error) {
|
||||||
if ps.pruningPointStaging != nil {
|
if ps.pruningPointStaging != nil {
|
||||||
@ -241,3 +249,15 @@ func (ps *pruningStore) PruningPointUTXOs(dbContext model.DBReader,
|
|||||||
}
|
}
|
||||||
return outpointAndUTXOEntryPairs, nil
|
return outpointAndUTXOEntryPairs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ps *pruningStore) StageStartUpdatingPruningPointUTXOSet() {
|
||||||
|
ps.startUpdatingPruningPointUTXOSetStaging = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *pruningStore) HadStartedUpdatingPruningPointUTXOSet(dbContext model.DBWriter) (bool, error) {
|
||||||
|
return dbContext.Has(updatingPruningPointUTXOSetKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *pruningStore) FinishUpdatingPruningPointUTXOSet(dbContext model.DBWriter) error {
|
||||||
|
return dbContext.Delete(updatingPruningPointUTXOSetKey)
|
||||||
|
}
|
||||||
|
@ -365,6 +365,10 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
err = pruningManager.UpdatePruningPointUTXOSetIfRequired()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,18 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|||||||
type PruningStore interface {
|
type PruningStore interface {
|
||||||
Store
|
Store
|
||||||
StagePruningPoint(pruningPointBlockHash *externalapi.DomainHash)
|
StagePruningPoint(pruningPointBlockHash *externalapi.DomainHash)
|
||||||
StagePruningPointUTXOSet(pruningPointUTXOSetIterator ReadOnlyUTXOSetIterator)
|
|
||||||
StagePruningPointCandidate(candidate *externalapi.DomainHash)
|
StagePruningPointCandidate(candidate *externalapi.DomainHash)
|
||||||
IsStaged() bool
|
IsStaged() bool
|
||||||
PruningPointCandidate(dbContext DBReader) (*externalapi.DomainHash, error)
|
PruningPointCandidate(dbContext DBReader) (*externalapi.DomainHash, error)
|
||||||
HasPruningPointCandidate(dbContext DBReader) (bool, error)
|
HasPruningPointCandidate(dbContext DBReader) (bool, error)
|
||||||
PruningPoint(dbContext DBReader) (*externalapi.DomainHash, error)
|
PruningPoint(dbContext DBReader) (*externalapi.DomainHash, error)
|
||||||
HasPruningPoint(dbContext DBReader) (bool, error)
|
HasPruningPoint(dbContext DBReader) (bool, error)
|
||||||
|
|
||||||
|
StageStartUpdatingPruningPointUTXOSet()
|
||||||
|
HadStartedUpdatingPruningPointUTXOSet(dbContext DBWriter) (bool, error)
|
||||||
|
FinishUpdatingPruningPointUTXOSet(dbContext DBWriter) error
|
||||||
|
UpdatePruningPointUTXOSet(dbContext DBWriter, utxoSetIterator ReadOnlyUTXOSetIterator) error
|
||||||
|
|
||||||
ClearImportedPruningPointUTXOs(dbContext DBWriter) error
|
ClearImportedPruningPointUTXOs(dbContext DBWriter) error
|
||||||
AppendImportedPruningPointUTXOs(dbTx DBTransaction, outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error
|
AppendImportedPruningPointUTXOs(dbTx DBTransaction, outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error
|
||||||
ImportedPruningPointUTXOIterator(dbContext DBReader) (ReadOnlyUTXOSetIterator, error)
|
ImportedPruningPointUTXOIterator(dbContext DBReader) (ReadOnlyUTXOSetIterator, error)
|
||||||
|
@ -8,4 +8,5 @@ type PruningManager interface {
|
|||||||
IsValidPruningPoint(blockHash *externalapi.DomainHash) (bool, error)
|
IsValidPruningPoint(blockHash *externalapi.DomainHash) (bool, error)
|
||||||
ClearImportedPruningPointData() error
|
ClearImportedPruningPointData() error
|
||||||
AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error
|
AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error
|
||||||
|
UpdatePruningPointUTXOSetIfRequired() error
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,11 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = bp.pruningManager.UpdatePruningPointUTXOSetIfRequired()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
log.Debug(logger.NewLogClosure(func() string {
|
log.Debug(logger.NewLogClosure(func() string {
|
||||||
hashrate := difficulty.GetHashrateString(difficulty.CompactToBig(block.Header.Bits()), bp.targetTimePerBlock)
|
hashrate := difficulty.GetHashrateString(difficulty.CompactToBig(block.Header.Bits()), bp.targetTimePerBlock)
|
||||||
return fmt.Sprintf("Block %s validated and inserted, network hashrate: %s", blockHash, hashrate)
|
return fmt.Sprintf("Block %s validated and inserted, network hashrate: %s", blockHash, hashrate)
|
||||||
|
@ -311,20 +311,15 @@ func (pm *pruningManager) savePruningPoint(pruningPointHash *externalapi.DomainH
|
|||||||
onEnd := logger.LogAndMeasureExecutionTime(log, "pruningManager.savePruningPoint")
|
onEnd := logger.LogAndMeasureExecutionTime(log, "pruningManager.savePruningPoint")
|
||||||
defer onEnd()
|
defer onEnd()
|
||||||
|
|
||||||
utxoSetIterator, err := pm.consensusStateManager.RestorePastUTXOSetIterator(pruningPointHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is an assert that takes ~30 seconds to run
|
// TODO: This is an assert that takes ~30 seconds to run
|
||||||
// It must be removed or optimized before launching testnet
|
// It must be removed or optimized before launching testnet
|
||||||
err = pm.validateUTXOSetFitsCommitment(utxoSetIterator, pruningPointHash)
|
err := pm.validateUTXOSetFitsCommitment(pruningPointHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pm.pruningStore.StagePruningPoint(pruningPointHash)
|
pm.pruningStore.StagePruningPoint(pruningPointHash)
|
||||||
pm.pruningStore.StagePruningPointUTXOSet(utxoSetIterator)
|
pm.pruningStore.StageStartUpdatingPruningPointUTXOSet()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -417,8 +412,11 @@ func (pm *pruningManager) pruningPointCandidate() (*externalapi.DomainHash, erro
|
|||||||
|
|
||||||
// validateUTXOSetFitsCommitment makes sure that the calculated UTXOSet of the new pruning point fits the commitment.
|
// validateUTXOSetFitsCommitment makes sure that the calculated UTXOSet of the new pruning point fits the commitment.
|
||||||
// This is a sanity test, to make sure that kaspad doesn't store, and subsequently sends syncing peers the wrong UTXOSet.
|
// This is a sanity test, to make sure that kaspad doesn't store, and subsequently sends syncing peers the wrong UTXOSet.
|
||||||
func (pm *pruningManager) validateUTXOSetFitsCommitment(utxoSetIterator model.ReadOnlyUTXOSetIterator,
|
func (pm *pruningManager) validateUTXOSetFitsCommitment(pruningPointHash *externalapi.DomainHash) error {
|
||||||
pruningPointHash *externalapi.DomainHash) error {
|
utxoSetIterator, err := pm.consensusStateManager.RestorePastUTXOSetIterator(pruningPointHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
utxoSetMultiset := multiset.New()
|
utxoSetMultiset := multiset.New()
|
||||||
for ok := utxoSetIterator.First(); ok; ok = utxoSetIterator.Next() {
|
for ok := utxoSetIterator.First(); ok; ok = utxoSetIterator.Next() {
|
||||||
@ -500,3 +498,51 @@ func (pm *pruningManager) AppendImportedPruningPointUTXOs(
|
|||||||
|
|
||||||
return dbTx.Commit()
|
return dbTx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pm *pruningManager) UpdatePruningPointUTXOSetIfRequired() error {
|
||||||
|
hadStartedUpdatingPruningPointUTXOSet, err := pm.pruningStore.HadStartedUpdatingPruningPointUTXOSet(pm.databaseContext)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !hadStartedUpdatingPruningPointUTXOSet {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Pruning point UTXO set update is required")
|
||||||
|
err = pm.updatePruningPointUTXOSet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("Pruning point UTXO set updated")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pruningManager) updatePruningPointUTXOSet() error {
|
||||||
|
onEnd := logger.LogAndMeasureExecutionTime(log, "updatePruningPointUTXOSet")
|
||||||
|
defer onEnd()
|
||||||
|
|
||||||
|
logger.LogMemoryStats(log, "updatePruningPointUTXOSet start")
|
||||||
|
defer logger.LogMemoryStats(log, "updatePruningPointUTXOSet end")
|
||||||
|
|
||||||
|
log.Debugf("Getting the pruning point")
|
||||||
|
pruningPoint, err := pm.pruningStore.PruningPoint(pm.databaseContext)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Restoring the pruning point UTXO set")
|
||||||
|
utxoSetIterator, err := pm.consensusStateManager.RestorePastUTXOSetIterator(pruningPoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Updating the pruning point UTXO set")
|
||||||
|
err = pm.pruningStore.UpdatePruningPointUTXOSet(pm.databaseContext, utxoSetIterator)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Finishing updating the pruning point UTXO set")
|
||||||
|
return pm.pruningStore.FinishUpdatingPruningPointUTXOSet(pm.databaseContext)
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,3 +17,13 @@ func LogAndMeasureExecutionTime(log *Logger, functionName string) (onEnd func())
|
|||||||
log.Debugf("%s end. Took: %s", functionName, time.Since(start))
|
log.Debugf("%s end. Took: %s", functionName, time.Since(start))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogMemoryStats logs memory stats for `functionName`
|
||||||
|
func LogMemoryStats(log *Logger, functionName string) {
|
||||||
|
log.Debug(NewLogClosure(func() string {
|
||||||
|
stats := runtime.MemStats{}
|
||||||
|
runtime.ReadMemStats(&stats)
|
||||||
|
return fmt.Sprintf("%s: used memory: %d bytes, total: %d bytes", functionName,
|
||||||
|
stats.Alloc, stats.HeapIdle-stats.HeapReleased+stats.HeapInuse)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user