-
Notifications
You must be signed in to change notification settings - Fork 594
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
chain: fix NeutrinoClient segfault on NotifyReceived call #822
Merged
+508
−12
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
78c97f3
deps: update .gitignore
MStreet3 7e0abd0
chain: define rescanner interface
MStreet3 a98d308
chain: define NeutrinoChainService interface
MStreet3 24ae861
chain: add unit tests
MStreet3 2aab929
chain: add rescanMtx to resolve rescan segfault
MStreet3 8c31629
chain: resolve data race in sequential start stop test
MStreet3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ vendor | |
.idea | ||
coverage.txt | ||
*.swp | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package chain | ||
|
||
import ( | ||
"github.com/btcsuite/btcd/btcutil" | ||
"github.com/btcsuite/btcd/btcutil/gcs" | ||
"github.com/btcsuite/btcd/chaincfg" | ||
"github.com/btcsuite/btcd/chaincfg/chainhash" | ||
"github.com/btcsuite/btcd/wire" | ||
"github.com/lightninglabs/neutrino" | ||
"github.com/lightninglabs/neutrino/banman" | ||
"github.com/lightninglabs/neutrino/headerfs" | ||
) | ||
|
||
// NeutrinoChainService is an interface that encapsulates all the public | ||
// methods of a *neutrino.ChainService | ||
type NeutrinoChainService interface { | ||
Start() error | ||
GetBlock(chainhash.Hash, ...neutrino.QueryOption) (*btcutil.Block, error) | ||
GetBlockHeight(*chainhash.Hash) (int32, error) | ||
BestBlock() (*headerfs.BlockStamp, error) | ||
GetBlockHash(int64) (*chainhash.Hash, error) | ||
GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, error) | ||
IsCurrent() bool | ||
SendTransaction(*wire.MsgTx) error | ||
GetCFilter(chainhash.Hash, wire.FilterType, | ||
...neutrino.QueryOption) (*gcs.Filter, error) | ||
GetUtxo(...neutrino.RescanOption) (*neutrino.SpendReport, error) | ||
BanPeer(string, banman.Reason) error | ||
IsBanned(addr string) bool | ||
AddPeer(*neutrino.ServerPeer) | ||
AddBytesSent(uint64) | ||
AddBytesReceived(uint64) | ||
NetTotals() (uint64, uint64) | ||
UpdatePeerHeights(*chainhash.Hash, int32, *neutrino.ServerPeer) | ||
ChainParams() chaincfg.Params | ||
Stop() error | ||
PeerByAddr(string) *neutrino.ServerPeer | ||
} | ||
|
||
var _ NeutrinoChainService = (*neutrino.ChainService)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package chain | ||
|
||
import ( | ||
"container/list" | ||
"errors" | ||
|
||
"github.com/btcsuite/btcd/btcutil" | ||
"github.com/btcsuite/btcd/btcutil/gcs" | ||
"github.com/btcsuite/btcd/chaincfg" | ||
"github.com/btcsuite/btcd/chaincfg/chainhash" | ||
"github.com/btcsuite/btcd/wire" | ||
"github.com/lightninglabs/neutrino" | ||
"github.com/lightninglabs/neutrino/banman" | ||
"github.com/lightninglabs/neutrino/headerfs" | ||
) | ||
|
||
var ( | ||
errNotImplemented = errors.New("not implemented") | ||
testBestBlock = &headerfs.BlockStamp{ | ||
Height: 42, | ||
} | ||
) | ||
|
||
var ( | ||
_ rescanner = (*mockRescanner)(nil) | ||
_ NeutrinoChainService = (*mockChainService)(nil) | ||
) | ||
|
||
// newMockNeutrinoClient constructs a neutrino client with a mock chain | ||
// service implementation and mock rescanner interface implementation. | ||
func newMockNeutrinoClient() *NeutrinoClient { | ||
// newRescanFunc returns a mockRescanner | ||
newRescanFunc := func(ro ...neutrino.RescanOption) rescanner { | ||
return &mockRescanner{ | ||
updateArgs: list.New(), | ||
} | ||
} | ||
|
||
return &NeutrinoClient{ | ||
CS: &mockChainService{}, | ||
newRescan: newRescanFunc, | ||
MStreet3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
// mockRescanner is a mock implementation of a rescanner interface for use in | ||
// tests. Only the Update method is implemented. | ||
type mockRescanner struct { | ||
updateArgs *list.List | ||
} | ||
|
||
func (m *mockRescanner) Update(opts ...neutrino.UpdateOption) error { | ||
m.updateArgs.PushBack(opts) | ||
return nil | ||
} | ||
|
||
func (m *mockRescanner) Start() <-chan error { | ||
return nil | ||
} | ||
|
||
func (m *mockRescanner) WaitForShutdown() { | ||
// no-op | ||
} | ||
|
||
// mockChainService is a mock implementation of a chain service for use in | ||
// tests. Only the Start, GetBlockHeader and BestBlock methods are implemented. | ||
type mockChainService struct { | ||
} | ||
|
||
func (m *mockChainService) Start() error { | ||
return nil | ||
} | ||
|
||
func (m *mockChainService) BestBlock() (*headerfs.BlockStamp, error) { | ||
return testBestBlock, nil | ||
} | ||
|
||
func (m *mockChainService) GetBlockHeader( | ||
*chainhash.Hash) (*wire.BlockHeader, error) { | ||
|
||
return &wire.BlockHeader{}, nil | ||
} | ||
|
||
func (m *mockChainService) GetBlock(chainhash.Hash, | ||
...neutrino.QueryOption) (*btcutil.Block, error) { | ||
|
||
return nil, errNotImplemented | ||
} | ||
|
||
func (m *mockChainService) GetBlockHeight(*chainhash.Hash) (int32, error) { | ||
return 0, errNotImplemented | ||
} | ||
|
||
func (m *mockChainService) GetBlockHash(int64) (*chainhash.Hash, error) { | ||
return nil, errNotImplemented | ||
} | ||
|
||
func (m *mockChainService) IsCurrent() bool { | ||
return false | ||
} | ||
|
||
func (m *mockChainService) SendTransaction(*wire.MsgTx) error { | ||
return errNotImplemented | ||
} | ||
|
||
func (m *mockChainService) GetCFilter(chainhash.Hash, | ||
wire.FilterType, ...neutrino.QueryOption) (*gcs.Filter, error) { | ||
|
||
return nil, errNotImplemented | ||
} | ||
|
||
func (m *mockChainService) GetUtxo( | ||
_ ...neutrino.RescanOption) (*neutrino.SpendReport, error) { | ||
|
||
return nil, errNotImplemented | ||
} | ||
|
||
func (m *mockChainService) BanPeer(string, banman.Reason) error { | ||
return errNotImplemented | ||
} | ||
|
||
func (m *mockChainService) IsBanned(addr string) bool { | ||
panic(errNotImplemented) | ||
} | ||
|
||
func (m *mockChainService) AddPeer(*neutrino.ServerPeer) { | ||
panic(errNotImplemented) | ||
} | ||
|
||
func (m *mockChainService) AddBytesSent(uint64) { | ||
panic(errNotImplemented) | ||
} | ||
|
||
func (m *mockChainService) AddBytesReceived(uint64) { | ||
panic(errNotImplemented) | ||
} | ||
|
||
func (m *mockChainService) NetTotals() (uint64, uint64) { | ||
panic(errNotImplemented) | ||
} | ||
|
||
func (m *mockChainService) UpdatePeerHeights(*chainhash.Hash, | ||
int32, *neutrino.ServerPeer, | ||
) { | ||
panic(errNotImplemented) | ||
} | ||
|
||
func (m *mockChainService) ChainParams() chaincfg.Params { | ||
panic(errNotImplemented) | ||
} | ||
|
||
func (m *mockChainService) Stop() error { | ||
panic(errNotImplemented) | ||
} | ||
|
||
func (m *mockChainService) PeerByAddr(string) *neutrino.ServerPeer { | ||
panic(errNotImplemented) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Up to you and the maintainers, but I'd pare this down a lot. Half of the methods are not actually needed by
NeutrinoClient
, just bloating the mock client. Note the updated comment and method set:No need for all those
panic
methods in themockChainService
.Of course if in the future
NeutrinoClient
wanted to use one of the other methods, this interface would need to be ammended, which would be a breaking change... maybe best to have all the unused methods specified as you have.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, agreed.
The first time I implemented this it was much slimmer, however, I saw that this created a failure in LND. This is because LND has some code that is directly accessing the
CS
field of aNeutrinoClient
. The consumer in LND expected theCS
field to be the single*neutrino.ChainService
concrete implementation, but in reality it was the slim interface. So to be able to allow LND to update the version ofbtcwallet
with this change I had to expose EVERY public method of*neutrino.ChainService
event though aNeutrinoClient
only uses a few of the methods.When I saw that I realized that since
CS
was public, I couldn't know how many other packages would break based on the assumption that theCS
field of aNeutrinoClient
is a pointer to a concrete implementation versus a slim interface.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhhh, ok. That's interesting.
I'd just note that lnd can assert that CS satisfies an even broader interface itself (the assertion applies to the underlying type, which can have more methods). It seems like the lnd-btcwallet relationship is quite tight. This partly explains why
CS
is even exported.