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 }