Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

peer, main, netsync, blockchain: parallel block downloads #2226

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
blockchain: store block concurrently while verifying it
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 9, 2024
commit 9a4cac4cdd583cce746a887c468e700257ba8563
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
}