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

Check filter header checkpoints against hardcoded list #159

Merged
merged 2 commits into from Jun 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -1236,6 +1237,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)
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}