Skip to content

Commit

Permalink
Merge pull request #159 from halseth/filter-header-checkpoints
Browse files Browse the repository at this point in the history
Check filter header checkpoints against hardcoded list
  • Loading branch information
Roasbeef committed Jun 22, 2019
2 parents 7e5b808 + 40060ce commit 65d7608
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 0 deletions.
32 changes: 32 additions & 0 deletions blockmanager.go
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/btcsuite/btcutil/gcs"
"github.com/btcsuite/btcutil/gcs/builder"
"github.com/lightninglabs/neutrino/blockntfns"
"github.com/lightninglabs/neutrino/chainsync"
"github.com/lightninglabs/neutrino/headerfs"
"github.com/lightninglabs/neutrino/headerlist"
)
Expand Down Expand Up @@ -1217,6 +1218,37 @@ func (b *blockManager) resolveConflict(
store *headerfs.FilterHeaderStore, fType wire.FilterType) (
[]*chainhash.Hash, error) {

// First check the served checkpoints against the hardcoded ones.
for peer, cp := range checkpoints {
for i, header := range cp {
height := uint32((i + 1) * wire.CFCheckptInterval)
err := chainsync.ControlCFHeader(
b.server.chainParams, fType, height, header,
)
if err == chainsync.ErrCheckpointMismatch {
log.Warnf("Banning peer=%v since served "+
"checkpoints didn't match our "+
"checkpoint at height %d", peer, height)

sp := b.server.PeerByAddr(peer)
if sp != nil {
b.server.BanPeer(sp)
sp.Disconnect()
}
delete(checkpoints, peer)
break
} else if err != nil {
return nil, err
}
}
}

if len(checkpoints) == 0 {
return nil, fmt.Errorf("no peer is serving good cfheader " +
"checkpoints")
}

// Check if the remaining checkpoints are sane.
heightDiff, err := checkCFCheckptSanity(checkpoints, store)
if err != nil {
return nil, err
Expand Down
56 changes: 56 additions & 0 deletions chainsync/filtercontrol.go
@@ -0,0 +1,56 @@
package chainsync

import (
"fmt"

"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)

// ErrCheckpointMismatch is returned if given filter headers don't pass our
// control check.
var ErrCheckpointMismatch = fmt.Errorf("checkpoint doesn't match")

// filterHeaderCheckpoints holds a mapping from heights to filter headers for
// various heights. We use them to check whether peers are serving us the
// expected filter headers.
var filterHeaderCheckpoints = map[wire.BitcoinNet]map[uint32]*chainhash.Hash{
// Mainnet filter header checkpoints.
chaincfg.MainNetParams.Net: map[uint32]*chainhash.Hash{},

// Testnet filter header checkpoints.
chaincfg.TestNet3Params.Net: map[uint32]*chainhash.Hash{},
}

// ControlCFHeader controls the given filter header against our list of
// checkpoints. It returns ErrCheckpointMismatch if we have a checkpoint at the
// given height, and it doesn't match.
func ControlCFHeader(params chaincfg.Params, fType wire.FilterType,
height uint32, filterHeader *chainhash.Hash) error {

if fType != wire.GCSFilterRegular {
return fmt.Errorf("unsupported filter type %v", fType)
}

control, ok := filterHeaderCheckpoints[params.Net]
if !ok {
return nil
}

hash, ok := control[height]
if !ok {
return nil
}

if *filterHeader != *hash {
return ErrCheckpointMismatch
}

return nil
}

func hashFromStr(hexStr string) *chainhash.Hash {
hash, _ := chainhash.NewHashFromStr(hexStr)
return hash
}
52 changes: 52 additions & 0 deletions chainsync/filtercontrol_test.go
@@ -0,0 +1,52 @@
package chainsync

import (
"testing"

"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)

func TestControlCFHeader(t *testing.T) {
t.Parallel()

// We'll modify our backing list of checkpoints for this test.
height := uint32(999)
header := hashFromStr(
"4a242283a406a7c089f671bb8df7671e5d5e9ba577cea1047d30a7f4919df193",
)
filterHeaderCheckpoints = map[wire.BitcoinNet]map[uint32]*chainhash.Hash{
chaincfg.MainNetParams.Net: map[uint32]*chainhash.Hash{
height: header,
},
}

// Expect the control at height to succeed.
err := ControlCFHeader(
chaincfg.MainNetParams, wire.GCSFilterRegular, height, header,
)
if err != nil {
t.Fatalf("error checking height: %v", err)
}

// Pass an invalid header, this should return an error.
header = hashFromStr(
"000000000006a7c089f671bb8df7671e5d5e9ba577cea1047d30a7f4919df193",
)
err = ControlCFHeader(
chaincfg.MainNetParams, wire.GCSFilterRegular, height, header,
)
if err != ErrCheckpointMismatch {
t.Fatalf("expected ErrCheckpointMismatch, got %v", err)
}

// Finally, control an unknown height. This should also pass since we
// don't have the checkpoint stored.
err = ControlCFHeader(
chaincfg.MainNetParams, wire.GCSFilterRegular, 99, header,
)
if err != nil {
t.Fatalf("error checking height: %v", err)
}
}

0 comments on commit 65d7608

Please sign in to comment.