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.
This commit is contained in:
Andrey Samokhvalov 2017-03-30 04:01:28 +03:00 committed by Olaoluwa Osuntokun
parent 19174ebdfd
commit 4bca54e30c
4 changed files with 94 additions and 68 deletions

@ -65,11 +65,6 @@ func (r *mockGraphSource) UpdateEdge(policy *channeldb.ChannelEdgePolicy) error
return nil return nil
} }
func (r *mockGraphSource) AddProof(chanID uint8,
proof *channeldb.ChannelAuthProof) error {
return nil
}
func (r *mockGraphSource) SelfEdges() ([]*channeldb.ChannelEdgePolicy, error) { func (r *mockGraphSource) SelfEdges() ([]*channeldb.ChannelEdgePolicy, error) {
return nil, nil return nil, nil
} }

@ -10,8 +10,10 @@ import (
prand "math/rand" prand "math/rand"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/chaincfg/chainhash"
@ -32,14 +34,20 @@ var (
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, 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() updateTime := prand.Int63()
priv, err := btcec.NewPrivateKey(btcec.S256()) priv, err := btcec.NewPrivateKey(btcec.S256())
if err != nil { if err != nil {
return nil, err return nil, errors.Errorf("unable create private key: %v", err)
} }
pub := priv.PubKey().SerializeCompressed() pub := priv.PubKey().SerializeCompressed()
@ -54,29 +62,7 @@ func createGraphNode() (*channeldb.LightningNode, error) {
}, nil }, nil
} }
func createTestNode() (*channeldb.LightningNode, error) { func randEdgePolicy(chanID *lnwire.ShortChannelID,
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,
node *channeldb.LightningNode) *channeldb.ChannelEdgePolicy { node *channeldb.LightningNode) *channeldb.ChannelEdgePolicy {
return &channeldb.ChannelEdgePolicy{ return &channeldb.ChannelEdgePolicy{
@ -91,29 +77,37 @@ func randEdgePolicy(chanID lnwire.ShortChannelID,
} }
} }
func randChannelEdge(ctx *testCtx, chanValue btcutil.Amount, func createChannelEdge(ctx *testCtx, bitcoinKey1, bitcoinKey2 []byte,
fundingHeight uint32) (*wire.MsgTx, wire.OutPoint, lnwire.ShortChannelID) { chanValue int64, fundingHeight uint32) (*wire.MsgTx, *wire.OutPoint,
*lnwire.ShortChannelID, error) {
fundingTx := wire.NewMsgTx(2) fundingTx := wire.NewMsgTx(2)
fundingTx.TxOut = append(fundingTx.TxOut, &wire.TxOut{ _, tx, err := lnwallet.GenFundingPkScript(
Value: int64(chanValue), bitcoinKey1,
}) bitcoinKey2,
chanValue,
)
if err != nil {
return nil, nil, nil, err
}
fundingTx.TxOut = append(fundingTx.TxOut, tx)
chanUtxo := wire.OutPoint{ chanUtxo := wire.OutPoint{
Hash: fundingTx.TxHash(), Hash: fundingTx.TxHash(),
Index: 0, Index: 0,
} }
// With the utxo constructed, we'll mark it as closed. // 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. // Our fake channel will be "confirmed" at height 101.
chanID := lnwire.ShortChannelID{ chanID := &lnwire.ShortChannelID{
BlockHeight: fundingHeight, BlockHeight: fundingHeight,
TxIndex: 0, TxIndex: 0,
TxPosition: 0, TxPosition: 0,
} }
return fundingTx, chanUtxo, chanID return fundingTx, &chanUtxo, chanID, nil
} }
type mockChain struct { type mockChain struct {
@ -169,11 +163,9 @@ func (m *mockChain) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
return &hash, nil 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.Lock()
m.utxos[op] = wire.TxOut{ m.utxos[op] = *out
Value: int64(value),
}
m.Unlock() m.Unlock()
} }
func (m *mockChain) GetUtxo(txid *chainhash.Hash, index uint32) (*wire.TxOut, error) { 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, // TestEdgeUpdateNotification tests that when edges are updated or added,
// a proper notification is sent of to all registered clients. // a proper notification is sent of to all registered clients.
func TestEdgeUpdateNotification(t *testing.T) { func TestEdgeUpdateNotification(t *testing.T) {
const startingBlockHeight = 101 ctx, cleanUp, err := createTestCtx(0)
ctx, cleanUp, err := createTestCtx(startingBlockHeight)
defer cleanUp() defer cleanUp()
if err != nil { if err != nil {
t.Fatalf("unable to create router: %v", err) t.Fatalf("unable to create router: %v", err)
} }
// First we'll create the utxo for the channel to be "closed" // First we'll create the utxo for the channel to be "closed"
const chanValue = btcutil.Amount(10000) const chanValue = 10000
fundingTx, chanPoint, chanID := randChannelEdge(ctx, chanValue, fundingTx, chanPoint, chanID, err := createChannelEdge(ctx,
startingBlockHeight) 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 // We'll also add a record for the block that included our funding
// transaction. // transaction.
@ -319,8 +314,8 @@ func TestEdgeUpdateNotification(t *testing.T) {
ChannelID: chanID.ToUint64(), ChannelID: chanID.ToUint64(),
NodeKey1: node1.PubKey, NodeKey1: node1.PubKey,
NodeKey2: node2.PubKey, NodeKey2: node2.PubKey,
BitcoinKey1: node1.PubKey, BitcoinKey1: bitcoinKey1,
BitcoinKey2: node2.PubKey, BitcoinKey2: bitcoinKey2,
AuthProof: &channeldb.ChannelAuthProof{ AuthProof: &channeldb.ChannelAuthProof{
NodeSig1: testSig, NodeSig1: testSig,
NodeSig2: testSig, NodeSig2: testSig,
@ -330,7 +325,7 @@ func TestEdgeUpdateNotification(t *testing.T) {
} }
if err := ctx.router.AddEdge(edge); err != nil { 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 // 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: "+ t.Fatalf("channel ID of edge doesn't match: "+
"expected %v, got %v", chanID.ToUint64(), edgeUpdate.ChanID) "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", t.Fatalf("channel don't match: expected %v, got %v",
chanPoint, edgeUpdate.ChanPoint) chanPoint, edgeUpdate.ChanPoint)
} }
@ -580,9 +575,13 @@ func TestChannelCloseNotification(t *testing.T) {
} }
// First we'll create the utxo for the channel to be "closed" // First we'll create the utxo for the channel to be "closed"
const chanValue = btcutil.Amount(10000) const chanValue = 10000
fundingTx, chanUtxo, chanID := randChannelEdge(ctx, chanValue, fundingTx, chanUtxo, chanID, err := createChannelEdge(ctx,
startingBlockHeight) 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 // We'll also add a record for the block that included our funding
// transaction. // transaction.
@ -608,8 +607,8 @@ func TestChannelCloseNotification(t *testing.T) {
ChannelID: chanID.ToUint64(), ChannelID: chanID.ToUint64(),
NodeKey1: node1.PubKey, NodeKey1: node1.PubKey,
NodeKey2: node2.PubKey, NodeKey2: node2.PubKey,
BitcoinKey1: node1.PubKey, BitcoinKey1: bitcoinKey1,
BitcoinKey2: node2.PubKey, BitcoinKey2: bitcoinKey2,
AuthProof: &channeldb.ChannelAuthProof{ AuthProof: &channeldb.ChannelAuthProof{
NodeSig1: testSig, NodeSig1: testSig,
NodeSig2: testSig, NodeSig2: testSig,
@ -637,7 +636,7 @@ func TestChannelCloseNotification(t *testing.T) {
{ {
TxIn: []*wire.TxIn{ 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: "+ t.Fatalf("close height of closed channel doesn't match: "+
"expected %v, got %v", blockHeight, closedChan.ClosedHeight) "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: "+ t.Fatalf("chan point of closed channel doesn't match: "+
"expected %v, got %v", chanUtxo, closedChan.ChanPoint) "expected %v, got %v", chanUtxo, closedChan.ChanPoint)
} }

@ -45,6 +45,10 @@ type ChannelGraphSource interface {
// subsystem. // subsystem.
CurrentBlockHeight() (uint32, error) 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 is used to iterate over every node in router topology.
ForEachNode(func(node *channeldb.LightningNode) error) error ForEachNode(func(node *channeldb.LightningNode) error) error
@ -541,9 +545,6 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error {
"chan_id=%v", msg.ChannelID) "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 // Before we can add the channel to the channel graph, we need
// to obtain the full funding outpoint that's encoded within // to obtain the full funding outpoint that's encoded within
// the channel ID. // the channel ID.
@ -560,14 +561,37 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error {
chanUtxo, err := r.cfg.Chain.GetUtxo(&fundingPoint.Hash, fundingPoint.Index) chanUtxo, err := r.cfg.Chain.GetUtxo(&fundingPoint.Hash, fundingPoint.Index)
if err != nil { if err != nil {
return errors.Errorf("unable to fetch utxo for "+ 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 // TODO(roasbeef): this is a hack, needs to be removed
// after commitment fees are dynamic. // after commitment fees are dynamic.
msg.Capacity = btcutil.Amount(chanUtxo.Value) - btcutil.Amount(5000) msg.Capacity = btcutil.Amount(chanUtxo.Value) - btcutil.Amount(5000)
msg.ChannelPoint = *fundingPoint msg.ChannelPoint = *fundingPoint
if err := r.cfg.Graph.AddChannelEdge(msg); err != nil { if err := r.cfg.Graph.AddChannelEdge(msg); err != nil {
return errors.Errorf("unable to add edge: %v", err) 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)) 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 // Finally once we have the block itself, we seek to the targeted
// transaction index to obtain the funding output and txid. // transaction index to obtain the funding output and txid.
fundingTx := fundingBlock.Transactions[chanID.TxIndex] fundingTx := fundingBlock.Transactions[chanID.TxIndex]
@ -1012,6 +1033,15 @@ func (r *ChannelRouter) CurrentBlockHeight() (uint32, error) {
return uint32(height), err 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. // ForEachNode is used to iterate over every node in router topology.
// NOTE: Part of the ChannelGraphSource interface. // NOTE: Part of the ChannelGraphSource interface.
func (r *ChannelRouter) ForEachNode(cb func(*channeldb.LightningNode) error) error { func (r *ChannelRouter) ForEachNode(cb func(*channeldb.LightningNode) error) error {

@ -6,8 +6,10 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcutil" "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) return nil, nil, fmt.Errorf("unable to create test graph: %v", err)
} }
sourceNode, err = createGraphNode() sourceNode, err = createTestNode()
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("unable to create source node: %v", err) return nil, nil, fmt.Errorf("unable to create source node: %v", err)
} }