From 4bca54e30c767756cfee3a45fe3d6ab51ce9a676 Mon Sep 17 00:00:00 2001 From: Andrey Samokhvalov Date: Thu, 30 Mar 2017 04:01:28 +0300 Subject: [PATCH] routing: add validation of utxo Add check that the edge that was received really exist in the blockchain and that the announced funding keys and capcity corresponds to reality. --- discovery/service_test.go | 5 -- routing/notifications_test.go | 105 +++++++++++++++++----------------- routing/router.go | 46 ++++++++++++--- routing/router_test.go | 6 +- 4 files changed, 94 insertions(+), 68 deletions(-) diff --git a/discovery/service_test.go b/discovery/service_test.go index 4b6f6dc1..85ce3e79 100644 --- a/discovery/service_test.go +++ b/discovery/service_test.go @@ -65,11 +65,6 @@ func (r *mockGraphSource) UpdateEdge(policy *channeldb.ChannelEdgePolicy) error return nil } -func (r *mockGraphSource) AddProof(chanID uint8, - proof *channeldb.ChannelAuthProof) error { - return nil -} - func (r *mockGraphSource) SelfEdges() ([]*channeldb.ChannelEdgePolicy, error) { return nil, nil } diff --git a/routing/notifications_test.go b/routing/notifications_test.go index e0170885..ab783317 100644 --- a/routing/notifications_test.go +++ b/routing/notifications_test.go @@ -10,8 +10,10 @@ import ( prand "math/rand" + "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" @@ -32,14 +34,20 @@ var ( 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, } + + priv1, _ = btcec.NewPrivateKey(btcec.S256()) + bitcoinKey1 = priv1.PubKey() + + priv2, _ = btcec.NewPrivateKey(btcec.S256()) + bitcoinKey2 = priv2.PubKey() ) -func createGraphNode() (*channeldb.LightningNode, error) { +func createTestNode() (*channeldb.LightningNode, error) { updateTime := prand.Int63() priv, err := btcec.NewPrivateKey(btcec.S256()) if err != nil { - return nil, err + return nil, errors.Errorf("unable create private key: %v", err) } pub := priv.PubKey().SerializeCompressed() @@ -54,29 +62,7 @@ func createGraphNode() (*channeldb.LightningNode, error) { }, nil } -func createTestNode() (*channeldb.LightningNode, error) { - priv, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - return nil, err - } - - pub := priv.PubKey().SerializeCompressed() - - alias, err := lnwire.NewAlias("kek" + string(pub[:])) - if err != nil { - return nil, err - } - - return &channeldb.LightningNode{ - LastUpdate: time.Now(), - Addresses: testAddrs, - PubKey: priv.PubKey(), - Alias: alias.String(), - Features: testFeatures, - }, nil -} - -func randEdgePolicy(chanID lnwire.ShortChannelID, +func randEdgePolicy(chanID *lnwire.ShortChannelID, node *channeldb.LightningNode) *channeldb.ChannelEdgePolicy { return &channeldb.ChannelEdgePolicy{ @@ -91,29 +77,37 @@ func randEdgePolicy(chanID lnwire.ShortChannelID, } } -func randChannelEdge(ctx *testCtx, chanValue btcutil.Amount, - fundingHeight uint32) (*wire.MsgTx, wire.OutPoint, lnwire.ShortChannelID) { +func createChannelEdge(ctx *testCtx, bitcoinKey1, bitcoinKey2 []byte, + chanValue int64, fundingHeight uint32) (*wire.MsgTx, *wire.OutPoint, + *lnwire.ShortChannelID, error) { fundingTx := wire.NewMsgTx(2) - fundingTx.TxOut = append(fundingTx.TxOut, &wire.TxOut{ - Value: int64(chanValue), - }) + _, tx, err := lnwallet.GenFundingPkScript( + bitcoinKey1, + bitcoinKey2, + chanValue, + ) + if err != nil { + return nil, nil, nil, err + } + + fundingTx.TxOut = append(fundingTx.TxOut, tx) chanUtxo := wire.OutPoint{ Hash: fundingTx.TxHash(), Index: 0, } // With the utxo constructed, we'll mark it as closed. - ctx.chain.addUtxo(chanUtxo, chanValue) + ctx.chain.addUtxo(chanUtxo, tx) // Our fake channel will be "confirmed" at height 101. - chanID := lnwire.ShortChannelID{ + chanID := &lnwire.ShortChannelID{ BlockHeight: fundingHeight, TxIndex: 0, TxPosition: 0, } - return fundingTx, chanUtxo, chanID + return fundingTx, &chanUtxo, chanID, nil } type mockChain struct { @@ -169,11 +163,9 @@ func (m *mockChain) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) { return &hash, nil } -func (m *mockChain) addUtxo(op wire.OutPoint, value btcutil.Amount) { +func (m *mockChain) addUtxo(op wire.OutPoint, out *wire.TxOut) { m.Lock() - m.utxos[op] = wire.TxOut{ - Value: int64(value), - } + m.utxos[op] = *out m.Unlock() } func (m *mockChain) GetUtxo(txid *chainhash.Hash, index uint32) (*wire.TxOut, error) { @@ -274,17 +266,20 @@ func (m *mockNotifier) Stop() error { // TestEdgeUpdateNotification tests that when edges are updated or added, // a proper notification is sent of to all registered clients. func TestEdgeUpdateNotification(t *testing.T) { - const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtx(startingBlockHeight) + ctx, cleanUp, err := createTestCtx(0) defer cleanUp() if err != nil { t.Fatalf("unable to create router: %v", err) } // First we'll create the utxo for the channel to be "closed" - const chanValue = btcutil.Amount(10000) - fundingTx, chanPoint, chanID := randChannelEdge(ctx, chanValue, - startingBlockHeight) + const chanValue = 10000 + fundingTx, chanPoint, chanID, err := createChannelEdge(ctx, + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), + chanValue, 0) + if err != nil { + t.Fatalf("unbale create channel edge: %v", err) + } // We'll also add a record for the block that included our funding // transaction. @@ -319,8 +314,8 @@ func TestEdgeUpdateNotification(t *testing.T) { ChannelID: chanID.ToUint64(), NodeKey1: node1.PubKey, NodeKey2: node2.PubKey, - BitcoinKey1: node1.PubKey, - BitcoinKey2: node2.PubKey, + BitcoinKey1: bitcoinKey1, + BitcoinKey2: bitcoinKey2, AuthProof: &channeldb.ChannelAuthProof{ NodeSig1: testSig, NodeSig2: testSig, @@ -330,7 +325,7 @@ func TestEdgeUpdateNotification(t *testing.T) { } if err := ctx.router.AddEdge(edge); err != nil { - t.Fatal(err) + t.Fatalf("unable to add edge: %v", err) } // With the channel edge now in place, we'll subscribe for topology @@ -360,7 +355,7 @@ func TestEdgeUpdateNotification(t *testing.T) { t.Fatalf("channel ID of edge doesn't match: "+ "expected %v, got %v", chanID.ToUint64(), edgeUpdate.ChanID) } - if edgeUpdate.ChanPoint != chanPoint { + if edgeUpdate.ChanPoint != *chanPoint { t.Fatalf("channel don't match: expected %v, got %v", chanPoint, edgeUpdate.ChanPoint) } @@ -580,9 +575,13 @@ func TestChannelCloseNotification(t *testing.T) { } // First we'll create the utxo for the channel to be "closed" - const chanValue = btcutil.Amount(10000) - fundingTx, chanUtxo, chanID := randChannelEdge(ctx, chanValue, - startingBlockHeight) + const chanValue = 10000 + fundingTx, chanUtxo, chanID, err := createChannelEdge(ctx, + bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), + chanValue, startingBlockHeight) + if err != nil { + t.Fatalf("unable create channel edge: %v", err) + } // We'll also add a record for the block that included our funding // transaction. @@ -608,8 +607,8 @@ func TestChannelCloseNotification(t *testing.T) { ChannelID: chanID.ToUint64(), NodeKey1: node1.PubKey, NodeKey2: node2.PubKey, - BitcoinKey1: node1.PubKey, - BitcoinKey2: node2.PubKey, + BitcoinKey1: bitcoinKey1, + BitcoinKey2: bitcoinKey2, AuthProof: &channeldb.ChannelAuthProof{ NodeSig1: testSig, NodeSig2: testSig, @@ -637,7 +636,7 @@ func TestChannelCloseNotification(t *testing.T) { { TxIn: []*wire.TxIn{ { - PreviousOutPoint: chanUtxo, + PreviousOutPoint: *chanUtxo, }, }, }, @@ -678,7 +677,7 @@ func TestChannelCloseNotification(t *testing.T) { t.Fatalf("close height of closed channel doesn't match: "+ "expected %v, got %v", blockHeight, closedChan.ClosedHeight) } - if closedChan.ChanPoint != chanUtxo { + if closedChan.ChanPoint != *chanUtxo { t.Fatalf("chan point of closed channel doesn't match: "+ "expected %v, got %v", chanUtxo, closedChan.ChanPoint) } diff --git a/routing/router.go b/routing/router.go index d8cf39a7..3dbdd74b 100644 --- a/routing/router.go +++ b/routing/router.go @@ -45,6 +45,10 @@ type ChannelGraphSource interface { // subsystem. CurrentBlockHeight() (uint32, error) + // GetChannelByID return the channel by the channel id. + GetChannelByID(chanID lnwire.ShortChannelID) (*channeldb.ChannelEdgeInfo, + *channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy, error) + // ForEachNode is used to iterate over every node in router topology. ForEachNode(func(node *channeldb.LightningNode) error) error @@ -541,9 +545,6 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { "chan_id=%v", msg.ChannelID) } - // TODO(andrew.shvv) Add validation that bitcoin keys are - // binded to the funding transaction. - // Before we can add the channel to the channel graph, we need // to obtain the full funding outpoint that's encoded within // the channel ID. @@ -560,14 +561,37 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { chanUtxo, err := r.cfg.Chain.GetUtxo(&fundingPoint.Hash, fundingPoint.Index) if err != nil { return errors.Errorf("unable to fetch utxo for "+ - "chan_id=%v: %v", channelID, err) + "chan_id=%v: %v", msg.ChannelID, err) + } + + // Recreate witness output to be sure that declared in + // channel edge bitcoin keys and channel value corresponds to + // the reality. + _, witnessOutput, err := lnwallet.GenFundingPkScript( + msg.BitcoinKey1.SerializeCompressed(), + msg.BitcoinKey2.SerializeCompressed(), + chanUtxo.Value, + ) + if err != nil { + return errors.Errorf("unable to create funding pk "+ + "script: %v", err) + } + + // By checking the equality of witness pkscripts we checks that + // funding witness script is multisignature lock which contains + // both local and remote public keys which was declared in + // channel edge and also that the announced channel value is + // right. + if !bytes.Equal(witnessOutput.PkScript, chanUtxo.PkScript) { + return errors.New("pkscipts aren't equal, " + + "which means that either bitcoin keys" + + " are wrong or value don't correponds") } // TODO(roasbeef): this is a hack, needs to be removed // after commitment fees are dynamic. msg.Capacity = btcutil.Amount(chanUtxo.Value) - btcutil.Amount(5000) msg.ChannelPoint = *fundingPoint - if err := r.cfg.Graph.AddChannelEdge(msg); err != nil { return errors.Errorf("unable to add edge: %v", err) } @@ -681,9 +705,6 @@ func (r *ChannelRouter) fetchChanPoint(chanID *lnwire.ShortChannelID) (*wire.Out numTxns-1, spew.Sdump(chanID)) } - // TODO(roasbeef): skipping validation here as the discovery service - // should handle full validate - // Finally once we have the block itself, we seek to the targeted // transaction index to obtain the funding output and txid. fundingTx := fundingBlock.Transactions[chanID.TxIndex] @@ -1012,6 +1033,15 @@ func (r *ChannelRouter) CurrentBlockHeight() (uint32, error) { return uint32(height), err } +// GetChannelByID return the channel by the channel id. +// NOTE: Part of the Router interface. +func (r *ChannelRouter) GetChannelByID(chanID lnwire.ShortChannelID) ( + *channeldb.ChannelEdgeInfo, + *channeldb.ChannelEdgePolicy, + *channeldb.ChannelEdgePolicy, error) { + return r.cfg.Graph.FetchChannelEdgesByID(chanID.ToUint64()) +} + // ForEachNode is used to iterate over every node in router topology. // NOTE: Part of the ChannelGraphSource interface. func (r *ChannelRouter) ForEachNode(cb func(*channeldb.LightningNode) error) error { diff --git a/routing/router_test.go b/routing/router_test.go index 96cbddeb..a8bd7d56 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -6,8 +6,10 @@ import ( "fmt" "testing" - "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" + "github.com/roasbeef/btcd/wire" + + "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcutil" @@ -46,7 +48,7 @@ func createTestCtx(startingHeight uint32, testGraph ...string) (*testCtx, func() return nil, nil, fmt.Errorf("unable to create test graph: %v", err) } - sourceNode, err = createGraphNode() + sourceNode, err = createTestNode() if err != nil { return nil, nil, fmt.Errorf("unable to create source node: %v", err) }