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

blockmanager.go: use btcd libs to validate headers #263

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
320 changes: 300 additions & 20 deletions blockmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,33 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {
// atomically in order to improve peformance.
headerWriteBatch := make([]headerfs.BlockHeader, 0, len(msg.Headers))

// Explicitly check that each header in msg.Headers builds off of the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be moved out into a some helper function. Also would have though the functions we exported from btcd would handle it so we don't need to do it again?

// previous one.
var (
lastHeader chainhash.Hash
emptyHash chainhash.Hash
)

for _, blockHeader := range msg.Headers {
blockHash := blockHeader.BlockHash()

if lastHeader == emptyHash {
// If we haven't yet set lastHeader, set it now.
lastHeader = blockHash
} else {
// Ensure that blockHeader.PrevBlock matches
// lastHeader.
if blockHeader.PrevBlock != lastHeader {
log.Warnf("Headers received from peer don't " +
"connect")
hmsg.peer.Disconnect()
return
}

lastHeader = blockHash
}
}

// Process all of the received headers ensuring each one connects to
// the previous and that checkpoints match.
receivedCheckpoint := false
Expand Down Expand Up @@ -2411,8 +2438,10 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {
prevNode := prevNodeEl
prevHash := prevNode.Header.BlockHash()
if prevHash.IsEqual(&blockHeader.PrevBlock) {
prevNodeHeight := prevNode.Height
prevNodeHeader := prevNode.Header
err := b.checkHeaderSanity(blockHeader, maxTimestamp,
false)
false, prevNodeHeight, &prevNodeHeader)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can fix line folding here.

if err != nil {
log.Warnf("Header doesn't pass sanity check: "+
"%s -- disconnecting peer", err)
Expand Down Expand Up @@ -2520,8 +2549,28 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {
})
totalWork := big.NewInt(0)
for j, reorgHeader := range msg.Headers[i:] {
err = b.checkHeaderSanity(reorgHeader,
maxTimestamp, true)
// We have to get the parent's height and
// header to be able to contextually validate
// this header.
prevNodeHeight := backHeight + uint32(j)

var prevNodeHeader *wire.BlockHeader

if i+j == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So i && j == 0?

// Use backHead if we are using the
// first header in the Headers slice.
prevNodeHeader = backHead
} else {
// We can find the parent in the
// Headers slice by getting the header
// at index i+j-1.
prevNodeHeader = msg.Headers[i+j-1]
}

err = b.checkHeaderSanity(
reorgHeader, maxTimestamp, true,
int32(prevNodeHeight), prevNodeHeader,
)
if err != nil {
log.Warnf("Header doesn't pass sanity"+
" check: %s -- disconnecting "+
Expand Down Expand Up @@ -2706,29 +2755,44 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {
b.newHeadersSignal.Broadcast()
}

// checkHeaderSanity checks the PoW, and timestamp of a block header.
// checkHeaderSanity performs contextual and context-less checks on the passed
// wire.BlockHeader. This function calls blockchain.CheckBlockHeaderContext for
// the contextual check and blockchain.CheckBlockHeaderSanity for context-less
// checks.
func (b *blockManager) checkHeaderSanity(blockHeader *wire.BlockHeader,
maxTimestamp time.Time, reorgAttempt bool) error {
maxTimestamp time.Time, reorgAttempt bool, prevNodeHeight int32,
prevNodeHeader *wire.BlockHeader) error {

diff, err := b.calcNextRequiredDifficulty(
blockHeader.Timestamp, reorgAttempt)
if err != nil {
return err
// Create the lightHeaderCtx for the blockHeader's parent.
hList := b.headerList
if reorgAttempt {
hList = b.reorgList
}
stubBlock := btcutil.NewBlock(&wire.MsgBlock{
Header: *blockHeader,
})
err = blockchain.CheckProofOfWork(stubBlock,
blockchain.CompactToBig(diff))

parentHeaderCtx := newLightHeaderCtx(
prevNodeHeight, prevNodeHeader, b.cfg.BlockHeaders, hList,
)

// Create a lightChainCtx as well.
chainCtx := newLightChainCtx(
&b.cfg.ChainParams, b.blocksPerRetarget, b.minRetargetTimespan,
b.maxRetargetTimespan,
)

var emptyFlags blockchain.BehaviorFlags
err := blockchain.CheckBlockHeaderContext(
blockHeader, parentHeaderCtx, emptyFlags, true, chainCtx,
)
if err != nil {
return err
}
// Ensure the block time is not too far in the future.
if blockHeader.Timestamp.After(maxTimestamp) {
return fmt.Errorf("block timestamp of %v is too far in the "+
"future", blockHeader.Timestamp)
}
return nil

err = blockchain.CheckBlockHeaderSanity(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just do return here.

blockHeader, b.cfg.ChainParams.PowLimit, b.cfg.TimeSource,
emptyFlags,
)

return err
}

// calcNextRequiredDifficulty calculates the required difficulty for the block
Expand Down Expand Up @@ -2953,3 +3017,219 @@ func (b *blockManager) NotificationsSinceHeight(

return blocks, bestHeight, nil
}

// lightChainCtx is an implementation of the blockchain.ChainCtx interface and
// gives a neutrino node the ability to contextually validate headers it
// receives.
type lightChainCtx struct {
params *chaincfg.Params
blocksPerRetarget int32
minRetargetTimespan int64
maxRetargetTimespan int64
}

// newLightChainCtx returns a new lightChainCtx instance from the passed
// arguments.
func newLightChainCtx(params *chaincfg.Params, blocksPerRetarget int32,
minRetargetTimespan, maxRetargetTimespan int64) *lightChainCtx {

return &lightChainCtx{
params: params,
blocksPerRetarget: blocksPerRetarget,
minRetargetTimespan: minRetargetTimespan,
maxRetargetTimespan: maxRetargetTimespan,
}
}

// ChainParams returns the configured chain parameters.
//
// NOTE: Part of the blockchain.ChainCtx interface.
func (l *lightChainCtx) ChainParams() *chaincfg.Params {
return l.params
}

// BlocksPerRetarget returns the number of blocks before retargeting occurs.
//
// NOTE: Part of the blockchain.ChainCtx interface.
func (l *lightChainCtx) BlocksPerRetarget() int32 {
return l.blocksPerRetarget
}

// MinRetargetTimespan returns the minimum amount of time used in the
// difficulty calculation.
//
// NOTE: Part of the blockchain.ChainCtx interface.
func (l *lightChainCtx) MinRetargetTimespan() int64 {
return l.minRetargetTimespan
}

// MaxRetargetTimespan returns the maximum amount of time used in the
// difficulty calculation.
//
// NOTE: Part of the blockchain.ChainCtx interface.
func (l *lightChainCtx) MaxRetargetTimespan() int64 {
return l.maxRetargetTimespan
}

// VerifyCheckpoint returns false as the lightChainCtx does not need to
// validate checkpoints. This is already done inside the handleHeadersMsg
// function.
//
// NOTE: Part of the blockchain.ChainCtx interface.
func (l *lightChainCtx) VerifyCheckpoint(height int32,
hash *chainhash.Hash) bool {

return false
}

// FindPreviousCheckpoint returns nil values since the lightChainCtx does not
// need to validate against checkpoints. This is already done inside the
// handleHeadersMsg function.
//
// NOTE: Part of the blockchain.ChainCtx interface.
func (l *lightChainCtx) FindPreviousCheckpoint() (blockchain.HeaderCtx,
error) {

return nil, nil
}

// lightHeaderCtx is an implementation of the blockchain.HeaderCtx interface.
// It is used so neutrino can perform contextual header validation checks.
type lightHeaderCtx struct {
height int32
bits uint32
timestamp int64

store headerfs.BlockHeaderStore
headerList headerlist.Chain
}

// newLightHeaderCtx returns an instance of a lightHeaderCtx to be used when
// contextually validating headers.
func newLightHeaderCtx(height int32, header *wire.BlockHeader,
store headerfs.BlockHeaderStore,
headerList headerlist.Chain) *lightHeaderCtx {

return &lightHeaderCtx{
height: height,
bits: header.Bits,
timestamp: header.Timestamp.Unix(),
store: store,
headerList: headerList,
}
}

// Height returns the height for the underlying header this context was created
// from.
//
// NOTE: Part of the blockchain.HeaderCtx interface.
func (l *lightHeaderCtx) Height() int32 {
return l.height
}

// Bits returns the difficulty bits for the underlying header this context was
// created from.
//
// NOTE: Part of the blockchain.HeaderCtx interface.
func (l *lightHeaderCtx) Bits() uint32 {
return l.bits
}

// Timestamp returns the timestamp for the underlying header this context was
// created from.
//
// NOTE: Part of the blockchain.HeaderCtx interface.
func (l *lightHeaderCtx) Timestamp() int64 {
return l.timestamp
}

// Parent returns the parent of the underlying header this context was created
// from.
//
// NOTE: Part of the blockchain.HeaderCtx interface.
func (l *lightHeaderCtx) Parent() blockchain.HeaderCtx {
parentHeight := l.height - 1

var (
parent *wire.BlockHeader
err error
)

// We'll first consult the headerList to see if the parent can be found
// there. If that fails, we'll look up the header in the header store.
iterNode := l.headerList.Back()

// Keep looping until iterNode is nil or we encounter the desired
// height.
for iterNode != nil {
if iterNode.Height == parentHeight {
// We've found the parent header.
parent = &iterNode.Header
break
}

// We haven't hit the parent header yet, so we'll go
// back one.
iterNode = iterNode.Prev()
}

if parent == nil {
// Lookup the parent in the header store.
parent, err = l.store.FetchHeaderByHeight(uint32(parentHeight))
if err != nil {
return nil
}
}

return newLightHeaderCtx(
parentHeight, parent, l.store, l.headerList,
)
}

// RelativeAncestorCtx returns the ancestor that is distance blocks before the
// underlying header in the chain.
//
// NOTE: Part of the blockchain.HeaderCtx interface.
func (l *lightHeaderCtx) RelativeAncestorCtx(
distance int32) blockchain.HeaderCtx {

ancestorHeight := l.height - distance

var (
ancestor *wire.BlockHeader
err error
)

// We'll first consult the headerList to see if the ancestor can be
// found there. If that fails, we'll look up the header in the header
// store.
iterNode := l.headerList.Back()

// Keep looping until iterNode is nil or the ancestor height is
// encountered.
for iterNode != nil {
if iterNode.Height == ancestorHeight {
// We've found the ancestor.
ancestor = &iterNode.Header
break
}

// We haven't hit the ancestor header yet, so we'll go
// back one.
iterNode = iterNode.Prev()
}

if ancestor == nil {
// Lookup the ancestor in the header store.
ancestor, err = l.store.FetchHeaderByHeight(
uint32(ancestorHeight),
)
if err != nil {
return nil
}
}

return newLightHeaderCtx(
ancestorHeight, ancestor, l.store, l.headerList,
)
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ require (
)

go 1.13

replace github.com/btcsuite/btcd => /Users/nsa/go/src/github.com/btcsuite/btcd