github.com/decred/dcrlnd@v0.7.6/contractcourt/chain_arbitrator_test.go (about)

     1  package contractcourt
     2  
     3  import (
     4  	"io/ioutil"
     5  	"net"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/decred/dcrd/chaincfg/chainhash"
    10  	"github.com/decred/dcrd/chaincfg/v3"
    11  	"github.com/decred/dcrd/wire"
    12  	"github.com/decred/dcrlnd/chainntnfs"
    13  	"github.com/decred/dcrlnd/channeldb"
    14  	"github.com/decred/dcrlnd/clock"
    15  	"github.com/decred/dcrlnd/lntest/mock"
    16  	"github.com/decred/dcrlnd/lnwallet"
    17  )
    18  
    19  // TestChainArbitratorRepulishCloses tests that the chain arbitrator will
    20  // republish closing transactions for channels marked CommitementBroadcast or
    21  // CoopBroadcast in the database at startup.
    22  func TestChainArbitratorRepublishCloses(t *testing.T) {
    23  	t.Parallel()
    24  
    25  	tempPath, err := ioutil.TempDir("", "testdb")
    26  	if err != nil {
    27  		t.Fatal(err)
    28  	}
    29  	defer os.RemoveAll(tempPath)
    30  
    31  	db, err := channeldb.Open(tempPath)
    32  	if err != nil {
    33  		t.Fatal(err)
    34  	}
    35  	defer db.Close()
    36  
    37  	// Create 10 test channels and sync them to the database.
    38  	const numChans = 10
    39  	var channels []*channeldb.OpenChannel
    40  	for i := 0; i < numChans; i++ {
    41  		lChannel, _, cleanup, err := lnwallet.CreateTestChannels(
    42  			channeldb.SingleFunderTweaklessBit,
    43  		)
    44  		if err != nil {
    45  			t.Fatal(err)
    46  		}
    47  		defer cleanup()
    48  
    49  		channel := lChannel.State()
    50  
    51  		// We manually set the db here to make sure all channels are
    52  		// synced to the same db.
    53  		channel.Db = db.ChannelStateDB()
    54  
    55  		addr := &net.TCPAddr{
    56  			IP:   net.ParseIP("127.0.0.1"),
    57  			Port: 18556,
    58  		}
    59  		if err := channel.SyncPending(addr, 101); err != nil {
    60  			t.Fatal(err)
    61  		}
    62  
    63  		channels = append(channels, channel)
    64  	}
    65  
    66  	// Mark half of the channels as commitment broadcasted.
    67  	for i := 0; i < numChans/2; i++ {
    68  		closeTx := channels[i].FundingTxn.Copy()
    69  		closeTx.TxIn[0].PreviousOutPoint = channels[i].FundingOutpoint
    70  		err := channels[i].MarkCommitmentBroadcasted(closeTx, true)
    71  		if err != nil {
    72  			t.Fatal(err)
    73  		}
    74  
    75  		err = channels[i].MarkCoopBroadcasted(closeTx, true)
    76  		if err != nil {
    77  			t.Fatal(err)
    78  		}
    79  	}
    80  
    81  	// We keep track of the transactions published by the ChainArbitrator
    82  	// at startup.
    83  	published := make(map[chainhash.Hash]int)
    84  
    85  	chainArbCfg := ChainArbitratorConfig{
    86  		NetParams: chaincfg.RegNetParams(),
    87  		ChainIO:   &mock.ChainIO{},
    88  		Notifier: &mock.ChainNotifier{
    89  			SpendChan: make(chan *chainntnfs.SpendDetail),
    90  			EpochChan: make(chan *chainntnfs.BlockEpoch),
    91  			ConfChan:  make(chan *chainntnfs.TxConfirmation),
    92  		},
    93  		PublishTx: func(tx *wire.MsgTx, _ string) error {
    94  			published[tx.TxHash()]++
    95  			return nil
    96  		},
    97  		Clock: clock.NewDefaultClock(),
    98  	}
    99  	chainArb := NewChainArbitrator(
   100  		chainArbCfg, db,
   101  	)
   102  
   103  	if err := chainArb.Start(); err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	defer func() {
   107  		if err := chainArb.Stop(); err != nil {
   108  			t.Fatal(err)
   109  		}
   110  	}()
   111  
   112  	// Half of the channels should have had their closing tx re-published.
   113  	if len(published) != numChans/2 {
   114  		t.Fatalf("expected %d re-published transactions, got %d",
   115  			numChans/2, len(published))
   116  	}
   117  
   118  	// And make sure the published transactions are correct, and unique.
   119  	for i := 0; i < numChans/2; i++ {
   120  		closeTx := channels[i].FundingTxn.Copy()
   121  		closeTx.TxIn[0].PreviousOutPoint = channels[i].FundingOutpoint
   122  
   123  		count, ok := published[closeTx.TxHash()]
   124  		if !ok {
   125  			t.Fatalf("closing tx not re-published")
   126  		}
   127  
   128  		// We expect one coop close and one force close.
   129  		if count != 2 {
   130  			t.Fatalf("expected 2 closing txns, only got %d", count)
   131  		}
   132  
   133  		delete(published, closeTx.TxHash())
   134  	}
   135  
   136  	if len(published) != 0 {
   137  		t.Fatalf("unexpected tx published")
   138  	}
   139  }
   140  
   141  // TestResolveContract tests that if we have an active channel being watched by
   142  // the chain arb, then a call to ResolveContract will mark the channel as fully
   143  // closed in the database, and also clean up all arbitrator state.
   144  func TestResolveContract(t *testing.T) {
   145  	t.Parallel()
   146  
   147  	// To start with, we'll create a new temp DB for the duration of this
   148  	// test.
   149  	tempPath, err := ioutil.TempDir("", "testdb")
   150  	if err != nil {
   151  		t.Fatalf("unable to make temp dir: %v", err)
   152  	}
   153  	defer os.RemoveAll(tempPath)
   154  	db, err := channeldb.Open(tempPath)
   155  	if err != nil {
   156  		t.Fatalf("unable to open db: %v", err)
   157  	}
   158  	defer db.Close()
   159  
   160  	// With the DB created, we'll make a new channel, and mark it as
   161  	// pending open within the database.
   162  	newChannel, _, cleanup, err := lnwallet.CreateTestChannels(
   163  		channeldb.SingleFunderTweaklessBit,
   164  	)
   165  	if err != nil {
   166  		t.Fatalf("unable to make new test channel: %v", err)
   167  	}
   168  	defer cleanup()
   169  	channel := newChannel.State()
   170  	channel.Db = db.ChannelStateDB()
   171  	addr := &net.TCPAddr{
   172  		IP:   net.ParseIP("127.0.0.1"),
   173  		Port: 18556,
   174  	}
   175  	if err := channel.SyncPending(addr, 101); err != nil {
   176  		t.Fatalf("unable to write channel to db: %v", err)
   177  	}
   178  
   179  	// With the channel inserted into the database, we'll now create a new
   180  	// chain arbitrator that should pick up these new channels and launch
   181  	// resolver for them.
   182  	chainArbCfg := ChainArbitratorConfig{
   183  		ChainIO: &mock.ChainIO{},
   184  		Notifier: &mock.ChainNotifier{
   185  			SpendChan: make(chan *chainntnfs.SpendDetail),
   186  			EpochChan: make(chan *chainntnfs.BlockEpoch),
   187  			ConfChan:  make(chan *chainntnfs.TxConfirmation),
   188  		},
   189  		PublishTx: func(tx *wire.MsgTx, _ string) error {
   190  			return nil
   191  		},
   192  		Clock: clock.NewDefaultClock(),
   193  	}
   194  	chainArb := NewChainArbitrator(
   195  		chainArbCfg, db,
   196  	)
   197  	if err := chainArb.Start(); err != nil {
   198  		t.Fatal(err)
   199  	}
   200  	defer func() {
   201  		if err := chainArb.Stop(); err != nil {
   202  			t.Fatal(err)
   203  		}
   204  	}()
   205  
   206  	channelArb := chainArb.activeChannels[channel.FundingOutpoint]
   207  
   208  	// While the resolver are active, we'll now remove the channel from the
   209  	// database (mark is as closed).
   210  	err = db.ChannelStateDB().AbandonChannel(&channel.FundingOutpoint, 4)
   211  	if err != nil {
   212  		t.Fatalf("unable to remove channel: %v", err)
   213  	}
   214  
   215  	// With the channel removed, we'll now manually call ResolveContract.
   216  	// This stimulates needing to remove a channel from the chain arb due
   217  	// to any possible external consistency issues.
   218  	err = chainArb.ResolveContract(channel.FundingOutpoint)
   219  	if err != nil {
   220  		t.Fatalf("unable to resolve contract: %v", err)
   221  	}
   222  
   223  	// The shouldn't be an active chain watcher or channel arb for this
   224  	// channel.
   225  	if len(chainArb.activeChannels) != 0 {
   226  		t.Fatalf("expected zero active channels, instead have %v",
   227  			len(chainArb.activeChannels))
   228  	}
   229  	if len(chainArb.activeWatchers) != 0 {
   230  		t.Fatalf("expected zero active watchers, instead have %v",
   231  			len(chainArb.activeWatchers))
   232  	}
   233  
   234  	// At this point, the channel's arbitrator log should also be empty as
   235  	// well.
   236  	_, err = channelArb.log.FetchContractResolutions()
   237  	if err != errScopeBucketNoExist {
   238  		t.Fatalf("channel arb log state should have been "+
   239  			"removed: %v", err)
   240  	}
   241  
   242  	// If we attempt to call this method again, then we should get a nil
   243  	// error, as there is no more state to be cleaned up.
   244  	err = chainArb.ResolveContract(channel.FundingOutpoint)
   245  	if err != nil {
   246  		t.Fatalf("second resolve call shouldn't fail: %v", err)
   247  	}
   248  }