multi: add labels to lnd native transactions

Follow up labelling of external transactions with labels for the
transaction types we create within lnd. Since these labels will live
a life of string matching, a version number and rigid format is added
so that string matching is less painful. We start out with channel ID,
where available, and a transaction "type". External labels, added in a
previous PR, are not updated to this new versioned label because they
are not lnd-initiated transactions. Label matching can check this case,
then check for a version number.
This commit is contained in:
carla 2020-07-29 09:27:22 +02:00
parent a39c91fbcb
commit 2a614cc596
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
13 changed files with 225 additions and 20 deletions

@ -20,6 +20,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb/kvdb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
@ -566,7 +567,8 @@ justiceTxBroadcast:
// We'll now attempt to broadcast the transaction which finalized the
// channel's retribution against the cheating counter party.
err = b.cfg.PublishTransaction(finalTx, "")
label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil)
err = b.cfg.PublishTransaction(finalTx, label)
if err != nil {
brarLog.Errorf("Unable to broadcast justice tx: %v", err)

@ -15,6 +15,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb/kvdb"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
@ -715,7 +716,10 @@ func (c *ChainArbitrator) rebroadcast(channel *channeldb.OpenChannel,
log.Infof("Re-publishing %s close tx(%v) for channel %v",
kind, closeTx.TxHash(), chanPoint)
err = c.cfg.PublishTx(closeTx, "")
label := labels.MakeLabel(
labels.LabelTypeChannelClose, &channel.ShortChannelID,
)
err = c.cfg.PublishTx(closeTx, label)
if err != nil && err != lnwallet.ErrDoubleSpend {
log.Warnf("Unable to broadcast %s close tx(%v): %v",
kind, closeTx.TxHash(), err)

@ -16,6 +16,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
@ -874,7 +875,11 @@ func (c *ChannelArbitrator) stateStep(
// At this point, we'll now broadcast the commitment
// transaction itself.
if err := c.cfg.PublishTx(closeTx, ""); err != nil {
label := labels.MakeLabel(
labels.LabelTypeChannelClose, &c.cfg.ShortChanID,
)
if err := c.cfg.PublishTx(closeTx, label); err != nil {
log.Errorf("ChannelArbitrator(%v): unable to broadcast "+
"close tx: %v", c.cfg.ChanPoint, err)
if err != lnwallet.ErrDoubleSpend {

@ -10,6 +10,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
)
@ -157,7 +158,10 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
// Regardless of whether an existing transaction was found or newly
// constructed, we'll broadcast the sweep transaction to the
// network.
err := h.PublishTx(h.sweepTx, "")
label := labels.MakeLabel(
labels.LabelTypeChannelClose, &h.ShortChanID,
)
err := h.PublishTx(h.sweepTx, label)
if err != nil {
log.Infof("%T(%x): unable to publish tx: %v",
h, h.htlc.RHash[:], err)
@ -206,7 +210,10 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
// the claiming process.
//
// TODO(roasbeef): after changing sighashes send to tx bundler
err := h.PublishTx(h.htlcResolution.SignedSuccessTx, "")
label := labels.MakeLabel(
labels.LabelTypeChannelClose, &h.ShortChanID,
)
err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
if err != nil {
return nil, err
}

@ -22,6 +22,7 @@ import (
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
@ -243,6 +244,10 @@ type fundingConfig struct {
// transaction to the network.
PublishTransaction func(*wire.MsgTx, string) error
// UpdateLabel updates the label that a transaction has in our wallet,
// overwriting any existing labels.
UpdateLabel func(chainhash.Hash, string) error
// FeeEstimator calculates appropriate fee rates based on historical
// transaction information.
FeeEstimator chainfee.Estimator
@ -576,8 +581,15 @@ func (f *fundingManager) start() error {
channel.FundingOutpoint,
fundingTxBuf.Bytes())
// Set a nil short channel ID at this stage
// because we do not know it until our funding
// tx confirms.
label := labels.MakeLabel(
labels.LabelTypeChannelOpen, nil,
)
err = f.cfg.PublishTransaction(
channel.FundingTxn, "",
channel.FundingTxn, label,
)
if err != nil {
fndgLog.Errorf("Unable to rebroadcast "+
@ -2032,7 +2044,13 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
fndgLog.Infof("Broadcasting funding tx for ChannelPoint(%v): %x",
completeChan.FundingOutpoint, fundingTxBuf.Bytes())
err = f.cfg.PublishTransaction(fundingTx, "")
// Set a nil short channel ID at this stage because we do not
// know it until our funding tx confirms.
label := labels.MakeLabel(
labels.LabelTypeChannelOpen, nil,
)
err = f.cfg.PublishTransaction(fundingTx, label)
if err != nil {
fndgLog.Errorf("Unable to broadcast funding tx %x for "+
"ChannelPoint(%v): %v", fundingTxBuf.Bytes(),
@ -2372,6 +2390,25 @@ func (f *fundingManager) handleFundingConfirmation(
fndgLog.Errorf("unable to report short chan id: %v", err)
}
// If we opened the channel, and lnd's wallet published our funding tx
// (which is not the case for some channels) then we update our
// transaction label with our short channel ID, which is known now that
// our funding transaction has confirmed. We do not label transactions
// we did not publish, because our wallet has no knowledge of them.
if completeChan.IsInitiator && completeChan.ChanType.HasFundingTx() {
shortChanID := completeChan.ShortChanID()
label := labels.MakeLabel(
labels.LabelTypeChannelOpen, &shortChanID,
)
err = f.cfg.UpdateLabel(
completeChan.FundingOutpoint.Hash, label,
)
if err != nil {
fndgLog.Errorf("unable to update label: %v", err)
}
}
// Close the discoverySignal channel, indicating to a separate
// goroutine that the channel now is marked as open in the database
// and that it is acceptable to process funding locked messages

@ -421,6 +421,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
publTxChan <- txn
return nil
},
UpdateLabel: func(chainhash.Hash, string) error {
return nil
},
ZombieSweeperInterval: 1 * time.Hour,
ReservationTimeout: 1 * time.Nanosecond,
MaxPendingChannels: lncfg.DefaultMaxPendingChannels,
@ -524,6 +527,9 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
publishChan <- txn
return nil
},
UpdateLabel: func(chainhash.Hash, string) error {
return nil
},
ZombieSweeperInterval: oldCfg.ZombieSweeperInterval,
ReservationTimeout: oldCfg.ReservationTimeout,
OpenChannelPredicate: chainedAcceptor,

@ -1,12 +1,24 @@
// Package labels contains labels used to label transactions broadcast by lnd.
// These labels are used across packages, so they are declared in a separate
// package to avoid dependency issues.
//
// Labels for transactions broadcast by lnd have two set fields followed by an
// optional set labelled data values, all separated by colons.
// - Label version: an integer that indicates the version lnd used
// - Label type: the type of transaction we are labelling
// - {field name}-{value}: a named field followed by its value, these items are
// optional, and there may be more than field present.
//
// For version 0 we have the following optional data fields defined:
// - shortchanid: the short channel ID that a transaction is associated with,
// with its value set to the uint64 short channel id.
package labels
import (
"fmt"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/lnwire"
)
// External labels a transaction as user initiated via the api. This
@ -31,3 +43,50 @@ func ValidateAPI(label string) (string, error) {
return label, nil
}
// LabelVersion versions our labels so they can be easily update to contain
// new data while still easily string matched.
type LabelVersion uint8
// LabelVersionZero is the label version for labels that contain label type and
// channel ID (where available).
const LabelVersionZero LabelVersion = iota
// LabelType indicates the type of label we are creating. It is a string rather
// than an int for easy string matching and human-readability.
type LabelType string
const (
// LabelTypeChannelOpen is used to label channel opens.
LabelTypeChannelOpen LabelType = "openchannel"
// LabelTypeChannelClose is used to label channel closes.
LabelTypeChannelClose LabelType = "closechannel"
// LabelTypeJusticeTransaction is used to label justice transactions.
LabelTypeJusticeTransaction LabelType = "justicetx"
// LabelTypeSweepTransaction is used to label sweeps.
LabelTypeSweepTransaction LabelType = "sweep"
)
// LabelField is used to tag a value within a label.
type LabelField string
const (
// ShortChanID is used to tag short channel id values in our labels.
ShortChanID LabelField = "shortchanid"
)
// MakeLabel creates a label with the provided type and short channel id. If
// our short channel ID is not known, we simply return version:label_type. If
// we do have a short channel ID set, the label will also contain its value:
// shortchanid-{int64 chan ID}.
func MakeLabel(labelType LabelType, channelID *lnwire.ShortChannelID) string {
if channelID == nil {
return fmt.Sprintf("%v:%v", LabelVersionZero, labelType)
}
return fmt.Sprintf("%v:%v:%v-%v", LabelVersionZero, labelType,
ShortChanID, channelID.ToUint64())
}

@ -1204,9 +1204,14 @@ func (n *NetworkHarness) WaitForChannelClose(ctx context.Context,
}
// AssertChannelExists asserts that an active channel identified by the
// specified channel point exists from the point-of-view of the node.
// specified channel point exists from the point-of-view of the node. It takes
// an optional set of check functions which can be used to make further
// assertions using channel's values. These functions are responsible for
// failing the test themselves if they do not pass.
// nolint: interfacer
func (n *NetworkHarness) AssertChannelExists(ctx context.Context,
node *HarnessNode, chanPoint *wire.OutPoint) error {
node *HarnessNode, chanPoint *wire.OutPoint,
checks ...func(*lnrpc.Channel)) error {
req := &lnrpc.ListChannelsRequest{}
@ -1218,12 +1223,20 @@ func (n *NetworkHarness) AssertChannelExists(ctx context.Context,
for _, channel := range resp.Channels {
if channel.ChannelPoint == chanPoint.String() {
if channel.Active {
return nil
// First check whether our channel is active,
// failing early if it is not.
if !channel.Active {
return fmt.Errorf("channel %s inactive",
chanPoint)
}
return fmt.Errorf("channel %s inactive",
chanPoint)
// Apply any additional checks that we would
// like to verify.
for _, check := range checks {
check(channel)
}
return nil
}
}

@ -35,6 +35,7 @@ import (
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
@ -2905,6 +2906,7 @@ func testChannelFundingPersistence(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("unable to convert funding txid into chainhash.Hash:"+
" %v", err)
}
fundingTxStr := fundingTxID.String()
// Mine a block, then wait for Alice's node to notify us that the
// channel has been opened. The funding transaction should be found
@ -2912,6 +2914,10 @@ func testChannelFundingPersistence(net *lntest.NetworkHarness, t *harnessTest) {
block := mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, fundingTxID)
// Get the height that our transaction confirmed at.
_, height, err := net.Miner.Node.GetBestBlock()
require.NoError(t.t, err, "could not get best block")
// Restart both nodes to test that the appropriate state has been
// persisted and that both nodes recover gracefully.
if err := net.RestartNode(net.Alice, nil); err != nil {
@ -2934,6 +2940,16 @@ func testChannelFundingPersistence(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("unable to mine blocks: %v", err)
}
// Assert that our wallet has our opening transaction with a label
// that does not have a channel ID set yet, because we have not
// reached our required confirmations.
tx := findTxAtHeight(ctxt, t, height, fundingTxStr, net, net.Alice)
// At this stage, we expect the transaction to be labelled, but not with
// our channel ID because our transaction has not yet confirmed.
label := labels.MakeLabel(labels.LabelTypeChannelOpen, nil)
require.Equal(t.t, label, tx.Label, "open channel label wrong")
// Both nodes should still show a single channel as pending.
time.Sleep(time.Second * 1)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
@ -2957,9 +2973,27 @@ func testChannelFundingPersistence(net *lntest.NetworkHarness, t *harnessTest) {
Index: pendingUpdate.OutputIndex,
}
// Re-lookup our transaction in the block that it confirmed in.
tx = findTxAtHeight(ctxt, t, height, fundingTxStr, net, net.Alice)
// Create an additional check for our channel assertion that will
// check that our label is as expected.
check := func(channel *lnrpc.Channel) {
shortChanID := lnwire.NewShortChanIDFromInt(
channel.ChanId,
)
label := labels.MakeLabel(
labels.LabelTypeChannelOpen, &shortChanID,
)
require.Equal(t.t, label, tx.Label,
"open channel label not updated")
}
// Check both nodes to ensure that the channel is ready for operation.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
if err := net.AssertChannelExists(ctxt, net.Alice, &outPoint); err != nil {
err = net.AssertChannelExists(ctxt, net.Alice, &outPoint, check)
if err != nil {
t.Fatalf("unable to assert channel existence: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
@ -2980,6 +3014,30 @@ func testChannelFundingPersistence(net *lntest.NetworkHarness, t *harnessTest) {
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
}
// findTxAtHeight gets all of the transactions that a node's wallet has a record
// of at the target height, and finds and returns the tx with the target txid,
// failing if it is not found.
func findTxAtHeight(ctx context.Context, t *harnessTest, height int32,
target string, net *lntest.NetworkHarness,
node *lntest.HarnessNode) *lnrpc.Transaction {
txns, err := node.LightningClient.GetTransactions(
ctx, &lnrpc.GetTransactionsRequest{
StartHeight: height,
EndHeight: height,
},
)
require.NoError(t.t, err, "could not get transactions")
for _, tx := range txns.Transactions {
if tx.TxHash == target {
return tx
}
}
return nil
}
// testChannelBalance creates a new channel between Alice and Bob, then
// checks channel balance to be equal amount specified while creation of channel.
func testChannelBalance(net *lntest.NetworkHarness, t *harnessTest) {

@ -8,6 +8,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
@ -551,7 +552,14 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
return spew.Sdump(closeTx)
}),
)
if err := c.cfg.BroadcastTx(closeTx, ""); err != nil {
// Create a close channel label.
chanID := c.cfg.Channel.ShortChanID()
closeLabel := labels.MakeLabel(
labels.LabelTypeChannelClose, &chanID,
)
if err := c.cfg.BroadcastTx(closeTx, closeLabel); err != nil {
return nil, false, err
}

@ -971,8 +971,11 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
IDKey: nodeKeyECDH.PubKey(),
Wallet: cc.wallet,
PublishTransaction: cc.wallet.PublishTransaction,
Notifier: cc.chainNotifier,
FeeEstimator: cc.feeEstimator,
UpdateLabel: func(hash chainhash.Hash, label string) error {
return cc.wallet.LabelTransaction(hash, label, true)
},
Notifier: cc.chainNotifier,
FeeEstimator: cc.feeEstimator,
SignMessage: func(pubKey *btcec.PublicKey,
msg []byte) (input.Signature, error) {

@ -11,10 +11,10 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
)
@ -867,7 +867,8 @@ func (u *utxoNursery) sweepCribOutput(classHeight uint32, baby *babyOutput) erro
// We'll now broadcast the HTLC transaction, then wait for it to be
// confirmed before transitioning it to kindergarten.
err := u.cfg.PublishTransaction(baby.timeoutTx, "")
label := labels.MakeLabel(labels.LabelTypeSweepTransaction, nil)
err := u.cfg.PublishTransaction(baby.timeoutTx, label)
if err != nil && err != lnwallet.ErrDoubleSpend {
utxnLog.Errorf("Unable to broadcast baby tx: "+
"%v, %v", err, spew.Sdump(baby.timeoutTx))

@ -2,6 +2,7 @@ package lookout
import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/labels"
)
// PunisherConfig houses the resources required by the Punisher.
@ -42,7 +43,8 @@ func (p *BreachPunisher) Punish(desc *JusticeDescriptor, quit <-chan struct{}) e
log.Infof("Publishing justice transaction for client=%s with txid=%s",
desc.SessionInfo.ID, justiceTxn.TxHash())
err = p.cfg.PublishTx(justiceTxn, "")
label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil)
err = p.cfg.PublishTx(justiceTxn, label)
if err != nil {
log.Errorf("Unable to publish justice txn for client=%s"+
"with breach-txid=%s: %v",