Skip to content

Commit

Permalink
blockchain: store block concurrently while verifying it
Browse files Browse the repository at this point in the history
Storing block happens before the block validation is done and this can
be a bottleneck on computers with slow disks.  Allowing for concurrent
block storage saves time as the disk operation can be done in parallel
with the cpu operations of verifying the block.
  • Loading branch information
kcalvinalvin committed Dec 26, 2024
1 parent d80f02b commit 008eff3
Showing 1 changed file with 43 additions and 14 deletions.
57 changes: 43 additions & 14 deletions blockchain/accept.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package blockchain

import (
"fmt"
"sync"

"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/database"
Expand Down Expand Up @@ -44,20 +45,36 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
return false, err
}

// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it
// has already passed all proof-of-work and validity tests which means
// it would be prohibitively expensive for an attacker to fill up the
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the main chain or
// blocks that fail to connect available for further analysis.
err = b.db.Update(func(dbTx database.Tx) error {
return dbStoreBlock(dbTx, block)
})
if err != nil {
return false, err
// Store the block in parallel if we're in headers first mode. The
// headers were already checked and this block is under the checkpoint
// so it's safe to just add it to the database while the block
// validation is happening.
var wg sync.WaitGroup
var dbStoreError error
if flags&BFFastAdd == BFFastAdd {
go func() {
wg.Add(1)
defer wg.Done()
// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it
// has already passed all proof-of-work and validity tests which means
// it would be prohibitively expensive for an attacker to fill up the
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the main chain or
// blocks that fail to connect available for further analysis.
dbStoreError = b.db.Update(func(dbTx database.Tx) error {
return dbTx.StoreBlock(block)
})
}()
} else {
err = b.db.Update(func(dbTx database.Tx) error {
return dbStoreBlock(dbTx, block)
})
if err != nil {
return false, err
}
}

// Create a new block node for the block and add it to the node index. Even
Expand Down Expand Up @@ -90,5 +107,17 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
b.sendNotification(NTBlockAccepted, block)
}()

// Wait until the block is saved. If there was a db error, then unset
// the data stored flag and flush the block index.
wg.Wait()
if dbStoreError != nil {
b.index.UnsetStatusFlags(newNode, statusDataStored)
err = b.index.flushToDB()
if err != nil {
return false, fmt.Errorf("%v. %v", err, dbStoreError)
}
return false, dbStoreError
}

return isMainChain, nil
}

0 comments on commit 008eff3

Please sign in to comment.