github.com/decred/dcrlnd@v0.7.6/htlcswitch/circuit_map_test.go (about)

     1  package htlcswitch_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"testing"
     8  
     9  	"github.com/decred/dcrd/dcrutil/v4"
    10  	"github.com/decred/dcrd/wire"
    11  	"github.com/decred/dcrlnd/channeldb"
    12  	"github.com/decred/dcrlnd/htlcswitch"
    13  	"github.com/decred/dcrlnd/kvdb"
    14  	"github.com/decred/dcrlnd/lnwire"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  var (
    19  	// closedChannelBucket stores summarization information concerning
    20  	// previously open, but now closed channels.
    21  	closedChannelBucket = []byte("closed-chan-bucket")
    22  )
    23  
    24  // TestCircuitMapCleanClosedChannels checks that the circuits and keystones are
    25  // deleted for closed channels upon restart.
    26  func TestCircuitMapCleanClosedChannels(t *testing.T) {
    27  	t.Parallel()
    28  
    29  	var (
    30  		// chanID0 is a zero value channel ID indicating a locally
    31  		// initiated payment.
    32  		chanID0 = lnwire.NewShortChanIDFromInt(uint64(0))
    33  		chanID1 = lnwire.NewShortChanIDFromInt(uint64(1))
    34  		chanID2 = lnwire.NewShortChanIDFromInt(uint64(2))
    35  
    36  		inKey00  = htlcswitch.CircuitKey{ChanID: chanID0, HtlcID: 0}
    37  		inKey10  = htlcswitch.CircuitKey{ChanID: chanID1, HtlcID: 0}
    38  		inKey11  = htlcswitch.CircuitKey{ChanID: chanID1, HtlcID: 1}
    39  		inKey20  = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 0}
    40  		inKey21  = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 1}
    41  		inKey22  = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 2}
    42  		outKey00 = htlcswitch.CircuitKey{ChanID: chanID0, HtlcID: 0}
    43  		outKey10 = htlcswitch.CircuitKey{ChanID: chanID1, HtlcID: 0}
    44  		outKey11 = htlcswitch.CircuitKey{ChanID: chanID1, HtlcID: 1}
    45  		outKey20 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 0}
    46  		outKey21 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 1}
    47  		outKey22 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 2}
    48  	)
    49  
    50  	type closeChannelParams struct {
    51  		chanID    lnwire.ShortChannelID
    52  		isPending bool
    53  	}
    54  
    55  	testParams := []struct {
    56  		name string
    57  
    58  		// keystones is used to create and open circuits. A keystone is
    59  		// a pair of circuit keys, inKey and outKey, with the outKey
    60  		// optionally being empty. If a keystone with an outKey is used,
    61  		// a circuit will be created and opened, thus creating a circuit
    62  		// and a keystone in the DB. Otherwise, only the circuit is
    63  		// created.
    64  		keystones []htlcswitch.Keystone
    65  
    66  		chanParams []closeChannelParams
    67  		deleted    []htlcswitch.Keystone
    68  		untouched  []htlcswitch.Keystone
    69  	}{
    70  		{
    71  			name: "no deletion if there are no closed channels",
    72  			keystones: []htlcswitch.Keystone{
    73  				// Creates a circuit and a keystone
    74  				{InKey: inKey10, OutKey: outKey10},
    75  			},
    76  			untouched: []htlcswitch.Keystone{
    77  				{InKey: inKey10, OutKey: outKey10},
    78  			},
    79  		},
    80  		{
    81  			name: "no deletion if channel is pending close",
    82  			chanParams: []closeChannelParams{
    83  				// Creates a pending close channel.
    84  				{chanID: chanID1, isPending: true},
    85  			},
    86  			keystones: []htlcswitch.Keystone{
    87  				// Creates a circuit and a keystone
    88  				{InKey: inKey10, OutKey: outKey10},
    89  			},
    90  			untouched: []htlcswitch.Keystone{
    91  				{InKey: inKey10, OutKey: outKey10},
    92  			},
    93  		},
    94  		{
    95  			name: "no deletion if the chanID is zero value",
    96  			chanParams: []closeChannelParams{
    97  				// Creates a close channel with chanID0.
    98  				{chanID: chanID0, isPending: false},
    99  			},
   100  			keystones: []htlcswitch.Keystone{
   101  				// Creates a circuit and a keystone
   102  				{InKey: inKey00, OutKey: outKey00},
   103  			},
   104  			untouched: []htlcswitch.Keystone{
   105  				{InKey: inKey00, OutKey: outKey00},
   106  			},
   107  		},
   108  		{
   109  			name: "delete half circuits on inKey match",
   110  			chanParams: []closeChannelParams{
   111  				// Creates a close channel with chanID1.
   112  				{chanID: chanID1, isPending: false},
   113  			},
   114  			keystones: []htlcswitch.Keystone{
   115  				// Creates a circuit, no keystone created
   116  				{InKey: inKey10},
   117  				// Creates a circuit, no keystone created
   118  				{InKey: inKey11},
   119  				// Creates a circuit and a keystone
   120  				{InKey: inKey20, OutKey: outKey20},
   121  			},
   122  			deleted: []htlcswitch.Keystone{
   123  				{InKey: inKey00}, {InKey: inKey11},
   124  			},
   125  			untouched: []htlcswitch.Keystone{
   126  				{InKey: inKey20, OutKey: outKey20},
   127  			},
   128  		},
   129  		{
   130  			name: "delete half circuits on outKey match",
   131  			chanParams: []closeChannelParams{
   132  				// Creates a close channel with chanID1.
   133  				{chanID: chanID1, isPending: false},
   134  			},
   135  			keystones: []htlcswitch.Keystone{
   136  				// Creates a circuit and a keystone
   137  				{InKey: inKey20, OutKey: outKey10},
   138  				// Creates a circuit and a keystone
   139  				{InKey: inKey21, OutKey: outKey11},
   140  				// Creates a circuit and a keystone
   141  				{InKey: inKey22, OutKey: outKey21},
   142  			},
   143  			deleted: []htlcswitch.Keystone{
   144  				{InKey: inKey20, OutKey: outKey10},
   145  				{InKey: inKey21, OutKey: outKey11},
   146  			},
   147  			untouched: []htlcswitch.Keystone{
   148  				{InKey: inKey22, OutKey: outKey21},
   149  			},
   150  		},
   151  		{
   152  			name: "delete full circuits on inKey match",
   153  			chanParams: []closeChannelParams{
   154  				// Creates a close channel with chanID1.
   155  				{chanID: chanID1, isPending: false},
   156  			},
   157  			keystones: []htlcswitch.Keystone{
   158  				// Creates a circuit and a keystone
   159  				{InKey: inKey10, OutKey: outKey20},
   160  				// Creates a circuit and a keystone
   161  				{InKey: inKey11, OutKey: outKey21},
   162  				// Creates a circuit and a keystone
   163  				{InKey: inKey20, OutKey: outKey22},
   164  			},
   165  			deleted: []htlcswitch.Keystone{
   166  				{InKey: inKey10, OutKey: outKey20},
   167  				{InKey: inKey11, OutKey: outKey21},
   168  			},
   169  			untouched: []htlcswitch.Keystone{
   170  				{InKey: inKey20, OutKey: outKey22},
   171  			},
   172  		},
   173  		{
   174  			name: "delete full circuits on outKey match",
   175  			chanParams: []closeChannelParams{
   176  				// Creates a close channel with chanID1.
   177  				{chanID: chanID1, isPending: false},
   178  			},
   179  			keystones: []htlcswitch.Keystone{
   180  				// Creates a circuit and a keystone
   181  				{InKey: inKey20, OutKey: outKey10},
   182  				// Creates a circuit and a keystone
   183  				{InKey: inKey21, OutKey: outKey11},
   184  				// Creates a circuit and a keystone
   185  				{InKey: inKey22, OutKey: outKey20},
   186  			},
   187  			deleted: []htlcswitch.Keystone{
   188  				{InKey: inKey20, OutKey: outKey10},
   189  				{InKey: inKey21, OutKey: outKey11},
   190  			},
   191  			untouched: []htlcswitch.Keystone{
   192  				{InKey: inKey22, OutKey: outKey20},
   193  			},
   194  		},
   195  		{
   196  			name: "delete all circuits",
   197  			chanParams: []closeChannelParams{
   198  				// Creates a close channel with chanID1.
   199  				{chanID: chanID1, isPending: false},
   200  				// Creates a close channel with chanID2.
   201  				{chanID: chanID2, isPending: false},
   202  			},
   203  			keystones: []htlcswitch.Keystone{
   204  				// Creates a circuit and a keystone
   205  				{InKey: inKey20, OutKey: outKey10},
   206  				// Creates a circuit and a keystone
   207  				{InKey: inKey21, OutKey: outKey11},
   208  				// Creates a circuit and a keystone
   209  				{InKey: inKey22, OutKey: outKey20},
   210  			},
   211  			deleted: []htlcswitch.Keystone{
   212  				{InKey: inKey20, OutKey: outKey10},
   213  				{InKey: inKey21, OutKey: outKey11},
   214  				{InKey: inKey22, OutKey: outKey20},
   215  			},
   216  		},
   217  	}
   218  
   219  	for _, tt := range testParams {
   220  		test := tt
   221  
   222  		t.Run(test.name, func(t *testing.T) {
   223  			cfg, circuitMap := newCircuitMap(t)
   224  
   225  			// create test circuits
   226  			for _, ks := range test.keystones {
   227  				err := createTestCircuit(ks, circuitMap)
   228  				require.NoError(
   229  					t, err,
   230  					"failed to create test circuit",
   231  				)
   232  			}
   233  
   234  			// create close channels
   235  			err := kvdb.Update(cfg.DB, func(tx kvdb.RwTx) error {
   236  				for _, channel := range test.chanParams {
   237  					if err := createTestCloseChannelSummery(
   238  						tx, channel.isPending,
   239  						channel.chanID,
   240  					); err != nil {
   241  						return err
   242  					}
   243  				}
   244  				return nil
   245  			}, func() {})
   246  
   247  			require.NoError(
   248  				t, err,
   249  				"failed to create close channel summery",
   250  			)
   251  
   252  			// Now, restart the circuit map, and check that the
   253  			// circuits and keystones of closed channels are
   254  			// deleted in DB.
   255  			_, circuitMap = restartCircuitMap(t, cfg)
   256  
   257  			// Check that items are deleted. LookupCircuit and
   258  			// LookupOpenCircuit will check the cached circuits,
   259  			// which are loaded on restart from the DB.
   260  			for _, ks := range test.deleted {
   261  				assertKeystoneDeleted(t, circuitMap, ks)
   262  			}
   263  
   264  			// We also check we are not deleting wanted circuits.
   265  			for _, ks := range test.untouched {
   266  				assertKeystoneNotDeleted(t, circuitMap, ks)
   267  			}
   268  
   269  		})
   270  	}
   271  
   272  }
   273  
   274  // createTestCircuit creates a circuit for testing with its incoming key being
   275  // the keystone's InKey. If the keystone has an OutKey, the circuit will be
   276  // opened, which causes a Keystone to be created in DB.
   277  func createTestCircuit(ks htlcswitch.Keystone, cm htlcswitch.CircuitMap) error {
   278  	circuit := &htlcswitch.PaymentCircuit{
   279  		Incoming:       ks.InKey,
   280  		ErrorEncrypter: testExtracter,
   281  	}
   282  
   283  	// First we will try to add an new circuit to the circuit map, this
   284  	// should succeed.
   285  	_, err := cm.CommitCircuits(circuit)
   286  	if err != nil {
   287  		return fmt.Errorf("failed to commit circuits: %v", err)
   288  	}
   289  
   290  	// If the keystone has no outgoing key, we won't open it.
   291  	if ks.OutKey == htlcswitch.EmptyCircuitKey {
   292  		return nil
   293  	}
   294  
   295  	// Open the circuit, implicitly creates a keystone on disk.
   296  	err = cm.OpenCircuits(ks)
   297  	if err != nil {
   298  		return fmt.Errorf("failed to open circuits: %v", err)
   299  	}
   300  
   301  	return nil
   302  }
   303  
   304  // assertKeystoneDeleted checks that a given keystone is deleted from the
   305  // circuit map.
   306  func assertKeystoneDeleted(t *testing.T,
   307  	cm htlcswitch.CircuitLookup, ks htlcswitch.Keystone) {
   308  
   309  	c := cm.LookupCircuit(ks.InKey)
   310  	require.Nil(t, c, "no circuit should be found using InKey")
   311  
   312  	if ks.OutKey != htlcswitch.EmptyCircuitKey {
   313  		c = cm.LookupOpenCircuit(ks.OutKey)
   314  		require.Nil(t, c, "no circuit should be found using OutKey")
   315  	}
   316  }
   317  
   318  // assertKeystoneDeleted checks that a given keystone is not deleted from the
   319  // circuit map.
   320  func assertKeystoneNotDeleted(t *testing.T,
   321  	cm htlcswitch.CircuitLookup, ks htlcswitch.Keystone) {
   322  
   323  	c := cm.LookupCircuit(ks.InKey)
   324  	require.NotNil(t, c, "expecting circuit found using InKey")
   325  
   326  	if ks.OutKey != htlcswitch.EmptyCircuitKey {
   327  		c = cm.LookupOpenCircuit(ks.OutKey)
   328  		require.NotNil(t, c, "expecting circuit found using OutKey")
   329  	}
   330  }
   331  
   332  // createTestCloseChannelSummery creates a CloseChannelSummery for testing.
   333  func createTestCloseChannelSummery(tx kvdb.RwTx, isPending bool,
   334  	chanID lnwire.ShortChannelID) error {
   335  
   336  	closedChanBucket, err := tx.CreateTopLevelBucket(closedChannelBucket)
   337  	if err != nil {
   338  		return err
   339  	}
   340  	outputPoint := wire.OutPoint{Hash: hash1, Index: 1}
   341  
   342  	ccs := &channeldb.ChannelCloseSummary{
   343  		ChanPoint:      outputPoint,
   344  		ShortChanID:    chanID,
   345  		ChainHash:      hash1,
   346  		ClosingTXID:    hash2,
   347  		CloseHeight:    100,
   348  		RemotePub:      testEphemeralKey,
   349  		Capacity:       dcrutil.Amount(10000),
   350  		SettledBalance: dcrutil.Amount(50000),
   351  		CloseType:      channeldb.RemoteForceClose,
   352  		IsPending:      isPending,
   353  	}
   354  	var b bytes.Buffer
   355  	if err := serializeChannelCloseSummary(&b, ccs); err != nil {
   356  		return err
   357  	}
   358  
   359  	var chanPointBuf bytes.Buffer
   360  	if err := lnwire.WriteOutPoint(&chanPointBuf, outputPoint); err != nil {
   361  		return err
   362  	}
   363  
   364  	return closedChanBucket.Put(chanPointBuf.Bytes(), b.Bytes())
   365  }
   366  
   367  func serializeChannelCloseSummary(
   368  	w io.Writer,
   369  	cs *channeldb.ChannelCloseSummary) error {
   370  
   371  	err := channeldb.WriteElements(
   372  		w,
   373  		cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
   374  		cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
   375  		cs.TimeLockedBalance, cs.CloseType, cs.IsPending,
   376  	)
   377  	if err != nil {
   378  		return err
   379  	}
   380  
   381  	// If this is a close channel summary created before the addition of
   382  	// the new fields, then we can exit here.
   383  	if cs.RemoteCurrentRevocation == nil {
   384  		return channeldb.WriteElements(w, false)
   385  	}
   386  
   387  	return nil
   388  }