routing: make channel router aware of stale blocks
This commit make the channel router handle the case where it wakes up on a stale branch, and also the case where a reorg happens while it is active.
This commit is contained in:
parent
8eb994c14b
commit
256db86b02
@ -147,7 +147,9 @@ func (m *mockChain) GetBestBlock() (*chainhash.Hash, int32, error) {
|
|||||||
m.RLock()
|
m.RLock()
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
|
|
||||||
return nil, m.bestHeight, nil
|
blockHash := m.blockIndex[uint32(m.bestHeight)]
|
||||||
|
|
||||||
|
return &blockHash, m.bestHeight, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockChain) GetTransaction(txid *chainhash.Hash) (*wire.MsgTx, error) {
|
func (m *mockChain) GetTransaction(txid *chainhash.Hash) (*wire.MsgTx, error) {
|
||||||
@ -162,7 +164,6 @@ func (m *mockChain) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("can't find block hash, for "+
|
return nil, fmt.Errorf("can't find block hash, for "+
|
||||||
"height %v", blockHeight)
|
"height %v", blockHeight)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &hash, nil
|
return &hash, nil
|
||||||
@ -185,9 +186,9 @@ func (m *mockChain) GetUtxo(op *wire.OutPoint, _ uint32) (*wire.TxOut, error) {
|
|||||||
return &utxo, nil
|
return &utxo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockChain) addBlock(block *wire.MsgBlock, height uint32) {
|
func (m *mockChain) addBlock(block *wire.MsgBlock, height uint32, nonce uint32) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
block.Header.Nonce = height
|
block.Header.Nonce = nonce
|
||||||
hash := block.Header.BlockHash()
|
hash := block.Header.BlockHash()
|
||||||
m.blocks[hash] = block
|
m.blocks[hash] = block
|
||||||
m.blockIndex[height] = hash
|
m.blockIndex[height] = hash
|
||||||
@ -250,6 +251,19 @@ func (m *mockChainView) notifyBlock(hash chainhash.Hash, height uint32,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockChainView) notifyStaleBlock(hash chainhash.Hash, height uint32,
|
||||||
|
txns []*wire.MsgTx) {
|
||||||
|
|
||||||
|
m.RLock()
|
||||||
|
defer m.RUnlock()
|
||||||
|
|
||||||
|
m.staleBlocks <- &chainview.FilteredBlock{
|
||||||
|
Hash: hash,
|
||||||
|
Height: height,
|
||||||
|
Transactions: txns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockChainView) FilteredBlocks() <-chan *chainview.FilteredBlock {
|
func (m *mockChainView) FilteredBlocks() <-chan *chainview.FilteredBlock {
|
||||||
return m.newBlocks
|
return m.newBlocks
|
||||||
}
|
}
|
||||||
@ -259,7 +273,7 @@ func (m *mockChainView) DisconnectedBlocks() <-chan *chainview.FilteredBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockChainView) FilterBlock(blockHash *chainhash.Hash) (*chainview.FilteredBlock, error) {
|
func (m *mockChainView) FilterBlock(blockHash *chainhash.Hash) (*chainview.FilteredBlock, error) {
|
||||||
return nil, nil
|
return &chainview.FilteredBlock{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockChainView) Start() error {
|
func (m *mockChainView) Start() error {
|
||||||
@ -295,7 +309,7 @@ func TestEdgeUpdateNotification(t *testing.T) {
|
|||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight)
|
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight)
|
||||||
|
|
||||||
// Next we'll create two test nodes that the fake channel will be open
|
// Next we'll create two test nodes that the fake channel will be open
|
||||||
// between.
|
// between.
|
||||||
@ -477,7 +491,7 @@ func TestNodeUpdateNotification(t *testing.T) {
|
|||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight)
|
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight)
|
||||||
|
|
||||||
// Create two nodes acting as endpoints in the created channel, and use
|
// Create two nodes acting as endpoints in the created channel, and use
|
||||||
// them to trigger notifications by sending updated node announcement
|
// them to trigger notifications by sending updated node announcement
|
||||||
@ -658,7 +672,7 @@ func TestNotificationCancellation(t *testing.T) {
|
|||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight)
|
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight)
|
||||||
|
|
||||||
// We'll create a fresh new node topology update to feed to the channel
|
// We'll create a fresh new node topology update to feed to the channel
|
||||||
// router.
|
// router.
|
||||||
@ -743,7 +757,7 @@ func TestChannelCloseNotification(t *testing.T) {
|
|||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight)
|
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight)
|
||||||
|
|
||||||
// Next we'll create two test nodes that the fake channel will be open
|
// Next we'll create two test nodes that the fake channel will be open
|
||||||
// between.
|
// between.
|
||||||
@ -797,7 +811,7 @@ func TestChannelCloseNotification(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(newBlock, blockHeight)
|
ctx.chain.addBlock(newBlock, blockHeight, blockHeight)
|
||||||
ctx.chainView.notifyBlock(newBlock.Header.BlockHash(), blockHeight,
|
ctx.chainView.notifyBlock(newBlock.Header.BlockHash(), blockHeight,
|
||||||
newBlock.Transactions)
|
newBlock.Transactions)
|
||||||
|
|
||||||
|
@ -185,9 +185,14 @@ type ChannelRouter struct {
|
|||||||
routeCache map[routeTuple][]*Route
|
routeCache map[routeTuple][]*Route
|
||||||
|
|
||||||
// newBlocks is a channel in which new blocks connected to the end of
|
// newBlocks is a channel in which new blocks connected to the end of
|
||||||
// the main chain are sent over.
|
// the main chain are sent over, and blocks updated after a call to
|
||||||
|
// UpdateFilter.
|
||||||
newBlocks <-chan *chainview.FilteredBlock
|
newBlocks <-chan *chainview.FilteredBlock
|
||||||
|
|
||||||
|
// staleBlocks is a channel in which blocks disconnected fromt the end
|
||||||
|
// of our currently known best chain are sent over.
|
||||||
|
staleBlocks <-chan *chainview.FilteredBlock
|
||||||
|
|
||||||
// networkUpdates is a channel that carries new topology updates
|
// networkUpdates is a channel that carries new topology updates
|
||||||
// messages from outside the ChannelRouter to be processed by the
|
// messages from outside the ChannelRouter to be processed by the
|
||||||
// networkHandler.
|
// networkHandler.
|
||||||
@ -266,6 +271,7 @@ func (r *ChannelRouter) Start() error {
|
|||||||
// Once the instance is active, we'll fetch the channel we'll receive
|
// Once the instance is active, we'll fetch the channel we'll receive
|
||||||
// notifications over.
|
// notifications over.
|
||||||
r.newBlocks = r.cfg.ChainView.FilteredBlocks()
|
r.newBlocks = r.cfg.ChainView.FilteredBlocks()
|
||||||
|
r.staleBlocks = r.cfg.ChainView.DisconnectedBlocks()
|
||||||
|
|
||||||
// Before we begin normal operation of the router, we first need to
|
// Before we begin normal operation of the router, we first need to
|
||||||
// synchronize the channel graph to the latest state of the UTXO set.
|
// synchronize the channel graph to the latest state of the UTXO set.
|
||||||
@ -352,6 +358,46 @@ func (r *ChannelRouter) syncGraphWithChain() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the main chain blockhash at prune height is different from the
|
||||||
|
// prune hash, this might indicate the database is on a stale branch.
|
||||||
|
mainBlockHash, err := r.cfg.Chain.GetBlockHash(int64(pruneHeight))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we are on a stale branch of the chain, walk backwards to find
|
||||||
|
// first common block.
|
||||||
|
for !pruneHash.IsEqual(mainBlockHash) {
|
||||||
|
log.Infof("channel graph is stale. Disconnecting block %v "+
|
||||||
|
"(hash=%v)", pruneHeight, pruneHash)
|
||||||
|
// Prune the graph for every channel that was opened at height
|
||||||
|
// >= pruneHeigth.
|
||||||
|
_, err := r.cfg.Graph.DisconnectBlockAtHeight(pruneHeight)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pruneHash, pruneHeight, err = r.cfg.Graph.PruneTip()
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
// If at this point the graph has never been pruned, we
|
||||||
|
// can exit as this entails we are back to the point
|
||||||
|
// where it hasn't seen any block or created channels,
|
||||||
|
// alas there's nothing left to prune.
|
||||||
|
case err == channeldb.ErrGraphNeverPruned:
|
||||||
|
return nil
|
||||||
|
case err == channeldb.ErrGraphNotFound:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainBlockHash, err = r.cfg.Chain.GetBlockHash(int64(pruneHeight))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("Syncing channel graph from height=%v (hash=%v) to height=%v "+
|
log.Infof("Syncing channel graph from height=%v (hash=%v) to height=%v "+
|
||||||
"(hash=%v)", pruneHeight, pruneHash, bestHeight, bestHash)
|
"(hash=%v)", pruneHeight, pruneHash, bestHeight, bestHash)
|
||||||
|
|
||||||
@ -449,6 +495,35 @@ func (r *ChannelRouter) networkHandler() {
|
|||||||
// after N blocks pass with no corresponding
|
// after N blocks pass with no corresponding
|
||||||
// announcements.
|
// announcements.
|
||||||
|
|
||||||
|
case chainUpdate, ok := <-r.staleBlocks:
|
||||||
|
// If the channel has been closed, then this indicates
|
||||||
|
// the daemon is shutting down, so we exit ourselves.
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since this block is stale, we update our best height
|
||||||
|
// to the previous block.
|
||||||
|
blockHeight := uint32(chainUpdate.Height)
|
||||||
|
r.bestHeight = blockHeight - 1
|
||||||
|
|
||||||
|
// Update the channel graph to reflect that this block
|
||||||
|
// was disconnected.
|
||||||
|
_, err := r.cfg.Graph.DisconnectBlockAtHeight(blockHeight)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to prune graph with stale "+
|
||||||
|
"block: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the route cache, as some channels might
|
||||||
|
// not be confirmed anymore.
|
||||||
|
r.routeCacheMtx.Lock()
|
||||||
|
r.routeCache = make(map[routeTuple][]*Route)
|
||||||
|
r.routeCacheMtx.Unlock()
|
||||||
|
|
||||||
|
// TODO(halseth): notify client about the reorg?
|
||||||
|
|
||||||
// A new block has arrived, so we can prune the channel graph
|
// A new block has arrived, so we can prune the channel graph
|
||||||
// of any channels which were closed in the block.
|
// of any channels which were closed in the block.
|
||||||
case chainUpdate, ok := <-r.newBlocks:
|
case chainUpdate, ok := <-r.newBlocks:
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -400,7 +401,7 @@ func TestAddProof(t *testing.T) {
|
|||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight)
|
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight)
|
||||||
|
|
||||||
// After utxo was recreated adding the edge without the proof.
|
// After utxo was recreated adding the edge without the proof.
|
||||||
edge := &channeldb.ChannelEdgeInfo{
|
edge := &channeldb.ChannelEdgeInfo{
|
||||||
@ -502,7 +503,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
|
|||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight)
|
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight)
|
||||||
|
|
||||||
edge := &channeldb.ChannelEdgeInfo{
|
edge := &channeldb.ChannelEdgeInfo{
|
||||||
ChannelID: chanID.ToUint64(),
|
ChannelID: chanID.ToUint64(),
|
||||||
@ -600,7 +601,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
|
|||||||
fundingBlock = &wire.MsgBlock{
|
fundingBlock = &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight)
|
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight)
|
||||||
|
|
||||||
edge = &channeldb.ChannelEdgeInfo{
|
edge = &channeldb.ChannelEdgeInfo{
|
||||||
ChannelID: chanID.ToUint64(),
|
ChannelID: chanID.ToUint64(),
|
||||||
@ -718,3 +719,397 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
|
|||||||
t.Fatalf("fetched node not equal to original")
|
t.Fatalf("fetched node not equal to original")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestWakeUpOnStaleBranch tests that upon startup of the ChannelRouter, if the
|
||||||
|
// the chain previously reflected in the channel graph is stale (overtaken by a
|
||||||
|
// longer chain), the channel router will prune the graph for any channels
|
||||||
|
// confirmed on the stale chain, and resync to the main chain.
|
||||||
|
func TestWakeUpOnStaleBranch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const startingBlockHeight = 101
|
||||||
|
ctx, cleanUp, err := createTestCtx(startingBlockHeight)
|
||||||
|
defer cleanUp()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const chanValue = 10000
|
||||||
|
|
||||||
|
// chanID1 will not be reorged out.
|
||||||
|
var chanID1 uint64
|
||||||
|
|
||||||
|
// chanID2 will be reorged out.
|
||||||
|
var chanID2 uint64
|
||||||
|
|
||||||
|
// Create 10 common blocks, confirming chanID1.
|
||||||
|
for i := uint32(1); i <= 10; i++ {
|
||||||
|
block := &wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{},
|
||||||
|
}
|
||||||
|
height := startingBlockHeight + i
|
||||||
|
if i == 5 {
|
||||||
|
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
||||||
|
bitcoinKey1.SerializeCompressed(),
|
||||||
|
bitcoinKey2.SerializeCompressed(),
|
||||||
|
chanValue, height)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable create channel edge: %v", err)
|
||||||
|
}
|
||||||
|
block.Transactions = append(block.Transactions,
|
||||||
|
fundingTx)
|
||||||
|
chanID1 = chanID.ToUint64()
|
||||||
|
|
||||||
|
}
|
||||||
|
ctx.chain.addBlock(block, height, rand.Uint32())
|
||||||
|
ctx.chain.setBestBlock(int32(height))
|
||||||
|
ctx.chainView.notifyBlock(block.BlockHash(), height,
|
||||||
|
[]*wire.MsgTx{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give time to process new blocks
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
_, forkHeight, err := ctx.chain.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to ge best block: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 10 blocks on the minority chain, confirming chanID2.
|
||||||
|
for i := uint32(1); i <= 10; i++ {
|
||||||
|
block := &wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{},
|
||||||
|
}
|
||||||
|
height := uint32(forkHeight) + i
|
||||||
|
if i == 5 {
|
||||||
|
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
||||||
|
bitcoinKey1.SerializeCompressed(),
|
||||||
|
bitcoinKey2.SerializeCompressed(),
|
||||||
|
chanValue, height)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable create channel edge: %v", err)
|
||||||
|
}
|
||||||
|
block.Transactions = append(block.Transactions,
|
||||||
|
fundingTx)
|
||||||
|
chanID2 = chanID.ToUint64()
|
||||||
|
}
|
||||||
|
ctx.chain.addBlock(block, height, rand.Uint32())
|
||||||
|
ctx.chain.setBestBlock(int32(height))
|
||||||
|
ctx.chainView.notifyBlock(block.BlockHash(), height,
|
||||||
|
[]*wire.MsgTx{})
|
||||||
|
}
|
||||||
|
// Give time to process new blocks
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
// Now add the two edges to the channel graph, and check that they
|
||||||
|
// correctly show up in the database.
|
||||||
|
node1, err := createTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create test node: %v", err)
|
||||||
|
}
|
||||||
|
node2, err := createTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create test node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
edge1 := &channeldb.ChannelEdgeInfo{
|
||||||
|
ChannelID: chanID1,
|
||||||
|
NodeKey1: node1.PubKey,
|
||||||
|
NodeKey2: node2.PubKey,
|
||||||
|
BitcoinKey1: bitcoinKey1,
|
||||||
|
BitcoinKey2: bitcoinKey2,
|
||||||
|
AuthProof: &channeldb.ChannelAuthProof{
|
||||||
|
NodeSig1: testSig,
|
||||||
|
NodeSig2: testSig,
|
||||||
|
BitcoinSig1: testSig,
|
||||||
|
BitcoinSig2: testSig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.router.AddEdge(edge1); err != nil {
|
||||||
|
t.Fatalf("unable to add edge: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
edge2 := &channeldb.ChannelEdgeInfo{
|
||||||
|
ChannelID: chanID2,
|
||||||
|
NodeKey1: node1.PubKey,
|
||||||
|
NodeKey2: node2.PubKey,
|
||||||
|
BitcoinKey1: bitcoinKey1,
|
||||||
|
BitcoinKey2: bitcoinKey2,
|
||||||
|
AuthProof: &channeldb.ChannelAuthProof{
|
||||||
|
NodeSig1: testSig,
|
||||||
|
NodeSig2: testSig,
|
||||||
|
BitcoinSig1: testSig,
|
||||||
|
BitcoinSig2: testSig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.router.AddEdge(edge2); err != nil {
|
||||||
|
t.Fatalf("unable to add edge: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the fundingTxs are in the graph db.
|
||||||
|
_, _, has, err := ctx.graph.HasChannelEdge(chanID1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error looking for edge: %v", chanID1)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatalf("could not find edge in graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, has, err = ctx.graph.HasChannelEdge(chanID2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error looking for edge: %v", chanID2)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatalf("could not find edge in graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the router, so we can reorg the chain while its offline.
|
||||||
|
if err := ctx.router.Stop(); err != nil {
|
||||||
|
t.Fatalf("unable to stop router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a 15 block fork.
|
||||||
|
for i := uint32(1); i <= 15; i++ {
|
||||||
|
block := &wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{},
|
||||||
|
}
|
||||||
|
height := uint32(forkHeight) + i
|
||||||
|
ctx.chain.addBlock(block, height, rand.Uint32())
|
||||||
|
ctx.chain.setBestBlock(int32(height))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give time to process new blocks.
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
// Create new router with same graph database.
|
||||||
|
router, err := New(Config{
|
||||||
|
Graph: ctx.graph,
|
||||||
|
Chain: ctx.chain,
|
||||||
|
ChainView: ctx.chainView,
|
||||||
|
SendToSwitch: func(_ *btcec.PublicKey,
|
||||||
|
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
||||||
|
return [32]byte{}, nil
|
||||||
|
},
|
||||||
|
ChannelPruneExpiry: time.Hour * 24,
|
||||||
|
GraphPruneInterval: time.Hour * 2,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create router %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It should resync to the longer chain on startup.
|
||||||
|
if err := router.Start(); err != nil {
|
||||||
|
t.Fatalf("unable to start router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The channel with chanID2 should not be in the database anymore,
|
||||||
|
// since it is not confirmed on the longest chain. chanID1 should
|
||||||
|
// still be.
|
||||||
|
_, _, has, err = ctx.graph.HasChannelEdge(chanID1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error looking for edge: %v", chanID1)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatalf("did not find edge in graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, has, err = ctx.graph.HasChannelEdge(chanID2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error looking for edge: %v", chanID2)
|
||||||
|
}
|
||||||
|
if has {
|
||||||
|
t.Fatalf("found edge in graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDisconnectedBlocks checks that the router handles a reorg happening
|
||||||
|
// when it is active.
|
||||||
|
func TestDisconnectedBlocks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const startingBlockHeight = 101
|
||||||
|
ctx, cleanUp, err := createTestCtx(startingBlockHeight)
|
||||||
|
defer cleanUp()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const chanValue = 10000
|
||||||
|
|
||||||
|
// chanID1 will not be reorged out.
|
||||||
|
var chanID1 uint64
|
||||||
|
|
||||||
|
// chanID2 will be reorged out.
|
||||||
|
var chanID2 uint64
|
||||||
|
|
||||||
|
// Create 10 common blocks, confirming chanID1.
|
||||||
|
for i := uint32(1); i <= 10; i++ {
|
||||||
|
block := &wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{},
|
||||||
|
}
|
||||||
|
height := startingBlockHeight + i
|
||||||
|
if i == 5 {
|
||||||
|
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
||||||
|
bitcoinKey1.SerializeCompressed(),
|
||||||
|
bitcoinKey2.SerializeCompressed(),
|
||||||
|
chanValue, height)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable create channel edge: %v", err)
|
||||||
|
}
|
||||||
|
block.Transactions = append(block.Transactions,
|
||||||
|
fundingTx)
|
||||||
|
chanID1 = chanID.ToUint64()
|
||||||
|
|
||||||
|
}
|
||||||
|
ctx.chain.addBlock(block, height, rand.Uint32())
|
||||||
|
ctx.chain.setBestBlock(int32(height))
|
||||||
|
ctx.chainView.notifyBlock(block.BlockHash(), height,
|
||||||
|
[]*wire.MsgTx{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give time to process new blocks
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
_, forkHeight, err := ctx.chain.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to get best block: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 10 blocks on the minority chain, confirming chanID2.
|
||||||
|
var minorityChain []*wire.MsgBlock
|
||||||
|
for i := uint32(1); i <= 10; i++ {
|
||||||
|
block := &wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{},
|
||||||
|
}
|
||||||
|
height := uint32(forkHeight) + i
|
||||||
|
if i == 5 {
|
||||||
|
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
||||||
|
bitcoinKey1.SerializeCompressed(),
|
||||||
|
bitcoinKey2.SerializeCompressed(),
|
||||||
|
chanValue, height)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable create channel edge: %v", err)
|
||||||
|
}
|
||||||
|
block.Transactions = append(block.Transactions,
|
||||||
|
fundingTx)
|
||||||
|
chanID2 = chanID.ToUint64()
|
||||||
|
}
|
||||||
|
minorityChain = append(minorityChain, block)
|
||||||
|
ctx.chain.addBlock(block, height, rand.Uint32())
|
||||||
|
ctx.chain.setBestBlock(int32(height))
|
||||||
|
ctx.chainView.notifyBlock(block.BlockHash(), height,
|
||||||
|
[]*wire.MsgTx{})
|
||||||
|
}
|
||||||
|
// Give time to process new blocks
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
// Now add the two edges to the channel graph, and check that they
|
||||||
|
// correctly show up in the database.
|
||||||
|
node1, err := createTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create test node: %v", err)
|
||||||
|
}
|
||||||
|
node2, err := createTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create test node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
edge1 := &channeldb.ChannelEdgeInfo{
|
||||||
|
ChannelID: chanID1,
|
||||||
|
NodeKey1: node1.PubKey,
|
||||||
|
NodeKey2: node2.PubKey,
|
||||||
|
BitcoinKey1: bitcoinKey1,
|
||||||
|
BitcoinKey2: bitcoinKey2,
|
||||||
|
AuthProof: &channeldb.ChannelAuthProof{
|
||||||
|
NodeSig1: testSig,
|
||||||
|
NodeSig2: testSig,
|
||||||
|
BitcoinSig1: testSig,
|
||||||
|
BitcoinSig2: testSig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.router.AddEdge(edge1); err != nil {
|
||||||
|
t.Fatalf("unable to add edge: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
edge2 := &channeldb.ChannelEdgeInfo{
|
||||||
|
ChannelID: chanID2,
|
||||||
|
NodeKey1: node1.PubKey,
|
||||||
|
NodeKey2: node2.PubKey,
|
||||||
|
BitcoinKey1: bitcoinKey1,
|
||||||
|
BitcoinKey2: bitcoinKey2,
|
||||||
|
AuthProof: &channeldb.ChannelAuthProof{
|
||||||
|
NodeSig1: testSig,
|
||||||
|
NodeSig2: testSig,
|
||||||
|
BitcoinSig1: testSig,
|
||||||
|
BitcoinSig2: testSig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.router.AddEdge(edge2); err != nil {
|
||||||
|
t.Fatalf("unable to add edge: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the fundingTxs are in the graph db.
|
||||||
|
_, _, has, err := ctx.graph.HasChannelEdge(chanID1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error looking for edge: %v", chanID1)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatalf("could not find edge in graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, has, err = ctx.graph.HasChannelEdge(chanID2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error looking for edge: %v", chanID2)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatalf("could not find edge in graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a 15 block fork. We first let the chainView notify the
|
||||||
|
// router about stale blocks, before sending the now connected
|
||||||
|
// blocks. We do this because we expect this order from the
|
||||||
|
// chainview.
|
||||||
|
for i := len(minorityChain) - 1; i >= 0; i-- {
|
||||||
|
block := minorityChain[i]
|
||||||
|
height := uint32(forkHeight) + uint32(i) + 1
|
||||||
|
ctx.chainView.notifyStaleBlock(block.BlockHash(), height,
|
||||||
|
block.Transactions)
|
||||||
|
}
|
||||||
|
for i := uint32(1); i <= 15; i++ {
|
||||||
|
block := &wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{},
|
||||||
|
}
|
||||||
|
height := uint32(forkHeight) + i
|
||||||
|
ctx.chain.addBlock(block, height, rand.Uint32())
|
||||||
|
ctx.chain.setBestBlock(int32(height))
|
||||||
|
ctx.chainView.notifyBlock(block.BlockHash(), height,
|
||||||
|
block.Transactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give time to process new blocks
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
// The with chanID2 should not be in the database anymore, since it is
|
||||||
|
// not confirmed on the longest chain. chanID1 should still be.
|
||||||
|
_, _, has, err = ctx.graph.HasChannelEdge(chanID1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error looking for edge: %v", chanID1)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatalf("did not find edge in graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, has, err = ctx.graph.HasChannelEdge(chanID2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error looking for edge: %v", chanID2)
|
||||||
|
}
|
||||||
|
if has {
|
||||||
|
t.Fatalf("found edge in graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user