Skip to content

Commit

Permalink
multi: use btcd libs to validate headers
Browse files Browse the repository at this point in the history
This uses btcd's HeaderCtx and ChainCtx interfaces to be able to
validate headers, both contextually and context-free. This allows
neutrino to call blockchain.CheckBlockHeaderContext and
blockchain.CheckBlockHeaderSanity.
  • Loading branch information
Crypt-iQ authored and guggero committed Aug 4, 2023
1 parent 6dfa3a2 commit c3150fd
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 39 deletions.
269 changes: 239 additions & 30 deletions blockmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2374,11 +2374,6 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {
return
}

// For checking to make sure blocks aren't too far in the future as of
// the time we receive the headers message.
maxTimestamp := b.cfg.TimeSource.AdjustedTime().
Add(maxTimeOffset)

// We'll attempt to write the entire batch of validated headers
// atomically in order to improve performance.
headerWriteBatch := make([]headerfs.BlockHeader, 0, len(msg.Headers))
Expand Down Expand Up @@ -2420,8 +2415,12 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {
prevNode := prevNodeEl
prevHash := prevNode.Header.BlockHash()
if prevHash.IsEqual(&blockHeader.PrevBlock) {
err := b.checkHeaderSanity(blockHeader, maxTimestamp,
false)
prevNodeHeight := prevNode.Height
prevNodeHeader := prevNode.Header
err := b.checkHeaderSanity(
blockHeader, false, prevNodeHeight,
&prevNodeHeader,
)
if err != nil {
log.Warnf("Header doesn't pass sanity check: "+
"%s -- disconnecting peer", err)
Expand All @@ -2434,10 +2433,12 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {

// This header checks out, so we'll add it to our write
// batch.
headerWriteBatch = append(headerWriteBatch, headerfs.BlockHeader{
BlockHeader: blockHeader,
Height: uint32(node.Height),
})
headerWriteBatch = append(
headerWriteBatch, headerfs.BlockHeader{
BlockHeader: blockHeader,
Height: uint32(node.Height),
},
)

hmsg.peer.UpdateLastBlockHeight(node.Height)

Expand Down Expand Up @@ -2529,8 +2530,27 @@ 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 {
// 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, true,
int32(prevNodeHeight), prevNodeHeader,
)
if err != nil {
log.Warnf("Header doesn't pass sanity"+
" check: %s -- disconnecting "+
Expand Down Expand Up @@ -2743,29 +2763,42 @@ func areHeadersConnected(headers []*wire.BlockHeader) bool {
return true
}

// 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 {
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, chainCtx, true,
)
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

return blockchain.CheckBlockHeaderSanity(
blockHeader, b.cfg.ChainParams.PowLimit, b.cfg.TimeSource,
emptyFlags,
)
}

// calcNextRequiredDifficulty calculates the required difficulty for the block
Expand Down Expand Up @@ -2990,3 +3023,179 @@ 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(int32, *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 {
// The parent is just an ancestor with distance 1.
return l.RelativeAncestorCtx(1)
}

// 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,
)
}
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/lightninglabs/neutrino

require (
github.com/btcsuite/btcd v0.23.3
github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231
github.com/btcsuite/btcd/btcec/v2 v2.1.3
github.com/btcsuite/btcd/btcutil v1.1.1
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
Expand Down Expand Up @@ -29,8 +29,8 @@ require (
github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13P
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g=
github.com/btcsuite/btcd v0.22.0-beta.0.20220316175102-8d5c75c28923/go.mod h1:taIcYprAW2g6Z9S0gGUxyR+zDwimyDMK5ePOX+iJ2ds=
github.com/btcsuite/btcd v0.23.3 h1:4KH/JKy9WiCd+iUS9Mu0Zp7Dnj17TGdKrg9xc/FGj24=
github.com/btcsuite/btcd v0.23.3/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231 h1:FZR6mILlSI/GDx8ydNVBZAlXlRXsoRBWX2Un64mpfsI=
github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
Expand Down Expand Up @@ -110,8 +110,9 @@ go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBC
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand All @@ -129,8 +130,9 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down
16 changes: 14 additions & 2 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -885,8 +885,20 @@ func (s *ChainService) GetBlock(blockHash chainhash.Hash,
return noProgress
}

// TODO(roasbeef): modify CheckBlockSanity to also check witness
// commitment
if err := blockchain.ValidateWitnessCommitment(
block,
); err != nil {
log.Warnf("Invalid block for %s received from %s: %v "+
"-- disconnecting peer", blockHash, peer, err)

err = s.BanPeer(peer, banman.InvalidBlock)
if err != nil {
log.Errorf("Unable to ban peer %v: %v", peer,
err)
}

return noProgress
}

// At this point, the block matches what we know about it, and
// we declare it sane. We can kill the query and pass the
Expand Down

0 comments on commit c3150fd

Please sign in to comment.