0029905d43
Introduce an ECDSA signature verification into btcd in order to mitigate a certain DoS attack and as a performance optimization. The benefits of SigCache are two fold. Firstly, usage of SigCache mitigates a DoS attack wherein an attacker causes a victim's client to hang due to worst-case behavior triggered while processing attacker crafted invalid transactions. A detailed description of the mitigated DoS attack can be found here: https://bitslog.wordpress.com/2013/01/23/fixed-bitcoin-vulnerability-explanation-why-the-signature-cache-is-a-dos-protection/ Secondly, usage of the SigCache introduces a signature verification optimization which speeds up the validation of transactions within a block, if they've already been seen and verified within the mempool. The server itself manages the sigCache instance. The blockManager and txMempool respectively now receive pointers to the created sigCache instance. All read (sig triplet existence) operations on the sigCache will not block unless a separate goroutine is adding an entry (writing) to the sigCache. GetBlockTemplate generation now also utilizes the sigCache in order to avoid unnecessarily double checking signatures when generating a template after previously accepting a txn to the mempool. Consequently, the CPU miner now also employs the same optimization. The maximum number of entries for the sigCache has been introduced as a config parameter in order to allow users to configure the amount of memory consumed by this new additional caching.
172 lines
4.8 KiB
Go
172 lines
4.8 KiB
Go
// Copyright (c) 2013 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/database"
|
|
_ "github.com/btcsuite/btcd/database/ldb"
|
|
"github.com/btcsuite/btcd/wire"
|
|
)
|
|
|
|
const blockDbNamePrefix = "blocks"
|
|
|
|
var (
|
|
cfg *config
|
|
)
|
|
|
|
// loadBlockDB opens the block database and returns a handle to it.
|
|
func loadBlockDB() (database.Db, error) {
|
|
// The database name is based on the database type.
|
|
dbType := cfg.DbType
|
|
dbName := blockDbNamePrefix + "_" + dbType
|
|
if dbType == "sqlite" {
|
|
dbName = dbName + ".db"
|
|
}
|
|
dbPath := filepath.Join(cfg.DataDir, dbName)
|
|
fmt.Printf("Loading block database from '%s'\n", dbPath)
|
|
db, err := database.OpenDB(dbType, dbPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return db, nil
|
|
}
|
|
|
|
// findCandidates searches the chain backwards for checkpoint candidates and
|
|
// returns a slice of found candidates, if any. It also stops searching for
|
|
// candidates at the last checkpoint that is already hard coded into btcchain
|
|
// since there is no point in finding candidates before already existing
|
|
// checkpoints.
|
|
func findCandidates(db database.Db, latestHash *wire.ShaHash) ([]*chaincfg.Checkpoint, error) {
|
|
// Start with the latest block of the main chain.
|
|
block, err := db.FetchBlockBySha(latestHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Setup chain and get the latest checkpoint. Ignore notifications
|
|
// since they aren't needed for this util.
|
|
chain := blockchain.New(db, activeNetParams, nil, nil)
|
|
latestCheckpoint := chain.LatestCheckpoint()
|
|
if latestCheckpoint == nil {
|
|
return nil, fmt.Errorf("unable to retrieve latest checkpoint")
|
|
}
|
|
|
|
// The latest known block must be at least the last known checkpoint
|
|
// plus required checkpoint confirmations.
|
|
checkpointConfirmations := int32(blockchain.CheckpointConfirmations)
|
|
requiredHeight := latestCheckpoint.Height + checkpointConfirmations
|
|
if block.Height() < requiredHeight {
|
|
return nil, fmt.Errorf("the block database is only at height "+
|
|
"%d which is less than the latest checkpoint height "+
|
|
"of %d plus required confirmations of %d",
|
|
block.Height(), latestCheckpoint.Height,
|
|
checkpointConfirmations)
|
|
}
|
|
|
|
// Indeterminate progress setup.
|
|
numBlocksToTest := block.Height() - requiredHeight
|
|
progressInterval := (numBlocksToTest / 100) + 1 // min 1
|
|
fmt.Print("Searching for candidates")
|
|
defer fmt.Println()
|
|
|
|
// Loop backwards through the chain to find checkpoint candidates.
|
|
candidates := make([]*chaincfg.Checkpoint, 0, cfg.NumCandidates)
|
|
numTested := int32(0)
|
|
for len(candidates) < cfg.NumCandidates && block.Height() > requiredHeight {
|
|
// Display progress.
|
|
if numTested%progressInterval == 0 {
|
|
fmt.Print(".")
|
|
}
|
|
|
|
// Determine if this block is a checkpoint candidate.
|
|
isCandidate, err := chain.IsCheckpointCandidate(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// All checks passed, so this node seems like a reasonable
|
|
// checkpoint candidate.
|
|
if isCandidate {
|
|
checkpoint := chaincfg.Checkpoint{
|
|
Height: block.Height(),
|
|
Hash: block.Sha(),
|
|
}
|
|
candidates = append(candidates, &checkpoint)
|
|
}
|
|
|
|
prevHash := &block.MsgBlock().Header.PrevBlock
|
|
block, err = db.FetchBlockBySha(prevHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
numTested++
|
|
}
|
|
return candidates, nil
|
|
}
|
|
|
|
// showCandidate display a checkpoint candidate using and output format
|
|
// determined by the configuration parameters. The Go syntax output
|
|
// uses the format the btcchain code expects for checkpoints added to the list.
|
|
func showCandidate(candidateNum int, checkpoint *chaincfg.Checkpoint) {
|
|
if cfg.UseGoOutput {
|
|
fmt.Printf("Candidate %d -- {%d, newShaHashFromStr(\"%v\")},\n",
|
|
candidateNum, checkpoint.Height, checkpoint.Hash)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("Candidate %d -- Height: %d, Hash: %v\n", candidateNum,
|
|
checkpoint.Height, checkpoint.Hash)
|
|
|
|
}
|
|
|
|
func main() {
|
|
// Load configuration and parse command line.
|
|
tcfg, _, err := loadConfig()
|
|
if err != nil {
|
|
return
|
|
}
|
|
cfg = tcfg
|
|
|
|
// Load the block database.
|
|
db, err := loadBlockDB()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to load database: %v\n", err)
|
|
return
|
|
}
|
|
defer db.Close()
|
|
|
|
// Get the latest block hash and height from the database and report
|
|
// status.
|
|
latestHash, height, err := db.NewestSha()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return
|
|
}
|
|
fmt.Printf("Block database loaded with block height %d\n", height)
|
|
|
|
// Find checkpoint candidates.
|
|
candidates, err := findCandidates(db, latestHash)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Unable to identify candidates: %v", err)
|
|
return
|
|
}
|
|
|
|
// No candidates.
|
|
if len(candidates) == 0 {
|
|
fmt.Println("No candidates found.")
|
|
return
|
|
}
|
|
|
|
// Show the candidates.
|
|
for i, checkpoint := range candidates {
|
|
showCandidate(i+1, checkpoint)
|
|
}
|
|
}
|