Пример #1
0
// logEntryToHtlcPkt converts a particular Lightning Commitment Protocol (LCP)
// log entry the corresponding htlcPacket with src/dest set along with the
// proper wire message. This helepr method is provided in order to aide an
// htlcManager in forwarding packets to the htlcSwitch.
func logEntryToHtlcPkt(chanPoint wire.OutPoint,
	pd *lnwallet.PaymentDescriptor,
	onionPkt *sphinx.ProcessedPacket) (*htlcPacket, error) {

	pkt := &htlcPacket{}

	// TODO(roasbeef): alter after switch to log entry interface
	var msg lnwire.Message
	switch pd.EntryType {
	case lnwallet.Add:
		// TODO(roasbeef): timeout, onion blob, etc
		var b bytes.Buffer
		if err := onionPkt.Packet.Encode(&b); err != nil {
			return nil, err
		}

		msg = &lnwire.HTLCAddRequest{
			Amount:           lnwire.CreditsAmount(pd.Amount),
			RedemptionHashes: [][32]byte{pd.RHash},
			OnionBlob:        b.Bytes(),
		}
	case lnwallet.Settle:
		msg = &lnwire.HTLCSettleRequest{
			RedemptionProofs: [][32]byte{pd.RPreimage},
		}
	}

	pkt.amt = pd.Amount
	pkt.msg = msg

	pkt.srcLink = chanPoint
	pkt.onion = onionPkt

	return pkt, nil
}
Пример #2
0
// SendPayment dispatches a bi-directional streaming RPC for sending payments
// through the Lightning Network. A single RPC invocation creates a persistent
// bi-directional stream allowing clients to rapidly send payments through the
// Lightning Network with a single persistent connection.
func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) error {
	const queryTimeout = time.Duration(time.Second * 10)
	errChan := make(chan error, 1)
	payChan := make(chan *lnrpc.SendRequest)

	// Launch a new goroutine to handle reading new payment requests from
	// the client. This way we can handle errors independently of blocking
	// and waiting for the next payment request to come through.
	go func() {
		for {
			select {
			case <-r.quit:
				errChan <- nil
				return
			default:
				// Receive the next pending payment within the
				// stream sent by the client. If we read the
				// EOF sentinel, then the client has closed the
				// stream, and we can exit normally.
				nextPayment, err := paymentStream.Recv()
				if err == io.EOF {
					errChan <- nil
					return
				} else if err != nil {
					errChan <- err
					return
				}

				payChan <- nextPayment
			}
		}
	}()

	for {
		select {
		case err := <-errChan:
			return err
		case nextPayment := <-payChan:
			// Query the routing table for a potential path to the
			// destination node. If a path is ultimately
			// unavailable, then an error will be returned.
			destNode := hex.EncodeToString(nextPayment.Dest)
			targetVertex := graph.NewID(destNode)
			path, err := r.server.routingMgr.FindPath(targetVertex,
				queryTimeout)
			if err != nil {
				return err
			}
			rpcsLog.Tracef("[sendpayment] selected route: %v", path)

			// If we're in debug HTLC mode, then all outgoing
			// HTLC's will pay to the same debug rHash. Otherwise,
			// we pay to the rHash specified within the RPC
			// request.
			var rHash [32]byte
			if cfg.DebugHTLC {
				rHash = debugHash
			} else {
				copy(rHash[:], nextPayment.PaymentHash)
			}

			// Generate the raw encoded sphinx packet to be
			// included along with the HTLC add message.  We snip
			// off the first hop from the path as within the
			// routing table's star graph, we're always the first
			// hop.
			sphinxPacket, err := generateSphinxPacket(path[1:], rHash[:])
			if err != nil {
				return err
			}

			// Craft an HTLC packet to send to the routing
			// sub-system. The meta-data within this packet will be
			// used to route the payment through the network.
			htlcAdd := &lnwire.HTLCAddRequest{
				Amount:           lnwire.CreditsAmount(nextPayment.Amt),
				RedemptionHashes: [][32]byte{rHash},
				OnionBlob:        sphinxPacket,
			}
			firstHopPub, err := hex.DecodeString(path[1].String())
			if err != nil {
				return err
			}
			destAddr := wire.ShaHash(fastsha256.Sum256(firstHopPub))
			htlcPkt := &htlcPacket{
				dest: destAddr,
				msg:  htlcAdd,
			}

			// TODO(roasbeef): semaphore to limit num outstanding
			// goroutines.
			go func() {
				// Finally, send this next packet to the
				// routing layer in order to complete the next
				// payment.
				// TODO(roasbeef): this should go through the
				// L3 router once multi-hop is in place.
				if err := r.server.htlcSwitch.SendHTLC(htlcPkt); err != nil {
					errChan <- err
					return
				}

				// TODO(roasbeef): proper responses
				resp := &lnrpc.SendResponse{}
				if err := paymentStream.Send(resp); err != nil {
					errChan <- err
					return
				}
			}()
		}
	}

	return nil
}
Пример #3
0
func TestStateUpdatePersistence(t *testing.T) {
	// Create a test channel which will be used for the duration of this
	// unittest. The channel will be funded evenly with Alice having 5 BTC,
	// and Bob having 5 BTC.
	aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
	if err != nil {
		t.Fatalf("unable to create test channels: %v", err)
	}
	defer cleanUp()

	if err := aliceChannel.channelState.FullSync(); err != nil {
		t.Fatalf("unable to sync alice's channel: %v", err)
	}
	if err := bobChannel.channelState.FullSync(); err != nil {
		t.Fatalf("unable to sync bob's channel: %v", err)
	}

	aliceStartingBalance := aliceChannel.channelState.OurBalance
	bobStartingBalance := bobChannel.channelState.OurBalance

	const numHtlcs = 4

	// Alice adds 3 HTLC's to the update log, while Bob adds a single HTLC.
	var alicePreimage [32]byte
	copy(alicePreimage[:], bytes.Repeat([]byte{0xaa}, 32))
	var bobPreimage [32]byte
	copy(bobPreimage[:], bytes.Repeat([]byte{0xbb}, 32))
	for i := 0; i < 3; i++ {
		rHash := fastsha256.Sum256(alicePreimage[:])
		h := &lnwire.HTLCAddRequest{
			RedemptionHashes: [][32]byte{rHash},
			Amount:           lnwire.CreditsAmount(1000),
			Expiry:           uint32(10),
		}

		aliceChannel.AddHTLC(h)
		bobChannel.ReceiveHTLC(h)
	}
	rHash := fastsha256.Sum256(bobPreimage[:])
	bobh := &lnwire.HTLCAddRequest{
		RedemptionHashes: [][32]byte{rHash},
		Amount:           lnwire.CreditsAmount(1000),
		Expiry:           uint32(10),
	}
	bobChannel.AddHTLC(bobh)
	aliceChannel.ReceiveHTLC(bobh)

	// Next, Alice initiates a state transition to lock in the above HTLC's.
	if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
		t.Fatalf("unable to lock in HTLC's: %v", err)
	}

	// The balances of both channels should be updated accordingly.
	aliceBalance := aliceChannel.channelState.OurBalance
	expectedAliceBalance := aliceStartingBalance - btcutil.Amount(3000)
	bobBalance := bobChannel.channelState.OurBalance
	expectedBobBalance := bobStartingBalance - btcutil.Amount(1000)
	if aliceBalance != expectedAliceBalance {
		t.Fatalf("expected %v alice balance, got %v", expectedAliceBalance,
			aliceBalance)
	}
	if bobBalance != expectedBobBalance {
		t.Fatalf("expected %v bob balance, got %v", expectedBobBalance,
			bobBalance)
	}

	// The latest commitment from both sides should have all the HTLC's.
	numAliceOutgoing := aliceChannel.localCommitChain.tail().outgoingHTLCs
	numAliceIncoming := aliceChannel.localCommitChain.tail().incomingHTLCs
	if len(numAliceOutgoing) != 3 {
		t.Fatalf("expected %v htlcs, instead got %v", 3, numAliceOutgoing)
	}
	if len(numAliceIncoming) != 1 {
		t.Fatalf("expected %v htlcs, instead got %v", 1, numAliceIncoming)
	}
	numBobOutgoing := bobChannel.localCommitChain.tail().outgoingHTLCs
	numBobIncoming := bobChannel.localCommitChain.tail().incomingHTLCs
	if len(numBobOutgoing) != 1 {
		t.Fatalf("expected %v htlcs, instead got %v", 1, numBobOutgoing)
	}
	if len(numBobIncoming) != 3 {
		t.Fatalf("expected %v htlcs, instead got %v", 3, numBobIncoming)
	}

	// Now fetch both of the channels created above from disk to simulate a
	// node restart with persistence.
	alicePub := aliceChannel.channelState.IdentityPub
	aliceChannels, err := aliceChannel.channelState.Db.FetchOpenChannels(alicePub)
	if err != nil {
		t.Fatalf("unable to fetch channel: %v", err)
	}
	bobPub := bobChannel.channelState.IdentityPub
	bobChannels, err := bobChannel.channelState.Db.FetchOpenChannels(bobPub)
	if err != nil {
		t.Fatalf("unable to fetch channel: %v", err)
	}
	notifier := aliceChannel.channelEvents
	aliceChannelNew, err := NewLightningChannel(aliceChannel.signer, nil, notifier, aliceChannels[0])
	if err != nil {
		t.Fatalf("unable to create new channel: %v", err)
	}
	bobChannelNew, err := NewLightningChannel(bobChannel.signer, nil, notifier, bobChannels[0])
	if err != nil {
		t.Fatalf("unable to create new channel: %v", err)
	}
	if err := initRevocationWindows(aliceChannelNew, bobChannelNew, 3); err != nil {
		t.Fatalf("unable to init revocation windows: %v", err)
	}

	// The state update logs of the new channels and the old channels
	// should now be identical other than the height the HTLC's were added.
	if aliceChannel.ourLogCounter != aliceChannelNew.ourLogCounter {
		t.Fatalf("alice log counter: expected %v, got %v",
			aliceChannel.ourLogCounter, aliceChannelNew.ourLogCounter)
	}
	if aliceChannel.theirLogCounter != aliceChannelNew.theirLogCounter {
		t.Fatalf("alice log counter: expected %v, got %v",
			aliceChannel.theirLogCounter, aliceChannelNew.theirLogCounter)
	}
	if aliceChannel.ourUpdateLog.Len() != aliceChannelNew.ourUpdateLog.Len() {
		t.Fatalf("alice log len: expected %v, got %v",
			aliceChannel.ourUpdateLog.Len(),
			aliceChannelNew.ourUpdateLog.Len())
	}
	if aliceChannel.theirUpdateLog.Len() != aliceChannelNew.theirUpdateLog.Len() {
		t.Fatalf("alice log len: expected %v, got %v",
			aliceChannel.theirUpdateLog.Len(),
			aliceChannelNew.theirUpdateLog.Len())
	}
	if bobChannel.ourLogCounter != bobChannelNew.ourLogCounter {
		t.Fatalf("bob log counter: expected %v, got %v",
			bobChannel.ourLogCounter, bobChannelNew.ourLogCounter)
	}
	if bobChannel.theirLogCounter != bobChannelNew.theirLogCounter {
		t.Fatalf("bob log counter: expected %v, got %v",
			bobChannel.theirLogCounter, bobChannelNew.theirLogCounter)
	}
	if bobChannel.ourUpdateLog.Len() != bobChannelNew.ourUpdateLog.Len() {
		t.Fatalf("bob log len: expected %v, got %v",
			bobChannelNew.ourUpdateLog.Len(), bobChannelNew.ourUpdateLog.Len())
	}
	if bobChannel.theirUpdateLog.Len() != bobChannelNew.theirUpdateLog.Len() {
		t.Fatalf("bob log len: expected %v, got %v",
			bobChannel.theirUpdateLog.Len(), bobChannelNew.theirUpdateLog.Len())
	}

	// Now settle all the HTLC's, then force a state update. The state
	// update should suceed as both sides have identical.
	for i := 0; i < 3; i++ {
		settleIndex, err := bobChannelNew.SettleHTLC(alicePreimage)
		if err != nil {
			t.Fatalf("unable to settle htlc: %v", err)
		}
		err = aliceChannelNew.ReceiveHTLCSettle(alicePreimage, settleIndex)
		if err != nil {
			t.Fatalf("unable to settle htlc: %v", err)
		}
	}
	settleIndex, err := aliceChannelNew.SettleHTLC(bobPreimage)
	if err != nil {
		t.Fatalf("unable to settle htlc: %v", err)
	}
	err = bobChannelNew.ReceiveHTLCSettle(bobPreimage, settleIndex)
	if err != nil {
		t.Fatalf("unable to settle htlc: %v", err)
	}
	if err := forceStateTransition(aliceChannelNew, bobChannelNew); err != nil {
		t.Fatalf("unable to update commitments: %v", err)
	}

	// The balances of both sides should have been updated accordingly.
	aliceBalance = aliceChannelNew.channelState.OurBalance
	expectedAliceBalance = aliceStartingBalance - btcutil.Amount(2000)
	bobBalance = bobChannelNew.channelState.OurBalance
	expectedBobBalance = bobStartingBalance + btcutil.Amount(2000)
	if aliceBalance != expectedAliceBalance {
		t.Fatalf("expected %v alice balance, got %v", expectedAliceBalance,
			aliceBalance)
	}
	if bobBalance != expectedBobBalance {
		t.Fatalf("expected %v bob balance, got %v", expectedBobBalance,
			bobBalance)
	}
}
Пример #4
0
// TestSimpleAddSettleWorkflow tests a simple channel scenario wherein the
// local node (Alice in this case) creates a new outgoing HTLC to bob, commits
// this change, then bob immediately commits a settlement of the HTLC after the
// initial add is fully commited in both commit chains.
// TODO(roasbeef): write higher level framework to exercise various states of
// the state machine
//  * DSL language perhaps?
//  * constructed via input/output files
func TestSimpleAddSettleWorkflow(t *testing.T) {
	// Create a test channel which will be used for the duration of this
	// unittest. The channel will be funded evenly with Alice having 5 BTC,
	// and Bob having 5 BTC.
	aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
	if err != nil {
		t.Fatalf("unable to create test channels: %v", err)
	}
	defer cleanUp()

	// The edge of the revocation window for both sides should be 3 at this
	// point.
	if aliceChannel.revocationWindowEdge != 3 {
		t.Fatalf("alice revocation window not incremented, is %v should be %v",
			aliceChannel.revocationWindowEdge, 3)
	}
	if bobChannel.revocationWindowEdge != 3 {
		t.Fatalf("alice revocation window not incremented, is %v should be %v",
			bobChannel.revocationWindowEdge, 3)
	}

	paymentPreimage := bytes.Repeat([]byte{1}, 32)
	paymentHash := fastsha256.Sum256(paymentPreimage)
	htlc := &lnwire.HTLCAddRequest{
		RedemptionHashes: [][32]byte{paymentHash},
		// TODO(roasbeef): properly switch to credits: (1 msat)
		Amount: lnwire.CreditsAmount(1e8),
		Expiry: uint32(5),
	}

	// First Alice adds the outgoing HTLC to her local channel's state
	// update log. Then Alice sends this wire message over to Bob who also
	// adds this htlc to his local state update log.
	aliceChannel.AddHTLC(htlc)
	bobChannel.ReceiveHTLC(htlc)

	// Next alice commits this change by sending a signature message.
	aliceSig, bobLogIndex, err := aliceChannel.SignNextCommitment()
	if err != nil {
		t.Fatalf("alice unable to sign commitment: %v", err)
	}

	// Bob receives this signature message, then generates a signature for
	// Alice's commitment transaction, and the revocation to his prior
	// commitment transaction.
	if err := bobChannel.ReceiveNewCommitment(aliceSig, bobLogIndex); err != nil {
		t.Fatalf("bob unable to process alice's new commitment: %v", err)
	}
	bobSig, aliceLogIndex, err := bobChannel.SignNextCommitment()
	if err != nil {
		t.Fatalf("bob unable to sign alice's commitment: %v", err)
	}
	bobRevocation, err := bobChannel.RevokeCurrentCommitment()
	if err != nil {
		t.Fatalf("unable to generate bob revocation: %v", err)
	}

	// Alice then processes bob's signature, and generates a revocation for
	// bob.
	if err := aliceChannel.ReceiveNewCommitment(bobSig, aliceLogIndex); err != nil {
		t.Fatalf("alice unable to process bob's new commitment: %v", err)
	}
	// Alice then processes this revocation, sending her own recovation for
	// her prior commitment transaction. Alice shouldn't have any HTLC's to
	// forward since she's sending anoutgoing HTLC.
	if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
		t.Fatalf("alice unable to rocess bob's revocation: %v", err)
	} else if len(htlcs) != 0 {
		t.Fatalf("alice forwards %v htlcs, should forward none: ", len(htlcs))
	}
	aliceRevocation, err := aliceChannel.RevokeCurrentCommitment()
	if err != nil {
		t.Fatalf("unable to revoke alice channel: %v", err)
	}

	// Finally Bob processes Alice's revocation, at this point the new HTLC
	// is fully locked in within both commitment transactions. Bob should
	// also be able to forward an HTLC now that the HTLC has been locked
	// into both commitment transactions.
	if htlcs, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
		t.Fatalf("bob unable to process alive's revocation: %v", err)
	} else if len(htlcs) != 1 {
		t.Fatalf("bob should be able to forward an HTLC, instead can "+
			"forward %v", len(htlcs))
	}

	// At this point, both sides should have the proper balance, and
	// commitment height updated within their local channel state.
	aliceBalance := btcutil.Amount(4 * 1e8)
	bobBalance := btcutil.Amount(5 * 1e8)
	if aliceChannel.channelState.OurBalance != aliceBalance {
		t.Fatalf("alice has incorrect local balance %v vs %v",
			aliceChannel.channelState.OurBalance, aliceBalance)
	}
	if aliceChannel.channelState.TheirBalance != bobBalance {
		t.Fatalf("alice has incorrect remote balance %v vs %v",
			aliceChannel.channelState.TheirBalance, bobBalance)
	}
	if bobChannel.channelState.OurBalance != bobBalance {
		t.Fatalf("bob has incorrect local balance %v vs %v",
			bobChannel.channelState.OurBalance, bobBalance)
	}
	if bobChannel.channelState.TheirBalance != aliceBalance {
		t.Fatalf("bob has incorrect remote balance %v vs %v",
			bobChannel.channelState.TheirBalance, aliceBalance)
	}
	if bobChannel.currentHeight != 1 {
		t.Fatalf("bob has incorrect commitment height, %v vs %v",
			bobChannel.currentHeight, 1)
	}
	if aliceChannel.currentHeight != 1 {
		t.Fatalf("alice has incorrect commitment height, %v vs %v",
			aliceChannel.currentHeight, 1)
	}

	// Alice's revocation window should now be one beyond the size of the
	// intial window. Same goes for Bob.
	if aliceChannel.revocationWindowEdge != 4 {
		t.Fatalf("alice revocation window not incremented, is %v should be %v",
			aliceChannel.revocationWindowEdge, 4)
	}
	if bobChannel.revocationWindowEdge != 4 {
		t.Fatalf("alice revocation window not incremented, is %v should be %v",
			bobChannel.revocationWindowEdge, 4)
	}

	// Now we'll repeat a similar exchange, this time with Bob settling the
	// HTLC once he learns of the preimage.
	var preimage [32]byte
	copy(preimage[:], paymentPreimage)
	settleIndex, err := bobChannel.SettleHTLC(preimage)
	if err != nil {
		t.Fatalf("bob unable to settle inbound htlc: %v", err)
	}
	if err := aliceChannel.ReceiveHTLCSettle(preimage, settleIndex); err != nil {
		t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
	}
	bobSig2, aliceIndex2, err := bobChannel.SignNextCommitment()
	if err != nil {
		t.Fatalf("bob unable to sign settle commitment: %v", err)
	}
	if err := aliceChannel.ReceiveNewCommitment(bobSig2, aliceIndex2); err != nil {
		t.Fatalf("alice unable to process bob's new commitment: %v", err)
	}
	aliceSig2, bobLogIndex2, err := aliceChannel.SignNextCommitment()
	if err != nil {
		t.Fatalf("alice unable to sign new commitment: %v", err)
	}
	aliceRevocation2, err := aliceChannel.RevokeCurrentCommitment()
	if err != nil {
		t.Fatalf("alice unable to generate revoation: %v", err)
	}
	if err := bobChannel.ReceiveNewCommitment(aliceSig2, bobLogIndex2); err != nil {
		t.Fatalf("bob unable to process alice's new commitment: %v", err)
	}
	bobRevocation2, err := bobChannel.RevokeCurrentCommitment()
	if err != nil {
		t.Fatalf("bob unable to revoke commitment: %v", err)
	}
	if htlcs, err := bobChannel.ReceiveRevocation(aliceRevocation2); err != nil {
		t.Fatalf("bob unable to process alice's revocation: %v", err)
	} else if len(htlcs) != 0 {
		t.Fatalf("bob shouldn't forward any HTLC's after outgoing settle, "+
			"instead can forward: %v", spew.Sdump(htlcs))
	}
	if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation2); err != nil {
		t.Fatalf("alice unable to process bob's revocation: %v", err)
	} else if len(htlcs) != 1 {
		// Alice should now be able to forward the settlement HTLC to
		// any down stream peers.
		t.Fatalf("alice should be able to forward a single HTLC, "+
			"instead can forward %v: %v", len(htlcs), spew.Sdump(htlcs))
	}

	// At this point, bob should have 6BTC settled, with Alice still having
	// 4 BTC. They should also be at a commitment height at two, with the
	// revocation window extended by by 1 (5).
	aliceSettleBalance := btcutil.Amount(4 * 1e8)
	bobSettleBalance := btcutil.Amount(6 * 1e8)
	if aliceChannel.channelState.OurBalance != aliceSettleBalance {
		t.Fatalf("alice has incorrect local balance %v vs %v",
			aliceChannel.channelState.OurBalance, aliceSettleBalance)
	}
	if aliceChannel.channelState.TheirBalance != bobSettleBalance {
		t.Fatalf("alice has incorrect remote balance %v vs %v",
			aliceChannel.channelState.TheirBalance, bobSettleBalance)
	}
	if bobChannel.channelState.OurBalance != bobSettleBalance {
		t.Fatalf("bob has incorrect local balance %v vs %v",
			bobChannel.channelState.OurBalance, bobSettleBalance)
	}
	if bobChannel.channelState.TheirBalance != aliceSettleBalance {
		t.Fatalf("bob has incorrect remote balance %v vs %v",
			bobChannel.channelState.TheirBalance, aliceSettleBalance)
	}
	if bobChannel.currentHeight != 2 {
		t.Fatalf("bob has incorrect commitment height, %v vs %v",
			bobChannel.currentHeight, 2)
	}
	if aliceChannel.currentHeight != 2 {
		t.Fatalf("alice has incorrect commitment height, %v vs %v",
			aliceChannel.currentHeight, 2)
	}
	if aliceChannel.revocationWindowEdge != 5 {
		t.Fatalf("alice revocation window not incremented, is %v should be %v",
			aliceChannel.revocationWindowEdge, 5)
	}
	if bobChannel.revocationWindowEdge != 5 {
		t.Fatalf("alice revocation window not incremented, is %v should be %v",
			bobChannel.revocationWindowEdge, 5)
	}

	// The logs of both sides should now be cleared since the entry adding
	// the HTLC should have been removed once both sides recieve the
	// revocation.
	if aliceChannel.ourUpdateLog.Len() != 0 {
		t.Fatalf("alice's local not updated, should be empty, has %v entries "+
			"instead", aliceChannel.ourUpdateLog.Len())
	}
	if aliceChannel.theirUpdateLog.Len() != 0 {
		t.Fatalf("alice's remote not updated, should be empty, has %v entries "+
			"instead", aliceChannel.theirUpdateLog.Len())
	}
	if len(aliceChannel.ourLogIndex) != 0 {
		t.Fatalf("alice's local log index not cleared, should be empty but "+
			"has %v entries", len(aliceChannel.ourLogIndex))
	}
	if len(aliceChannel.theirLogIndex) != 0 {
		t.Fatalf("alice's remote log index not cleared, should be empty but "+
			"has %v entries", len(aliceChannel.theirLogIndex))
	}
	if bobChannel.ourUpdateLog.Len() != 0 {
		t.Fatalf("bob's local log not updated, should be empty, has %v entries "+
			"instead", bobChannel.ourUpdateLog.Len())
	}
	if bobChannel.theirUpdateLog.Len() != 0 {
		t.Fatalf("bob's remote log not updated, should be empty, has %v entries "+
			"instead", bobChannel.theirUpdateLog.Len())
	}
	if len(bobChannel.ourLogIndex) != 0 {
		t.Fatalf("bob's local log index not cleared, should be empty but "+
			"has %v entries", len(bobChannel.ourLogIndex))
	}
	if len(bobChannel.theirLogIndex) != 0 {
		t.Fatalf("bob's remote log index not cleared, should be empty but "+
			"has %v entries", len(bobChannel.theirLogIndex))
	}
}