From 29ba10400691033a7d50a51c098b5ac47ddf1c55 Mon Sep 17 00:00:00 2001 From: allocz Date: Fri, 8 May 2026 17:37:31 +0000 Subject: [PATCH 1/2] chaincfg: update checkpoints for assumevalid and add assumeutxo checkpoints. --- chaincfg/params.go | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/chaincfg/params.go b/chaincfg/params.go index 14056d7137..17d806a460 100644 --- a/chaincfg/params.go +++ b/chaincfg/params.go @@ -79,6 +79,15 @@ type Checkpoint struct { Hash *chainhash.Hash } +// AssumeUTXOCheckpoint identifies a known hash of the UTXOSet. This enables +// faster IBD by avoiding several validations. The hash is reproducible at the +// height and is compatible with the Bitcoin Core assumeutxo hashes. +type AssumeUTXOCheckpoint struct { + Height int32 + UTXOSetHash [32]byte + BlockHash [32]byte +} + // EffectiveAlwaysActiveHeight returns the effective activation height for the // deployment. If AlwaysActiveHeight is unset (i.e. zero), it returns // the maximum uint32 value to indicate that it does not force activation. @@ -261,6 +270,9 @@ type Params struct { // Checkpoints ordered from oldest to newest. Checkpoints []Checkpoint + // AssumeUTXOCheckpoints from oldest to newest + AssumeUTXOCheckpoints []AssumeUTXOCheckpoint + // These fields are related to voting on consensus rule changes as // defined by BIP0009. // @@ -372,6 +384,16 @@ var MainNetParams = Params{ {781565, newHashFromStr("00000000000000000002b8c04999434c33b8e033f11a977b288f8411766ee61c")}, {800000, newHashFromStr("00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054")}, {810000, newHashFromStr("000000000000000000028028ca82b6aa81ce789e4eb9e0321b74c3cbaf405dd1")}, + {938343, newHashFromStr("00000000000000000000ccebd6d74d9194d8dcdc1d177c478e094bfad51ba5ac")}, + }, + + // AssumeUTXOCheckpoints from oldest to newest + AssumeUTXOCheckpoints: []AssumeUTXOCheckpoint{ + { + Height: 935000, + UTXOSetHash: *newHashFromStr("e4b90ef9eae834f56c4b64d2d50143cee10ad87994c614d7d04125e2a6025050"), + BlockHash: *newHashFromStr("0000000000000000000147034958af1652b2b91bba607beacc5e72a56f0fb5ee"), + }, }, // Consensus rule change deployments. @@ -1028,7 +1050,18 @@ func CustomSignetParams(challenge []byte, dnsSeeds []DNSSeed) Params { GenerateSupported: false, // Checkpoints ordered from oldest to newest. - Checkpoints: nil, + Checkpoints: []Checkpoint{ + {293175, newHashFromStr("00000008414aab61092ef93f1aacc54cf9e9f16af29ddad493b908a01ff5c329")}, + }, + + // AssumeUTXOCheckpoints from oldest to newest + AssumeUTXOCheckpoints: []AssumeUTXOCheckpoint{ + { + Height: 290000, + UTXOSetHash: *newHashFromStr("97267e000b4b876800167e71b9123f1529d13b14308abec2888bbd2160d14545"), + BlockHash: *newHashFromStr("0000000577f2741bb30cd9d39d6d71b023afbeb9764f6260786a97969d5c9ac0"), + }, + }, // Consensus rule change deployments. // From 6cccc588c57a11b4c4effcc7627804a62ef73f3c Mon Sep 17 00:00:00 2001 From: allocz Date: Fri, 8 May 2026 17:40:43 +0000 Subject: [PATCH 2/2] blockchain,netsync,server: avoid fetching prevouts from disk on cache misses, and use hardcoded utxoset hash to assert correct utxoset state. Doing this removes de previous main bottleneck when executing IBD in machines constrained in RAM. The assumevalid checkpoints gives us the proof that the old blocks are consensus compliant and the utxoset hash confirms that there's no software or hardware failures causing a bad utxoset index. The trust assumption is the code and the full validation can be performed by executing btcd with `--nocheckpoints` --- blockchain/chain.go | 217 +++++++++++++++++++++++++++++++++----- blockchain/chainio.go | 8 ++ blockchain/checkpoints.go | 9 ++ blockchain/process.go | 6 ++ blockchain/utxocache.go | 70 ++++++++++-- netsync/manager.go | 88 ++++++++++++++++ server.go | 21 ++-- 7 files changed, 371 insertions(+), 48 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index fd11045837..cb7ba3f339 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -7,7 +7,10 @@ package blockchain import ( "container/list" + "crypto/sha256" + "encoding/binary" "fmt" + "io" "sync" "time" @@ -94,14 +97,16 @@ type BlockChain struct { // The following fields are set when the instance is created and can't // be changed afterwards, so there is no need to protect them with a // separate mutex. - checkpoints []chaincfg.Checkpoint - checkpointsByHeight map[int32]*chaincfg.Checkpoint - db database.DB - chainParams *chaincfg.Params - timeSource MedianTimeSource - sigCache *txscript.SigCache - indexManager IndexManager - hashCache *txscript.HashCache + checkpoints []chaincfg.Checkpoint + checkpointsByHeight map[int32]*chaincfg.Checkpoint + assumeUTXOCheckpoints []chaincfg.AssumeUTXOCheckpoint + assumeUTXOCheckpointsByHeight map[int32]*chaincfg.AssumeUTXOCheckpoint + db database.DB + chainParams *chaincfg.Params + timeSource MedianTimeSource + sigCache *txscript.SigCache + indexManager IndexManager + hashCache *txscript.HashCache // The following fields are calculated based upon the provided chain // parameters. They are also set when the instance is created and @@ -1215,7 +1220,8 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla // Connect the transactions to the cache. All the txs are considered valid // at this point as they have passed validation or was considered valid already. stxos := make([]SpentTxOut, 0, countSpentOutputs(block)) - err := b.utxoCache.connectTransactions(block, &stxos) + err := b.utxoCache.connectTransactions2(block, &stxos, + cacheOpts{bflags: flags}) if err != nil { return false, err } @@ -2175,6 +2181,14 @@ type Config struct { // checkpoints. Checkpoints []chaincfg.Checkpoint + // AssumeUTXOCheckpoints hold caller-defined assumeutxo checkpoints that + // should be added to the default assumeutxo checkpoints in ChainParams. + // Checkpoints must be sorted by height. + // + // This field can be nil if the caller does not wish to specify any + // checkpoints. + AssumeUTXOCheckpoints []chaincfg.AssumeUTXOCheckpoint + // TimeSource defines the median time source to use for things such as // block processing and determining whether or not the chain is current. // @@ -2246,31 +2260,54 @@ func New(config *Config) (*BlockChain, error) { } } + // Generate a assumeutxo checkpoint by height map from the provided + // checkpoints and assert the provided checkpoints are sorted by height + // as required. + var auCheckpointsByHeight map[int32]*chaincfg.AssumeUTXOCheckpoint + var auPrevCheckpointHeight int32 + if len(config.AssumeUTXOCheckpoints) > 0 { + auCheckpointsByHeight = make( + map[int32]*chaincfg.AssumeUTXOCheckpoint) + + for i := range config.AssumeUTXOCheckpoints { + checkpoint := &config.AssumeUTXOCheckpoints[i] + if checkpoint.Height <= auPrevCheckpointHeight { + return nil, AssertError("blockchain.New " + + "checkpoints are not sorted by height") + } + + auCheckpointsByHeight[checkpoint.Height] = checkpoint + auPrevCheckpointHeight = checkpoint.Height + } + } + params := config.ChainParams targetTimespan := int64(params.TargetTimespan / time.Second) targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second) adjustmentFactor := params.RetargetAdjustmentFactor b := BlockChain{ - checkpoints: config.Checkpoints, - checkpointsByHeight: checkpointsByHeight, - db: config.DB, - chainParams: params, - timeSource: config.TimeSource, - sigCache: config.SigCache, - indexManager: config.IndexManager, - minRetargetTimespan: targetTimespan / adjustmentFactor, - maxRetargetTimespan: targetTimespan * adjustmentFactor, - blocksPerRetarget: int32(targetTimespan / targetTimePerBlock), - index: newBlockIndex(config.DB, params), - utxoCache: newUtxoCache(config.DB, config.UtxoCacheMaxSize), - hashCache: config.HashCache, - bestChain: newChainView(nil), - bestHeader: newChainView(nil), - orphans: make(map[chainhash.Hash]*orphanBlock), - prevOrphans: make(map[chainhash.Hash][]*orphanBlock), - warningCaches: newThresholdCaches(vbNumBits), - deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments), - pruneTarget: config.Prune, + checkpoints: config.Checkpoints, + checkpointsByHeight: checkpointsByHeight, + assumeUTXOCheckpoints: config.AssumeUTXOCheckpoints, + assumeUTXOCheckpointsByHeight: auCheckpointsByHeight, + db: config.DB, + chainParams: params, + timeSource: config.TimeSource, + sigCache: config.SigCache, + indexManager: config.IndexManager, + minRetargetTimespan: targetTimespan / adjustmentFactor, + maxRetargetTimespan: targetTimespan * adjustmentFactor, + blocksPerRetarget: int32(targetTimespan / targetTimePerBlock), + index: newBlockIndex(config.DB, params), + utxoCache: newUtxoCache(config.DB, config.UtxoCacheMaxSize), + hashCache: config.HashCache, + bestChain: newChainView(nil), + bestHeader: newChainView(nil), + orphans: make(map[chainhash.Hash]*orphanBlock), + prevOrphans: make(map[chainhash.Hash][]*orphanBlock), + warningCaches: newThresholdCaches(vbNumBits), + deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments), + pruneTarget: config.Prune, } // Ensure all the deployments are synchronized with our clock if @@ -2333,3 +2370,125 @@ func (b *BlockChain) CachedStateSize() uint64 { defer b.chainLock.Unlock() return b.utxoCache.totalMemoryUsage() } + +// CheckUTXOSetIntegrity verifies that the current UTXO state matches +// expectedHash. +func (b *BlockChain) CheckUTXOSetIntegrity(expectedHash *[32]byte) error { + // Flush the cache to be sure all utxos are on disk + err := b.FlushUtxoCache(FlushRequired) + if err != nil { + return err + } + + writeCompactSize := func(w io.Writer, v uint64) error { + var b [8]byte + return wire.WriteVarIntBuf(w, 0, v, b[:]) + } + + writeUint32 := func(w io.Writer, v uint32) error { + var b [4]byte + binary.LittleEndian.PutUint32(b[:], v) + _, err := w.Write(b[:]) + return err + } + + writeUint64 := func(w io.Writer, v uint64) error { + var b [8]byte + binary.LittleEndian.PutUint64(b[:], v) + _, err := w.Write(b[:]) + return err + } + + writeTxOut := func(w io.Writer, outpoint *wire.OutPoint, + utxo *UtxoEntry) error { + + // txid + _, err := w.Write(outpoint.Hash[:]) + if err != nil { + return err + } + + // vout index + err = writeUint32(w, outpoint.Index) + if err != nil { + return err + } + + // height and coinbase flag + err = writeUint32( + w, + uint32(utxo.blockHeight<<1)| + uint32(utxo.packedFlags&tfCoinBase), + ) + if err != nil { + return err + } + + // amount + err = writeUint64(w, uint64(utxo.amount)) + if err != nil { + return err + } + + // scriptpub size + err = writeCompactSize(w, uint64(len(utxo.pkScript))) + if err != nil { + return err + } + + // scriptpub + _, err = w.Write(utxo.pkScript) + if err != nil { + return err + } + return nil + } + + processUtxo := func(w io.Writer, outpoint *wire.OutPoint, + utxo *UtxoEntry) error { + + err := writeTxOut(w, outpoint, utxo) + if err != nil { + return err + } + return nil + } + + hasher := sha256.New() + + utxoIterator := func(k, v []byte) error { + outpoint := deserializeOutpoint(k) + utxo, err := deserializeUtxoEntry(v) + if err != nil { + return err + } + + return processUtxo(hasher, outpoint, utxo) + } + + err = b.db.View(func(tx database.Tx) error { + return tx. + Metadata(). + Bucket(utxoSetBucketName). + ForEach(utxoIterator) + }) + if err != nil { + return fmt.Errorf("error while computing UTXOSet hash: %w", err) + } + + var hash [32]byte + hasher.Sum(hash[:0]) + hash = sha256.Sum256(hash[:]) + + if hash != *expectedHash { + return fmt.Errorf( + "error while computing UTXOSet hash"+ + " expected: %x"+ + " current: %x", + *expectedHash, + hash, + ) + } + + return nil +} diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 5d2c033c88..5e9588bf32 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -622,6 +622,14 @@ func outpointKey(outpoint wire.OutPoint) *[]byte { return key } +func deserializeOutpoint(data []byte) *wire.OutPoint { + r := &wire.OutPoint{} + copy(r.Hash[:], data[:32]) + v, _ := deserializeVLQ(data[32:]) + r.Index = uint32(v) + return r +} + // recycleOutpointKey puts the provided byte slice, which should have been // obtained via the outpointKey function, back on the free list. func recycleOutpointKey(key *[]byte) { diff --git a/blockchain/checkpoints.go b/blockchain/checkpoints.go index 2ad1784111..f8083d95a0 100644 --- a/blockchain/checkpoints.go +++ b/blockchain/checkpoints.go @@ -36,6 +36,15 @@ func (b *BlockChain) Checkpoints() []chaincfg.Checkpoint { return b.checkpoints } +// AssumeUTXOCheckpoints returns a slice of assumeutxo checkpoints (regardless +// of whether they are already known). When there are no checkpoints for the +// chain, it will return nil. +// +// This function is safe for concurrent access. +func (b *BlockChain) AssumeUTXOCheckpoints() []chaincfg.AssumeUTXOCheckpoint { + return b.assumeUTXOCheckpoints +} + // HasCheckpoints returns whether this BlockChain has checkpoints defined. // // This function is safe for concurrent access. diff --git a/blockchain/process.go b/blockchain/process.go index 87f4deb4d2..d42dc366b3 100644 --- a/blockchain/process.go +++ b/blockchain/process.go @@ -30,6 +30,12 @@ const ( // not be performed. BFNoPoWCheck + // BFAssumeUTXO may be set to indicate that several checks can be + // avoided for the block since it is already known to fit into the chain + // due to already proving it correct links into the chain up to a known + // AssumeUTXO checkpoint. + BFAssumeUTXO + // BFNone is a convenience value to specifically indicate no flags. BFNone BehaviorFlags = 0 ) diff --git a/blockchain/utxocache.go b/blockchain/utxocache.go index a423ad43fd..8f3271a3d3 100644 --- a/blockchain/utxocache.go +++ b/blockchain/utxocache.go @@ -206,6 +206,12 @@ const ( FlushIfNeeded ) +var dummyUtxoEntry = &UtxoEntry{} + +type cacheOpts struct { + bflags BehaviorFlags +} + // utxoCache is a cached utxo view in the chainstate of a BlockChain. type utxoCache struct { db database.DB @@ -266,6 +272,12 @@ func (s *utxoCache) totalMemoryUsage() uint64 { // // The returned entries are NOT safe for concurrent access. func (s *utxoCache) fetchEntries(outpoints []wire.OutPoint) ([]*UtxoEntry, error) { + return s.fetchEntries2(outpoints, cacheOpts{}) +} + +func (s *utxoCache) fetchEntries2(outpoints []wire.OutPoint, + opts cacheOpts) ([]*UtxoEntry, error) { + entries := make([]*UtxoEntry, len(outpoints)) var ( missingOps []wire.OutPoint @@ -288,19 +300,43 @@ func (s *utxoCache) fetchEntries(outpoints []wire.OutPoint) ([]*UtxoEntry, error missingOps = append(missingOps, outpoints[i]) } - // Return early and don't attempt access the database if we don't have any - // missing outpoints. + // Return early and don't attempt access the database if we don't have + // any missing outpoints. if len(missingOps) == 0 { return entries, nil } + // To reduce I/O on cache misses when operating under assumeUTXO, we + // just add a dummy entry to the cache, assuming that we trust the + // checkpoint, we can also safely skip the prevout check. + // + // Also, when we reach an assumeUTXO checkpoint block, the utxo state + // will be hashed and must match the expected hash which is hardcoded. + if opts.bflags&BFAssumeUTXO == BFAssumeUTXO { + // Add each of the entries to the UTXO cache and update their + // memory usage. + for i := range missingOps { + s.cachedEntries.put(missingOps[i], dummyUtxoEntry, + s.totalEntryMemory) + s.totalEntryMemory += dummyUtxoEntry.memoryUsage() + } + + // Fill in the entries with the dummy entry. + for i := range missingOpsIdx { + entries[missingOpsIdx[i]] = dummyUtxoEntry + } + + return entries, nil + } + // Fetch the missing outpoints in the cache from the database. dbEntries := make([]*UtxoEntry, len(missingOps)) err := s.db.View(func(dbTx database.Tx) error { utxoBucket := dbTx.Metadata().Bucket(utxoSetBucketName) for i := range missingOps { - entry, err := dbFetchUtxoEntry(dbTx, utxoBucket, missingOps[i]) + entry, err := dbFetchUtxoEntry(dbTx, utxoBucket, + missingOps[i]) if err != nil { return err } @@ -399,9 +435,16 @@ func (s *utxoCache) addTxOuts(tx *btcutil.Tx, blockHeight int32) error { // will be marked as spent and if the utxo is fresh (meaning that the database on disk // never saw it), it will be removed from the cache. func (s *utxoCache) addTxIn(txIn *wire.TxIn, stxos *[]SpentTxOut) error { + return s.addTxIn2(txIn, stxos, cacheOpts{}) +} + +func (s *utxoCache) addTxIn2(txIn *wire.TxIn, stxos *[]SpentTxOut, + opts cacheOpts) error { + // Ensure the referenced utxo exists in the view. This should // never happen unless there is a bug is introduced in the code. - entries, err := s.fetchEntries([]wire.OutPoint{txIn.PreviousOutPoint}) + entries, err := s.fetchEntries2([]wire.OutPoint{txIn.PreviousOutPoint}, + opts) if err != nil { return err } @@ -449,14 +492,16 @@ func (s *utxoCache) addTxIn(txIn *wire.TxIn, stxos *[]SpentTxOut) error { // utxo that is being spent by the input will be marked as spent and if the utxo // is fresh (meaning that the database on disk never saw it), it will be removed // from the cache. -func (s *utxoCache) addTxIns(tx *btcutil.Tx, stxos *[]SpentTxOut) error { +func (s *utxoCache) addTxIns(tx *btcutil.Tx, stxos *[]SpentTxOut, + opts cacheOpts) error { + // Coinbase transactions don't have any inputs to spend. if IsCoinBase(tx) { return nil } for _, txIn := range tx.MsgTx().TxIn { - err := s.addTxIn(txIn, stxos) + err := s.addTxIn2(txIn, stxos, opts) if err != nil { return err } @@ -471,9 +516,10 @@ func (s *utxoCache) addTxIns(tx *btcutil.Tx, stxos *[]SpentTxOut) error { // be updated to append an entry for each spent txout. An error will be returned // if the cache and the database does not contain the required utxos. func (s *utxoCache) connectTransaction( - tx *btcutil.Tx, blockHeight int32, stxos *[]SpentTxOut) error { + tx *btcutil.Tx, blockHeight int32, stxos *[]SpentTxOut, + opts cacheOpts) error { - err := s.addTxIns(tx, stxos) + err := s.addTxIns(tx, stxos, opts) if err != nil { return err } @@ -488,8 +534,14 @@ func (s *utxoCache) connectTransaction( // the passed block. In addition, when the 'stxos' argument is not nil, it will // be updated to append an entry for each spent txout. func (s *utxoCache) connectTransactions(block *btcutil.Block, stxos *[]SpentTxOut) error { + return s.connectTransactions2(block, stxos, cacheOpts{}) +} + +func (s *utxoCache) connectTransactions2(block *btcutil.Block, stxos *[]SpentTxOut, + opts cacheOpts) error { + for _, tx := range block.Transactions() { - err := s.connectTransaction(tx, block.Height(), stxos) + err := s.connectTransaction(tx, block.Height(), stxos, opts) if err != nil { return err } diff --git a/netsync/manager.go b/netsync/manager.go index cf9c898b37..b28a5a87fe 100644 --- a/netsync/manager.go +++ b/netsync/manager.go @@ -5,6 +5,7 @@ package netsync import ( + "fmt" "math/rand" "sync" "sync/atomic" @@ -231,6 +232,34 @@ func (sm *SyncManager) findNextHeaderCheckpoint(height int32) *chaincfg.Checkpoi return nextCheckpoint } +// findNextHeaderAssumeUTXOCheckpoint returns the next assumeutxo checkpoint +// after the passed height. It returns nil when there is not one. +func (sm *SyncManager) findNextHeaderAssumeUTXOCheckpoint( + height int32) *chaincfg.AssumeUTXOCheckpoint { + + checkpoints := sm.chain.AssumeUTXOCheckpoints() + if len(checkpoints) == 0 { + return nil + } + + // There is no next checkpoint if the height is already after the final + // checkpoint. + finalCheckpoint := &checkpoints[len(checkpoints)-1] + if height >= finalCheckpoint.Height { + return nil + } + + // Find the next checkpoint. + nextCheckpoint := finalCheckpoint + for i := len(checkpoints) - 2; i >= 0; i-- { + if height >= checkpoints[i].Height { + break + } + nextCheckpoint = &checkpoints[i] + } + return nextCheckpoint +} + // fetchHigherPeers returns all the peers that are at a higher block than the // given height. The peers that are not sync candidates are omitted from the // returned list. @@ -690,6 +719,41 @@ func (sm *SyncManager) checkHeadersList(blockHash *chainhash.Hash) ( return isCheckpointBlock, behaviorFlags } +// checkAssumeUTXO checks if the sync manager is in the initial block download +// mode and returns if the given block hash is a assumeUTXO block and the +// behavior flags for this block. If the block is still under the checkpoint, +// then it's given the assumeUTXO flag. +func (sm *SyncManager) checkAssumeUTXO(utxoSetHashOut *[32]byte, + blockHash *chainhash.Hash, flags blockchain.BehaviorFlags) (bool, + blockchain.BehaviorFlags) { + + if flags&blockchain.BFFastAdd == 0 { + return false, flags + } + + isAssumeUTXOBlock := false + + height, err := sm.chain.HeaderHeightByHash(*blockHash) + if err != nil { + return false, flags + } + + // Since findNextHeaderCheckpoint returns the next checkpoint after the + // passed height, we do a -1 to include the current block. + checkpoint := sm.findNextHeaderAssumeUTXOCheckpoint(height - 1) + if checkpoint == nil { + return false, flags + } + + flags |= blockchain.BFAssumeUTXO + if *blockHash == checkpoint.BlockHash { + isAssumeUTXOBlock = true + copy(utxoSetHashOut[:], checkpoint.UTXOSetHash[:]) + } + + return isAssumeUTXOBlock, flags +} + // handleBlockMsg handles block messages from all peers. func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) { peer := bmsg.peer @@ -720,6 +784,12 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) { // next checkpoint. isCheckpointBlock, behaviorFlags := sm.checkHeadersList(blockHash) + // Check if the block is eligible to assumeUTXO, which significantly + // improves IBD speed by reducing the I/O during block validation + var utxoSetHash [32]byte + isAssumeUTXOBlock, behaviorFlags := sm.checkAssumeUTXO(&utxoSetHash, + blockHash, behaviorFlags) + // Remove block from request maps. Either chain will know about it and // so we shouldn't have any more instances of trying to fetch it, or we // will fail the insert and thus we'll retry next time we get an inv. @@ -836,6 +906,24 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) { return } + // If we're on an assumeUTXO block, perform the verification of the + // utxoset + if isAssumeUTXOBlock { + log.Infof("Reached assumeUTXO block, performing utxoset validation") + start := time.Now() + err := sm.chain.CheckUTXOSetIntegrity(&utxoSetHash) + if err != nil { + // we are panicking here because we can't proceed with + // an invalid UTXOSet + err2 := fmt.Errorf( + "error checking UTXOSet integrity: %w", err) + log.Error(err2) + panic(err2) + } + log.Infof("utxoset validation completed in %fs", + time.Since(start).Seconds()) + } + // If we're on a checkpointed block, check if we still have checkpoints // to let the user know if we're switching to normal mode. if isCheckpointBlock { diff --git a/server.go b/server.go index b94abdca66..bc207e4c28 100644 --- a/server.go +++ b/server.go @@ -2981,16 +2981,17 @@ func newServer(listenAddrs, agentBlacklist, agentWhitelist []string, // Create a new block chain instance with the appropriate configuration. var err error s.chain, err = blockchain.New(&blockchain.Config{ - DB: s.db, - Interrupt: interrupt, - ChainParams: s.chainParams, - Checkpoints: checkpoints, - TimeSource: s.timeSource, - SigCache: s.sigCache, - IndexManager: indexManager, - HashCache: s.hashCache, - Prune: cfg.Prune * 1024 * 1024, - UtxoCacheMaxSize: uint64(cfg.UtxoCacheMaxSizeMiB) * 1024 * 1024, + DB: s.db, + Interrupt: interrupt, + ChainParams: s.chainParams, + Checkpoints: checkpoints, + AssumeUTXOCheckpoints: s.chainParams.AssumeUTXOCheckpoints, + TimeSource: s.timeSource, + SigCache: s.sigCache, + IndexManager: indexManager, + HashCache: s.hashCache, + Prune: cfg.Prune * 1024 * 1024, + UtxoCacheMaxSize: uint64(cfg.UtxoCacheMaxSizeMiB) * 1024 * 1024, }) if err != nil { return nil, err