af4afdb6f0
In this commit, we fix an existing rounding related bug in the codebase. The RPC interface for btcd and bitcoind return values in BTC rather than in satoshis. So in several places, we're forced to convert ourselves manually. The existing logic attempted to do this, but didn't properly account for rounding. As a result, our values can be off due to not rounding incorrectly. The fix for this is easy: simply properly use btcutil.NewAmount everywhere which does rounding properly. Fixes #939.
5015 lines
173 KiB
Go
5015 lines
173 KiB
Go
package lnwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"io"
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
"reflect"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/shachain"
|
|
"github.com/roasbeef/btcd/blockchain"
|
|
"github.com/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
"github.com/roasbeef/btcd/txscript"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
)
|
|
|
|
var (
|
|
privPass = []byte("private-test")
|
|
|
|
// For simplicity a single priv key controls all of our test outputs.
|
|
testWalletPrivKey = []byte{
|
|
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
|
|
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
|
|
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
|
|
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
|
|
}
|
|
|
|
// We're alice :)
|
|
bobsPrivKey = []byte{
|
|
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
|
|
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
|
|
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
|
|
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
|
|
}
|
|
|
|
// Use a hard-coded HD seed.
|
|
testHdSeed = chainhash.Hash{
|
|
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
|
|
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
|
|
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
|
|
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
|
|
}
|
|
|
|
// The number of confirmations required to consider any created channel
|
|
// open.
|
|
numReqConfs = uint16(1)
|
|
|
|
// A serializable txn for testing funding txn.
|
|
testTx = &wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: wire.OutPoint{
|
|
Hash: chainhash.Hash{},
|
|
Index: 0xffffffff,
|
|
},
|
|
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
|
|
Sequence: 0xffffffff,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 5000000000,
|
|
PkScript: []byte{
|
|
0x41, // OP_DATA_65
|
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
|
0xa6, // 65-byte signature
|
|
0xac, // OP_CHECKSIG
|
|
},
|
|
},
|
|
},
|
|
LockTime: 5,
|
|
}
|
|
)
|
|
|
|
// initRevocationWindows simulates a new channel being opened within the p2p
|
|
// network by populating the initial revocation windows of the passed
|
|
// commitment state machines.
|
|
//
|
|
// TODO(roasbeef): rename!
|
|
func initRevocationWindows(chanA, chanB *LightningChannel, windowSize int) error {
|
|
aliceNextRevoke, err := chanA.NextRevocationKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := chanB.InitNextRevocation(aliceNextRevoke); err != nil {
|
|
return err
|
|
}
|
|
|
|
bobNextRevoke, err := chanB.NextRevocationKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := chanA.InitNextRevocation(bobNextRevoke); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// forceStateTransition executes the necessary interaction between the two
|
|
// commitment state machines to transition to a new state locking in any
|
|
// pending updates.
|
|
func forceStateTransition(chanA, chanB *LightningChannel) error {
|
|
aliceSig, aliceHtlcSigs, err := chanA.SignNextCommitment()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs); err != nil {
|
|
return err
|
|
}
|
|
|
|
bobRevocation, _, err := chanB.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bobSig, bobHtlcSigs, err := chanB.SignNextCommitment()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, _, _, err := chanA.ReceiveRevocation(bobRevocation); err != nil {
|
|
return err
|
|
}
|
|
if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil {
|
|
return err
|
|
}
|
|
|
|
aliceRevocation, _, err := chanA.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, _, _, err := chanB.ReceiveRevocation(aliceRevocation); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createTestChannels creates two test lightning channels using the provided
|
|
// notifier. The channel itself is funded with 10 BTC, with 5 BTC allocated to
|
|
// each side. Within the channel, Alice is the initiator.
|
|
func createTestChannels(revocationWindow int) (*LightningChannel,
|
|
*LightningChannel, func(), error) {
|
|
|
|
channelCapacity, err := btcutil.NewAmount(10)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
channelBal := channelCapacity / 2
|
|
aliceDustLimit := btcutil.Amount(200)
|
|
bobDustLimit := btcutil.Amount(1300)
|
|
csvTimeoutAlice := uint32(5)
|
|
csvTimeoutBob := uint32(4)
|
|
|
|
prevOut := &wire.OutPoint{
|
|
Hash: chainhash.Hash(testHdSeed),
|
|
Index: 0,
|
|
}
|
|
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
|
|
|
// For each party, we'll create a distinct set of keys in order to
|
|
// emulate the typical set up with live channels.
|
|
var (
|
|
aliceKeys []*btcec.PrivateKey
|
|
bobKeys []*btcec.PrivateKey
|
|
)
|
|
for i := 0; i < 5; i++ {
|
|
key := make([]byte, len(testWalletPrivKey))
|
|
copy(key[:], testWalletPrivKey[:])
|
|
key[0] ^= byte(i + 1)
|
|
|
|
aliceKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key)
|
|
aliceKeys = append(aliceKeys, aliceKey)
|
|
|
|
key = make([]byte, len(bobsPrivKey))
|
|
copy(key[:], bobsPrivKey)
|
|
key[0] ^= byte(i + 1)
|
|
|
|
bobKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key)
|
|
bobKeys = append(bobKeys, bobKey)
|
|
}
|
|
|
|
aliceCfg := channeldb.ChannelConfig{
|
|
ChannelConstraints: channeldb.ChannelConstraints{
|
|
DustLimit: aliceDustLimit,
|
|
MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity),
|
|
ChanReserve: channelCapacity / 100,
|
|
MinHTLC: 0,
|
|
MaxAcceptedHtlcs: MaxHTLCNumber / 2,
|
|
},
|
|
CsvDelay: uint16(csvTimeoutAlice),
|
|
MultiSigKey: keychain.KeyDescriptor{
|
|
PubKey: aliceKeys[0].PubKey(),
|
|
},
|
|
RevocationBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeys[1].PubKey(),
|
|
},
|
|
PaymentBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeys[2].PubKey(),
|
|
},
|
|
DelayBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeys[3].PubKey(),
|
|
},
|
|
HtlcBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeys[4].PubKey(),
|
|
},
|
|
}
|
|
bobCfg := channeldb.ChannelConfig{
|
|
ChannelConstraints: channeldb.ChannelConstraints{
|
|
DustLimit: bobDustLimit,
|
|
MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity),
|
|
ChanReserve: channelCapacity / 100,
|
|
MinHTLC: 0,
|
|
MaxAcceptedHtlcs: MaxHTLCNumber / 2,
|
|
},
|
|
CsvDelay: uint16(csvTimeoutBob),
|
|
MultiSigKey: keychain.KeyDescriptor{
|
|
PubKey: bobKeys[0].PubKey(),
|
|
},
|
|
RevocationBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeys[1].PubKey(),
|
|
},
|
|
PaymentBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeys[2].PubKey(),
|
|
},
|
|
DelayBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeys[3].PubKey(),
|
|
},
|
|
HtlcBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeys[4].PubKey(),
|
|
},
|
|
}
|
|
|
|
bobRoot, err := chainhash.NewHash(bobKeys[0].Serialize())
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot)
|
|
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
bobCommitPoint := ComputeCommitmentPoint(bobFirstRevoke[:])
|
|
|
|
aliceRoot, err := chainhash.NewHash(aliceKeys[0].Serialize())
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot)
|
|
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
aliceCommitPoint := ComputeCommitmentPoint(aliceFirstRevoke[:])
|
|
|
|
aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(channelBal,
|
|
channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint,
|
|
*fundingTxIn)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
alicePath, err := ioutil.TempDir("", "alicedb")
|
|
dbAlice, err := channeldb.Open(alicePath)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
bobPath, err := ioutil.TempDir("", "bobdb")
|
|
dbBob, err := channeldb.Open(bobPath)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
estimator := &StaticFeeEstimator{24}
|
|
feePerVSize, err := estimator.EstimateFeePerVSize(1)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
feePerKw := feePerVSize.FeePerKWeight()
|
|
commitFee := calcStaticFee(0)
|
|
|
|
aliceCommit := channeldb.ChannelCommitment{
|
|
CommitHeight: 0,
|
|
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee),
|
|
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
CommitFee: commitFee,
|
|
FeePerKw: btcutil.Amount(feePerKw),
|
|
CommitTx: aliceCommitTx,
|
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
|
}
|
|
bobCommit := channeldb.ChannelCommitment{
|
|
CommitHeight: 0,
|
|
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee),
|
|
CommitFee: commitFee,
|
|
FeePerKw: btcutil.Amount(feePerKw),
|
|
CommitTx: bobCommitTx,
|
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
|
}
|
|
|
|
var chanIDBytes [8]byte
|
|
if _, err := io.ReadFull(rand.Reader, chanIDBytes[:]); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
shortChanID := lnwire.NewShortChanIDFromInt(
|
|
binary.BigEndian.Uint64(chanIDBytes[:]),
|
|
)
|
|
|
|
aliceChannelState := &channeldb.OpenChannel{
|
|
LocalChanCfg: aliceCfg,
|
|
RemoteChanCfg: bobCfg,
|
|
IdentityPub: aliceKeys[0].PubKey(),
|
|
FundingOutpoint: *prevOut,
|
|
ShortChanID: shortChanID,
|
|
ChanType: channeldb.SingleFunder,
|
|
IsInitiator: true,
|
|
Capacity: channelCapacity,
|
|
RemoteCurrentRevocation: bobCommitPoint,
|
|
RevocationProducer: alicePreimageProducer,
|
|
RevocationStore: shachain.NewRevocationStore(),
|
|
LocalCommitment: aliceCommit,
|
|
RemoteCommitment: aliceCommit,
|
|
Db: dbAlice,
|
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
|
FundingTxn: testTx,
|
|
}
|
|
bobChannelState := &channeldb.OpenChannel{
|
|
LocalChanCfg: bobCfg,
|
|
RemoteChanCfg: aliceCfg,
|
|
IdentityPub: bobKeys[0].PubKey(),
|
|
FundingOutpoint: *prevOut,
|
|
ShortChanID: shortChanID,
|
|
ChanType: channeldb.SingleFunder,
|
|
IsInitiator: false,
|
|
Capacity: channelCapacity,
|
|
RemoteCurrentRevocation: aliceCommitPoint,
|
|
RevocationProducer: bobPreimageProducer,
|
|
RevocationStore: shachain.NewRevocationStore(),
|
|
LocalCommitment: bobCommit,
|
|
RemoteCommitment: bobCommit,
|
|
Db: dbBob,
|
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
|
}
|
|
|
|
aliceSigner := &mockSigner{privkeys: aliceKeys}
|
|
bobSigner := &mockSigner{privkeys: bobKeys}
|
|
|
|
pCache := &mockPreimageCache{
|
|
// hash -> preimage
|
|
preimageMap: make(map[[32]byte][]byte),
|
|
}
|
|
|
|
// TODO(roasbeef): make mock version of pre-image store
|
|
channelAlice, err := NewLightningChannel(
|
|
aliceSigner, pCache, aliceChannelState,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
channelBob, err := NewLightningChannel(
|
|
bobSigner, pCache, bobChannelState,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
if err := channelAlice.channelState.FullSync(); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if err := channelBob.channelState.FullSync(); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
cleanUpFunc := func() {
|
|
os.RemoveAll(bobPath)
|
|
os.RemoveAll(alicePath)
|
|
|
|
channelAlice.Stop()
|
|
channelBob.Stop()
|
|
}
|
|
|
|
// Now that the channel are open, simulate the start of a session by
|
|
// having Alice and Bob extend their revocation windows to each other.
|
|
err = initRevocationWindows(channelAlice, channelBob, revocationWindow)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
return channelAlice, channelBob, cleanUpFunc, nil
|
|
}
|
|
|
|
// calcStaticFee calculates appropriate fees for commitment transactions. This
|
|
// function provides a simple way to allow test balance assertions to take fee
|
|
// calculations into account.
|
|
//
|
|
// TODO(bvu): Refactor when dynamic fee estimation is added.
|
|
func calcStaticFee(numHTLCs int) btcutil.Amount {
|
|
const (
|
|
commitWeight = btcutil.Amount(724)
|
|
htlcWeight = 172
|
|
feePerKw = btcutil.Amount(24/4) * 1000
|
|
)
|
|
return feePerKw * (commitWeight +
|
|
btcutil.Amount(htlcWeight*numHTLCs)) / 1000
|
|
}
|
|
|
|
// createHTLC is a utility function for generating an HTLC with a given
|
|
// preimage and a given amount.
|
|
func createHTLC(id int, amount lnwire.MilliSatoshi) (*lnwire.UpdateAddHTLC, [32]byte) {
|
|
preimage := bytes.Repeat([]byte{byte(id)}, 32)
|
|
paymentHash := sha256.Sum256(preimage)
|
|
|
|
var returnPreimage [32]byte
|
|
copy(returnPreimage[:], preimage)
|
|
|
|
return &lnwire.UpdateAddHTLC{
|
|
ID: uint64(id),
|
|
PaymentHash: paymentHash,
|
|
Amount: amount,
|
|
Expiry: uint32(5),
|
|
}, returnPreimage
|
|
}
|
|
|
|
func assertOutputExistsByValue(t *testing.T, commitTx *wire.MsgTx,
|
|
value btcutil.Amount) {
|
|
|
|
for _, txOut := range commitTx.TxOut {
|
|
if txOut.Value == int64(value) {
|
|
return
|
|
}
|
|
}
|
|
|
|
t.Fatalf("unable to find output of value %v within tx %v", value,
|
|
spew.Sdump(commitTx))
|
|
}
|
|
|
|
// 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 committed 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) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
paymentPreimage := bytes.Repeat([]byte{1}, 32)
|
|
paymentHash := sha256.Sum256(paymentPreimage)
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
|
htlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: paymentHash,
|
|
Amount: htlcAmt,
|
|
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 adds
|
|
// this htlc to his remote state update log.
|
|
aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
|
|
bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Next alice commits this change by sending a signature message. Since
|
|
// we expect the messages to be ordered, Bob will receive the HTLC we
|
|
// just sent before he receives this signature, so the signature will
|
|
// cover the HTLC.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to sign commitment: %v", err)
|
|
}
|
|
|
|
// Bob receives this signature message, and checks that this covers the
|
|
// state he has in his remote log. This includes the HTLC just sent
|
|
// from Alice.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's new commitment: %v", err)
|
|
}
|
|
|
|
// Bob revokes his prior commitment given to him by Alice, since he now
|
|
// has a valid signature for a newer commitment.
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate bob revocation: %v", err)
|
|
}
|
|
|
|
// Bob finally send a signature for Alice's commitment transaction.
|
|
// This signature will cover the HTLC, since Bob will first send the
|
|
// revocation just created. The revocation also acks every received
|
|
// HTLC up to the point where Alice sent here signature.
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign alice's commitment: %v", err)
|
|
}
|
|
|
|
// Alice then processes this revocation, sending her own revocation for
|
|
// her prior commitment transaction. Alice shouldn't have any HTLCs to
|
|
// forward since she's sending an outgoing HTLC.
|
|
fwdPkg, _, _, err := aliceChannel.ReceiveRevocation(bobRevocation)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to process bob's revocation: %v", err)
|
|
}
|
|
if len(fwdPkg.Adds) != 0 {
|
|
t.Fatalf("alice forwards %v add htlcs, should forward none",
|
|
len(fwdPkg.Adds))
|
|
}
|
|
if len(fwdPkg.SettleFails) != 0 {
|
|
t.Fatalf("alice forwards %v settle/fail htlcs, "+
|
|
"should forward none", len(fwdPkg.SettleFails))
|
|
}
|
|
|
|
// Alice then processes bob's signature, and since she just received
|
|
// the revocation, she expect this signature to cover everything up to
|
|
// the point where she sent her signature, including the HTLC.
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
|
}
|
|
|
|
// Alice then generates a revocation for bob.
|
|
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.
|
|
fwdPkg, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's revocation: %v", err)
|
|
}
|
|
if len(fwdPkg.Adds) != 1 {
|
|
t.Fatalf("bob forwards %v add htlcs, should only forward one",
|
|
len(fwdPkg.Adds))
|
|
}
|
|
if len(fwdPkg.SettleFails) != 0 {
|
|
t.Fatalf("bob forwards %v settle/fail htlcs, "+
|
|
"should forward none", len(fwdPkg.SettleFails))
|
|
}
|
|
|
|
// At this point, both sides should have the proper number of satoshis
|
|
// sent, and commitment height updated within their local channel
|
|
// state.
|
|
aliceSent := lnwire.MilliSatoshi(0)
|
|
bobSent := lnwire.MilliSatoshi(0)
|
|
|
|
if aliceChannel.channelState.TotalMSatSent != aliceSent {
|
|
t.Fatalf("alice has incorrect milli-satoshis sent: %v vs %v",
|
|
aliceChannel.channelState.TotalMSatSent, aliceSent)
|
|
}
|
|
if aliceChannel.channelState.TotalMSatReceived != bobSent {
|
|
t.Fatalf("alice has incorrect milli-satoshis received %v vs %v",
|
|
aliceChannel.channelState.TotalMSatReceived, bobSent)
|
|
}
|
|
if bobChannel.channelState.TotalMSatSent != bobSent {
|
|
t.Fatalf("bob has incorrect milli-satoshis sent %v vs %v",
|
|
bobChannel.channelState.TotalMSatSent, bobSent)
|
|
}
|
|
if bobChannel.channelState.TotalMSatReceived != aliceSent {
|
|
t.Fatalf("bob has incorrect milli-satoshis received %v vs %v",
|
|
bobChannel.channelState.TotalMSatReceived, aliceSent)
|
|
}
|
|
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)
|
|
}
|
|
|
|
// Both commitment transactions should have three outputs, and one of
|
|
// them should be exactly the amount of the HTLC.
|
|
if len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut) != 3 {
|
|
t.Fatalf("alice should have three commitment outputs, instead "+
|
|
"have %v",
|
|
len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut))
|
|
}
|
|
if len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut) != 3 {
|
|
t.Fatalf("bob should have three commitment outputs, instead "+
|
|
"have %v",
|
|
len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut))
|
|
}
|
|
assertOutputExistsByValue(t,
|
|
aliceChannel.channelState.LocalCommitment.CommitTx,
|
|
htlcAmt.ToSatoshis())
|
|
assertOutputExistsByValue(t,
|
|
bobChannel.channelState.LocalCommitment.CommitTx,
|
|
htlcAmt.ToSatoshis())
|
|
|
|
// 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)
|
|
err = bobChannel.SettleHTLC(preimage, bobHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to settle inbound htlc: %v", err)
|
|
}
|
|
|
|
err = aliceChannel.ReceiveHTLCSettle(preimage, aliceHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
|
|
}
|
|
|
|
bobSig2, bobHtlcSigs2, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign settle commitment: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig2, bobHtlcSigs2)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
|
}
|
|
|
|
aliceRevocation2, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to generate revocation: %v", err)
|
|
}
|
|
aliceSig2, aliceHtlcSigs2, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to sign new commitment: %v", err)
|
|
}
|
|
|
|
fwdPkg, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation2)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's revocation: %v", err)
|
|
}
|
|
if len(fwdPkg.Adds) != 0 {
|
|
t.Fatalf("bob forwards %v add htlcs, should forward none",
|
|
len(fwdPkg.Adds))
|
|
}
|
|
if len(fwdPkg.SettleFails) != 0 {
|
|
t.Fatalf("bob forwards %v settle/fail htlcs, "+
|
|
"should forward none", len(fwdPkg.SettleFails))
|
|
}
|
|
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig2, aliceHtlcSigs2)
|
|
if 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)
|
|
}
|
|
fwdPkg, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation2)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to process bob's revocation: %v", err)
|
|
}
|
|
if len(fwdPkg.Adds) != 0 {
|
|
// Alice should now be able to forward the settlement HTLC to
|
|
// any down stream peers.
|
|
t.Fatalf("alice should be forwarding an add HTLC, "+
|
|
"instead forwarding %v: %v", len(fwdPkg.Adds),
|
|
spew.Sdump(fwdPkg.Adds))
|
|
}
|
|
if len(fwdPkg.SettleFails) != 1 {
|
|
t.Fatalf("alice should be forwarding one settle/fails HTLC, "+
|
|
"instead forwarding: %v", len(fwdPkg.SettleFails))
|
|
}
|
|
|
|
// At this point, Bob should have 6 BTC settled, with Alice still having
|
|
// 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel
|
|
// should show 1 BTC received. They should also be at commitment height
|
|
// two, with the revocation window extended by by 1 (5).
|
|
mSatTransferred := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
|
if aliceChannel.channelState.TotalMSatSent != mSatTransferred {
|
|
t.Fatalf("alice satoshis sent incorrect %v vs %v expected",
|
|
aliceChannel.channelState.TotalMSatSent,
|
|
mSatTransferred)
|
|
}
|
|
if aliceChannel.channelState.TotalMSatReceived != 0 {
|
|
t.Fatalf("alice satoshis received incorrect %v vs %v expected",
|
|
aliceChannel.channelState.TotalMSatReceived, 0)
|
|
}
|
|
if bobChannel.channelState.TotalMSatReceived != mSatTransferred {
|
|
t.Fatalf("bob satoshis received incorrect %v vs %v expected",
|
|
bobChannel.channelState.TotalMSatReceived,
|
|
mSatTransferred)
|
|
}
|
|
if bobChannel.channelState.TotalMSatSent != 0 {
|
|
t.Fatalf("bob satoshis sent incorrect %v vs %v expected",
|
|
bobChannel.channelState.TotalMSatSent, 0)
|
|
}
|
|
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)
|
|
}
|
|
|
|
// The logs of both sides should now be cleared since the entry adding
|
|
// the HTLC should have been removed once both sides receive the
|
|
// revocation.
|
|
if aliceChannel.localUpdateLog.Len() != 0 {
|
|
t.Fatalf("alice's local not updated, should be empty, has %v "+
|
|
"entries instead", aliceChannel.localUpdateLog.Len())
|
|
}
|
|
if aliceChannel.remoteUpdateLog.Len() != 0 {
|
|
t.Fatalf("alice's remote not updated, should be empty, has %v "+
|
|
"entries instead", aliceChannel.remoteUpdateLog.Len())
|
|
}
|
|
if len(aliceChannel.localUpdateLog.updateIndex) != 0 {
|
|
t.Fatalf("alice's local log index not cleared, should be empty but "+
|
|
"has %v entries", len(aliceChannel.localUpdateLog.updateIndex))
|
|
}
|
|
if len(aliceChannel.remoteUpdateLog.updateIndex) != 0 {
|
|
t.Fatalf("alice's remote log index not cleared, should be empty but "+
|
|
"has %v entries", len(aliceChannel.remoteUpdateLog.updateIndex))
|
|
}
|
|
}
|
|
|
|
// TestCheckCommitTxSize checks that estimation size of commitment
|
|
// transaction with some degree of error corresponds to the actual size.
|
|
func TestCheckCommitTxSize(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
checkSize := func(channel *LightningChannel, count int) {
|
|
// Due to variable size of the signatures (70-73) in
|
|
// witness script actual size of commitment transaction might
|
|
// be lower on 6 weight.
|
|
BaseCommitmentTxSizeEstimationError := 6
|
|
|
|
commitTx, err := channel.getSignedCommitTx()
|
|
if err != nil {
|
|
t.Fatalf("unable to initiate alice force close: %v", err)
|
|
}
|
|
|
|
actualCost := blockchain.GetTransactionWeight(btcutil.NewTx(commitTx))
|
|
estimatedCost := estimateCommitTxWeight(count, false)
|
|
|
|
diff := int(estimatedCost - actualCost)
|
|
if 0 > diff || BaseCommitmentTxSizeEstimationError < diff {
|
|
t.Fatalf("estimation is wrong, diff: %v", diff)
|
|
}
|
|
|
|
}
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// Check that weight estimation of the commitment transaction without
|
|
// HTLCs is right.
|
|
checkSize(aliceChannel, 0)
|
|
checkSize(bobChannel, 0)
|
|
|
|
// Adding HTLCs and check that size stays in allowable estimation
|
|
// error window.
|
|
for i := 0; i <= 10; i++ {
|
|
htlc, _ := createHTLC(i, lnwire.MilliSatoshi(1e7))
|
|
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("bob unable to receive htlc: %v", err)
|
|
}
|
|
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete state update: %v", err)
|
|
}
|
|
checkSize(aliceChannel, i+1)
|
|
checkSize(bobChannel, i+1)
|
|
}
|
|
|
|
// Settle HTLCs and check that estimation is counting cost of settle
|
|
// HTLCs properly.
|
|
for i := 10; i >= 0; i-- {
|
|
_, preimage := createHTLC(i, lnwire.MilliSatoshi(1e7))
|
|
|
|
err := bobChannel.SettleHTLC(preimage, uint64(i), nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to settle inbound htlc: %v", err)
|
|
}
|
|
|
|
err = aliceChannel.ReceiveHTLCSettle(preimage, uint64(i))
|
|
if err != nil {
|
|
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
|
|
}
|
|
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete state update: %v", err)
|
|
}
|
|
checkSize(aliceChannel, i)
|
|
checkSize(bobChannel, i)
|
|
}
|
|
}
|
|
|
|
func TestCooperativeChannelClosure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
aliceDeliveryScript := bobsPrivKey[:]
|
|
bobDeliveryScript := testHdSeed[:]
|
|
|
|
aliceFeeRate := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw)
|
|
bobFeeRate := SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw)
|
|
|
|
// We'll store with both Alice and Bob creating a new close proposal
|
|
// with the same fee.
|
|
aliceFee := aliceChannel.CalcFee(aliceFeeRate)
|
|
aliceSig, _, _, err := aliceChannel.CreateCloseProposal(
|
|
aliceFee, aliceDeliveryScript, bobDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice coop close proposal: %v", err)
|
|
}
|
|
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
|
|
|
|
bobFee := bobChannel.CalcFee(bobFeeRate)
|
|
bobSig, _, _, err := bobChannel.CreateCloseProposal(
|
|
bobFee, bobDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob coop close proposal: %v", err)
|
|
}
|
|
bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
|
|
|
|
// With the proposals created, both sides should be able to properly
|
|
// process the other party's signature. This indicates that the
|
|
// transaction is well formed, and the signatures verify.
|
|
aliceCloseTx, _, err := bobChannel.CompleteCooperativeClose(
|
|
bobCloseSig, aliceCloseSig, bobDeliveryScript,
|
|
aliceDeliveryScript, bobFee)
|
|
if err != nil {
|
|
t.Fatalf("unable to complete alice cooperative close: %v", err)
|
|
}
|
|
bobCloseSha := aliceCloseTx.TxHash()
|
|
|
|
bobCloseTx, _, err := aliceChannel.CompleteCooperativeClose(
|
|
aliceCloseSig, bobCloseSig, aliceDeliveryScript,
|
|
bobDeliveryScript, aliceFee)
|
|
if err != nil {
|
|
t.Fatalf("unable to complete bob cooperative close: %v", err)
|
|
}
|
|
aliceCloseSha := bobCloseTx.TxHash()
|
|
|
|
if bobCloseSha != aliceCloseSha {
|
|
t.Fatalf("alice and bob close transactions don't match: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestForceClose checks that the resulting ForceCloseSummary is correct when a
|
|
// peer is ForceClosing the channel. Will check outputs both above and below
|
|
// the dust limit. Additionally, we'll ensure that the node which executed the
|
|
// force close generates HTLC resolutions that are capable of sweeping both
|
|
// incoming and outgoing HTLC's.
|
|
func TestForceClose(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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()
|
|
|
|
bobAmount := bobChannel.channelState.LocalCommitment.LocalBalance
|
|
|
|
// First, we'll add an outgoing HTLC from Alice to Bob, such that it
|
|
// will still be present within the broadcast commitment transaction.
|
|
// We'll ensure that the HTLC amount is above Alice's dust limit.
|
|
htlcAmount := lnwire.NewMSatFromSatoshis(20000)
|
|
htlcAlice, _ := createHTLC(0, htlcAmount)
|
|
if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil {
|
|
t.Fatalf("bob unable to recv add htlc: %v", err)
|
|
}
|
|
|
|
// We'll also a distinct HTLC from Bob -> Alice. This way, Alice will
|
|
// have both an incoming and outgoing HTLC on her commitment
|
|
// transaction.
|
|
htlcBob, preimageBob := createHTLC(0, htlcAmount)
|
|
if _, err := bobChannel.AddHTLC(htlcBob, nil); err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
if _, err := aliceChannel.ReceiveHTLC(htlcBob); err != nil {
|
|
t.Fatalf("bob unable to recv add htlc: %v", err)
|
|
}
|
|
|
|
// Next, we'll perform two state transitions to ensure that both HTLC's
|
|
// get fully locked-in.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("Can't update the channel state: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("Can't update the channel state: %v", err)
|
|
}
|
|
|
|
// Before we force close Alice's channel, we'll add the pre-image of
|
|
// Bob's HTLC to her preimage cache.
|
|
aliceChannel.pCache.AddPreimage(preimageBob[:])
|
|
|
|
// With the cache populated, we'll now attempt the force close
|
|
// initiated by Alice.
|
|
closeSummary, err := aliceChannel.ForceClose()
|
|
if err != nil {
|
|
t.Fatalf("unable to force close channel: %v", err)
|
|
}
|
|
|
|
// Alice should detect that she can sweep the outgoing HTLC after a
|
|
// timeout, but also that she's able to sweep in incoming HTLC Bob sent
|
|
// her.
|
|
if len(closeSummary.HtlcResolutions.OutgoingHTLCs) != 1 {
|
|
t.Fatalf("alice out htlc resolutions not populated: expected %v "+
|
|
"htlcs, got %v htlcs",
|
|
1, len(closeSummary.HtlcResolutions.OutgoingHTLCs))
|
|
}
|
|
if len(closeSummary.HtlcResolutions.IncomingHTLCs) != 1 {
|
|
t.Fatalf("alice in htlc resolutions not populated: expected %v "+
|
|
"htlcs, got %v htlcs",
|
|
1, len(closeSummary.HtlcResolutions.IncomingHTLCs))
|
|
}
|
|
|
|
// The SelfOutputSignDesc should be non-nil since the output to-self is
|
|
// non-dust.
|
|
aliceCommitResolution := closeSummary.CommitResolution
|
|
if aliceCommitResolution == nil {
|
|
t.Fatalf("alice fails to include to-self output in " +
|
|
"ForceCloseSummary")
|
|
}
|
|
|
|
// The rest of the close summary should have been populated properly.
|
|
aliceDelayPoint := aliceChannel.channelState.LocalChanCfg.DelayBasePoint
|
|
if !aliceCommitResolution.SelfOutputSignDesc.KeyDesc.PubKey.IsEqual(
|
|
aliceDelayPoint.PubKey,
|
|
) {
|
|
t.Fatalf("alice incorrect pubkey in SelfOutputSignDesc")
|
|
}
|
|
|
|
// Factoring in the fee rate, Alice's amount should properly reflect
|
|
// that we've added two additional HTLC to the commitment transaction.
|
|
totalCommitWeight := CommitWeight + (HtlcWeight * 2)
|
|
feePerKw := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw)
|
|
commitFee := feePerKw.FeeForWeight(totalCommitWeight)
|
|
expectedAmount := (aliceChannel.Capacity / 2) - htlcAmount.ToSatoshis() - commitFee
|
|
if aliceCommitResolution.SelfOutputSignDesc.Output.Value != int64(expectedAmount) {
|
|
t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+
|
|
"expected %v, got %v", int64(expectedAmount),
|
|
aliceCommitResolution.SelfOutputSignDesc.Output.Value)
|
|
}
|
|
|
|
// Alice's listed CSV delay should also match the delay that was
|
|
// pre-committed to at channel opening.
|
|
if aliceCommitResolution.MaturityDelay !=
|
|
uint32(aliceChannel.localChanCfg.CsvDelay) {
|
|
|
|
t.Fatalf("alice: incorrect local CSV delay in ForceCloseSummary, "+
|
|
"expected %v, got %v",
|
|
aliceChannel.channelState.LocalChanCfg.CsvDelay,
|
|
aliceCommitResolution.MaturityDelay)
|
|
}
|
|
|
|
// Next, we'll ensure that the second level HTLC transaction it itself
|
|
// spendable, and also that the delivery output (with delay) itself has
|
|
// a valid sign descriptor.
|
|
htlcResolution := closeSummary.HtlcResolutions.OutgoingHTLCs[0]
|
|
outHtlcIndex := htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint.Index
|
|
senderHtlcPkScript := closeSummary.CloseTx.TxOut[outHtlcIndex].PkScript
|
|
|
|
// First, verify that the second level transaction can properly spend
|
|
// the multi-sig clause within the output on the commitment transaction
|
|
// that produces this HTLC.
|
|
timeoutTx := htlcResolution.SignedTimeoutTx
|
|
vm, err := txscript.NewEngine(senderHtlcPkScript,
|
|
timeoutTx, 0, txscript.StandardVerifyFlags, nil,
|
|
nil, int64(htlcAmount.ToSatoshis()))
|
|
if err != nil {
|
|
t.Fatalf("unable to create engine: %v", err)
|
|
}
|
|
if err := vm.Execute(); err != nil {
|
|
t.Fatalf("htlc timeout spend is invalid: %v", err)
|
|
}
|
|
|
|
// Next, we'll ensure that we can spend the output of the second level
|
|
// transaction given a properly crafted sweep transaction.
|
|
sweepTx := wire.NewMsgTx(2)
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: wire.OutPoint{
|
|
Hash: htlcResolution.SignedTimeoutTx.TxHash(),
|
|
Index: 0,
|
|
},
|
|
})
|
|
sweepTx.AddTxOut(&wire.TxOut{
|
|
PkScript: senderHtlcPkScript,
|
|
Value: htlcResolution.SweepSignDesc.Output.Value,
|
|
})
|
|
htlcResolution.SweepSignDesc.InputIndex = 0
|
|
sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.signer,
|
|
&htlcResolution.SweepSignDesc, sweepTx,
|
|
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay))
|
|
if err != nil {
|
|
t.Fatalf("unable to gen witness for timeout output: %v", err)
|
|
}
|
|
|
|
// With the witness fully populated for the success spend from the
|
|
// second-level transaction, we ensure that the scripts properly
|
|
// validate given the information within the htlc resolution struct.
|
|
vm, err = txscript.NewEngine(
|
|
htlcResolution.SweepSignDesc.Output.PkScript,
|
|
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
|
nil, htlcResolution.SweepSignDesc.Output.Value,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create engine: %v", err)
|
|
}
|
|
if err := vm.Execute(); err != nil {
|
|
t.Fatalf("htlc timeout spend is invalid: %v", err)
|
|
}
|
|
|
|
// Finally, the txid of the commitment transaction and the one returned
|
|
// as the closing transaction should also match.
|
|
closeTxHash := closeSummary.CloseTx.TxHash()
|
|
commitTxHash := aliceChannel.channelState.LocalCommitment.CommitTx.TxHash()
|
|
if !bytes.Equal(closeTxHash[:], commitTxHash[:]) {
|
|
t.Fatalf("alice: incorrect close transaction txid")
|
|
}
|
|
|
|
// We'll now perform similar set of checks to ensure that Alice is able
|
|
// to sweep the output that Bob sent to her on-chain with knowledge of
|
|
// the preimage.
|
|
inHtlcResolution := closeSummary.HtlcResolutions.IncomingHTLCs[0]
|
|
inHtlcIndex := inHtlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint.Index
|
|
receiverHtlcScript := closeSummary.CloseTx.TxOut[inHtlcIndex].PkScript
|
|
|
|
// With the original pkscript located, we'll now verify that the second
|
|
// level transaction can spend from the multi-sig out.
|
|
successTx := inHtlcResolution.SignedSuccessTx
|
|
vm, err = txscript.NewEngine(receiverHtlcScript,
|
|
successTx, 0, txscript.StandardVerifyFlags, nil,
|
|
nil, int64(htlcAmount.ToSatoshis()))
|
|
if err != nil {
|
|
t.Fatalf("unable to create engine: %v", err)
|
|
}
|
|
if err := vm.Execute(); err != nil {
|
|
t.Fatalf("htlc success spend is invalid: %v", err)
|
|
}
|
|
|
|
// Finally, we'll construct a transaction to spend the produced
|
|
// second-level output with the attached SignDescriptor.
|
|
sweepTx = wire.NewMsgTx(2)
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: inHtlcResolution.ClaimOutpoint,
|
|
})
|
|
sweepTx.AddTxOut(&wire.TxOut{
|
|
PkScript: receiverHtlcScript,
|
|
Value: inHtlcResolution.SweepSignDesc.Output.Value,
|
|
})
|
|
inHtlcResolution.SweepSignDesc.InputIndex = 0
|
|
sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.signer,
|
|
&inHtlcResolution.SweepSignDesc, sweepTx,
|
|
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay))
|
|
if err != nil {
|
|
t.Fatalf("unable to gen witness for timeout output: %v", err)
|
|
}
|
|
|
|
// The spend we create above spending the second level HTLC output
|
|
// should validate without any issues.
|
|
vm, err = txscript.NewEngine(
|
|
inHtlcResolution.SweepSignDesc.Output.PkScript,
|
|
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
|
nil, inHtlcResolution.SweepSignDesc.Output.Value,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create engine: %v", err)
|
|
}
|
|
if err := vm.Execute(); err != nil {
|
|
t.Fatalf("htlc timeout spend is invalid: %v", err)
|
|
}
|
|
|
|
// Check the same for Bob's ForceCloseSummary.
|
|
closeSummary, err = bobChannel.ForceClose()
|
|
if err != nil {
|
|
t.Fatalf("unable to force close channel: %v", err)
|
|
}
|
|
bobCommitResolution := closeSummary.CommitResolution
|
|
if bobCommitResolution == nil {
|
|
t.Fatalf("bob fails to include to-self output in ForceCloseSummary")
|
|
}
|
|
bobDelayPoint := bobChannel.channelState.LocalChanCfg.DelayBasePoint
|
|
if !bobCommitResolution.SelfOutputSignDesc.KeyDesc.PubKey.IsEqual(bobDelayPoint.PubKey) {
|
|
t.Fatalf("bob incorrect pubkey in SelfOutputSignDesc")
|
|
}
|
|
if bobCommitResolution.SelfOutputSignDesc.Output.Value !=
|
|
int64(bobAmount.ToSatoshis()-htlcAmount.ToSatoshis()) {
|
|
|
|
t.Fatalf("bob incorrect output value in SelfOutputSignDesc, "+
|
|
"expected %v, got %v",
|
|
bobAmount.ToSatoshis(),
|
|
int64(bobCommitResolution.SelfOutputSignDesc.Output.Value))
|
|
}
|
|
if bobCommitResolution.MaturityDelay !=
|
|
uint32(bobChannel.channelState.LocalChanCfg.CsvDelay) {
|
|
|
|
t.Fatalf("bob: incorrect local CSV delay in ForceCloseSummary, "+
|
|
"expected %v, got %v",
|
|
bobChannel.channelState.LocalChanCfg.CsvDelay,
|
|
bobCommitResolution.MaturityDelay)
|
|
}
|
|
|
|
closeTxHash = closeSummary.CloseTx.TxHash()
|
|
commitTxHash = bobChannel.channelState.LocalCommitment.CommitTx.TxHash()
|
|
if !bytes.Equal(closeTxHash[:], commitTxHash[:]) {
|
|
t.Fatalf("bob: incorrect close transaction txid")
|
|
}
|
|
|
|
// As we didn't add the preimage of Alice's HTLC to bob's preimage
|
|
// cache, he should only detect that he can sweep only his outgoing
|
|
// HTLC upon force close.
|
|
if len(closeSummary.HtlcResolutions.OutgoingHTLCs) != 1 {
|
|
t.Fatalf("alice out htlc resolutions not populated: expected %v "+
|
|
"htlcs, got %v htlcs",
|
|
1, len(closeSummary.HtlcResolutions.OutgoingHTLCs))
|
|
}
|
|
|
|
// Bob should recognize that the incoming HTLC is there, but the
|
|
// preimage should be empty as he doesn't have the knowledge required
|
|
// to sweep it.
|
|
if len(closeSummary.HtlcResolutions.IncomingHTLCs) != 1 {
|
|
t.Fatalf("bob in htlc resolutions not populated: expected %v "+
|
|
"htlcs, got %v htlcs",
|
|
1, len(closeSummary.HtlcResolutions.IncomingHTLCs))
|
|
}
|
|
var zeroHash [32]byte
|
|
if closeSummary.HtlcResolutions.IncomingHTLCs[0].Preimage != zeroHash {
|
|
t.Fatalf("bob shouldn't know preimage but does")
|
|
}
|
|
}
|
|
|
|
// TestForceCloseDustOutput tests that if either side force closes with an
|
|
// active dust output (for only a single party due to asymmetric dust values),
|
|
// then the force close summary is well crafted.
|
|
func TestForceCloseDustOutput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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()
|
|
|
|
// We set both node's channel reserves to 0, to make sure
|
|
// they can create small dust ouputs without going under
|
|
// their channel reserves.
|
|
aliceChannel.localChanCfg.ChanReserve = 0
|
|
bobChannel.localChanCfg.ChanReserve = 0
|
|
aliceChannel.remoteChanCfg.ChanReserve = 0
|
|
bobChannel.remoteChanCfg.ChanReserve = 0
|
|
|
|
htlcAmount := lnwire.NewMSatFromSatoshis(500)
|
|
|
|
aliceAmount := aliceChannel.channelState.LocalCommitment.LocalBalance
|
|
bobAmount := bobChannel.channelState.LocalCommitment.LocalBalance
|
|
|
|
// Have Bobs' to-self output be below her dust limit and check
|
|
// ForceCloseSummary again on both peers.
|
|
htlc, preimage := createHTLC(0, bobAmount-htlcAmount)
|
|
bobHtlcIndex, err := bobChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
aliceHtlcIndex, err := aliceChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to receive htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("Can't update the channel state: %v", err)
|
|
}
|
|
|
|
// Settle HTLC and sign new commitment.
|
|
err = aliceChannel.SettleHTLC(preimage, aliceHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to settle inbound htlc: %v", err)
|
|
}
|
|
err = bobChannel.ReceiveHTLCSettle(preimage, bobHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("Can't update the channel state: %v", err)
|
|
}
|
|
|
|
aliceAmount = aliceChannel.channelState.LocalCommitment.LocalBalance
|
|
bobAmount = bobChannel.channelState.LocalCommitment.RemoteBalance
|
|
|
|
closeSummary, err := aliceChannel.ForceClose()
|
|
if err != nil {
|
|
t.Fatalf("unable to force close channel: %v", err)
|
|
}
|
|
|
|
// Alice's to-self output should still be in the commitment
|
|
// transaction.
|
|
commitResolution := closeSummary.CommitResolution
|
|
if commitResolution == nil {
|
|
t.Fatalf("alice fails to include to-self output in " +
|
|
"ForceCloseSummary")
|
|
}
|
|
if !commitResolution.SelfOutputSignDesc.KeyDesc.PubKey.IsEqual(
|
|
aliceChannel.channelState.LocalChanCfg.DelayBasePoint.PubKey,
|
|
) {
|
|
t.Fatalf("alice incorrect pubkey in SelfOutputSignDesc")
|
|
}
|
|
if commitResolution.SelfOutputSignDesc.Output.Value !=
|
|
int64(aliceAmount.ToSatoshis()) {
|
|
t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+
|
|
"expected %v, got %v",
|
|
aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis(),
|
|
commitResolution.SelfOutputSignDesc.Output.Value)
|
|
}
|
|
|
|
if commitResolution.MaturityDelay !=
|
|
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay) {
|
|
t.Fatalf("alice: incorrect local CSV delay in ForceCloseSummary, "+
|
|
"expected %v, got %v",
|
|
aliceChannel.channelState.LocalChanCfg.CsvDelay,
|
|
commitResolution.MaturityDelay)
|
|
}
|
|
|
|
closeTxHash := closeSummary.CloseTx.TxHash()
|
|
commitTxHash := aliceChannel.channelState.LocalCommitment.CommitTx.TxHash()
|
|
if !bytes.Equal(closeTxHash[:], commitTxHash[:]) {
|
|
t.Fatalf("alice: incorrect close transaction txid")
|
|
}
|
|
|
|
closeSummary, err = bobChannel.ForceClose()
|
|
if err != nil {
|
|
t.Fatalf("unable to force close channel: %v", err)
|
|
}
|
|
|
|
// Bob's to-self output is below Bob's dust value and should be
|
|
// reflected in the ForceCloseSummary.
|
|
commitResolution = closeSummary.CommitResolution
|
|
if commitResolution != nil {
|
|
t.Fatalf("bob incorrectly includes to-self output in " +
|
|
"ForceCloseSummary")
|
|
}
|
|
|
|
closeTxHash = closeSummary.CloseTx.TxHash()
|
|
commitTxHash = bobChannel.channelState.LocalCommitment.CommitTx.TxHash()
|
|
if !bytes.Equal(closeTxHash[:], commitTxHash[:]) {
|
|
t.Fatalf("bob: incorrect close transaction txid")
|
|
}
|
|
}
|
|
|
|
// TestDustHTLCFees checks that fees are calculated correctly when HTLCs fall
|
|
// below the nodes' dust limit. In these cases, the amount of the dust HTLCs
|
|
// should be applied to the commitment transaction fee.
|
|
func TestDustHTLCFees(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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()
|
|
|
|
aliceStartingBalance := aliceChannel.channelState.LocalCommitment.LocalBalance
|
|
|
|
// This HTLC amount should be lower than the dust limits of both nodes.
|
|
htlcAmount := lnwire.NewMSatFromSatoshis(100)
|
|
htlc, _ := createHTLC(0, htlcAmount)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("bob unable to receive htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("Can't update the channel state: %v", err)
|
|
}
|
|
|
|
// After the transition, we'll ensure that we performed fee accounting
|
|
// properly. Namely, the local+remote+commitfee values should add up to
|
|
// the total capacity of the channel. This same should hold for both
|
|
// sides.
|
|
totalSatoshisAlice := (aliceChannel.channelState.LocalCommitment.LocalBalance +
|
|
aliceChannel.channelState.LocalCommitment.RemoteBalance +
|
|
lnwire.NewMSatFromSatoshis(aliceChannel.channelState.LocalCommitment.CommitFee))
|
|
if totalSatoshisAlice+htlcAmount != lnwire.NewMSatFromSatoshis(aliceChannel.Capacity) {
|
|
t.Fatalf("alice's funds leaked: total satoshis are %v, but channel "+
|
|
"capacity is %v", int64(totalSatoshisAlice),
|
|
int64(aliceChannel.Capacity))
|
|
}
|
|
totalSatoshisBob := (bobChannel.channelState.LocalCommitment.LocalBalance +
|
|
bobChannel.channelState.LocalCommitment.RemoteBalance +
|
|
lnwire.NewMSatFromSatoshis(bobChannel.channelState.LocalCommitment.CommitFee))
|
|
if totalSatoshisBob+htlcAmount != lnwire.NewMSatFromSatoshis(bobChannel.Capacity) {
|
|
t.Fatalf("bob's funds leaked: total satoshis are %v, but channel "+
|
|
"capacity is %v", int64(totalSatoshisBob),
|
|
int64(bobChannel.Capacity))
|
|
}
|
|
|
|
// The commitment fee paid should be the same, as there have been no
|
|
// new material outputs added.
|
|
defaultFee := calcStaticFee(0)
|
|
if aliceChannel.channelState.LocalCommitment.CommitFee != defaultFee {
|
|
t.Fatalf("dust htlc amounts not subtracted from commitment fee "+
|
|
"expected %v, got %v", defaultFee,
|
|
aliceChannel.channelState.LocalCommitment.CommitFee)
|
|
}
|
|
if bobChannel.channelState.LocalCommitment.CommitFee != defaultFee {
|
|
t.Fatalf("dust htlc amounts not subtracted from commitment fee "+
|
|
"expected %v, got %v", defaultFee,
|
|
bobChannel.channelState.LocalCommitment.CommitFee)
|
|
}
|
|
|
|
// Alice's final balance should reflect the HTLC deficit even though
|
|
// the HTLC was paid to fees as it was trimmed.
|
|
aliceEndBalance := aliceChannel.channelState.LocalCommitment.LocalBalance
|
|
aliceExpectedBalance := aliceStartingBalance - htlcAmount
|
|
if aliceEndBalance != aliceExpectedBalance {
|
|
t.Fatalf("alice not credited for dust: expected %v, got %v",
|
|
aliceExpectedBalance, aliceEndBalance)
|
|
}
|
|
}
|
|
|
|
// TestHTLCDustLimit checks the situation in which an HTLC is larger than one
|
|
// channel participant's dust limit, but smaller than the other participant's
|
|
// dust limit. In this case, the participants' commitment chains will diverge.
|
|
// In one commitment chain, the HTLC will be added as normal, in the other
|
|
// chain, the amount of the HTLC will contribute to the fees to be paid.
|
|
func TestHTLCDustLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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 amount of the HTLC should be above Alice's dust limit and below
|
|
// Bob's dust limit.
|
|
htlcSat := (btcutil.Amount(500) + htlcTimeoutFee(
|
|
SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw)))
|
|
htlcAmount := lnwire.NewMSatFromSatoshis(htlcSat)
|
|
|
|
htlc, preimage := createHTLC(0, htlcAmount)
|
|
aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to receive htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("Can't update the channel state: %v", err)
|
|
}
|
|
|
|
// At this point, Alice's commitment transaction should have an HTLC,
|
|
// while Bob's should not, because the value falls beneath his dust
|
|
// limit. The amount of the HTLC should be applied to fees in Bob's
|
|
// commitment transaction.
|
|
aliceCommitment := aliceChannel.localCommitChain.tip()
|
|
if len(aliceCommitment.txn.TxOut) != 3 {
|
|
t.Fatalf("incorrect # of outputs: expected %v, got %v",
|
|
3, len(aliceCommitment.txn.TxOut))
|
|
}
|
|
bobCommitment := bobChannel.localCommitChain.tip()
|
|
if len(bobCommitment.txn.TxOut) != 2 {
|
|
t.Fatalf("incorrect # of outputs: expected %v, got %v",
|
|
2, len(bobCommitment.txn.TxOut))
|
|
}
|
|
defaultFee := calcStaticFee(0)
|
|
if bobChannel.channelState.LocalCommitment.CommitFee != defaultFee {
|
|
t.Fatalf("dust htlc amount was subtracted from commitment fee "+
|
|
"expected %v, got %v", defaultFee,
|
|
bobChannel.channelState.LocalCommitment.CommitFee)
|
|
}
|
|
|
|
// Settle HTLC and create a new commitment state.
|
|
err = bobChannel.SettleHTLC(preimage, bobHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to settle inbound htlc: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveHTLCSettle(preimage, aliceHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("state transition error: %v", err)
|
|
}
|
|
|
|
// At this point, for Alice's commitment chains, the value of the HTLC
|
|
// should have been added to Alice's balance and TotalSatoshisSent.
|
|
commitment := aliceChannel.localCommitChain.tip()
|
|
if len(commitment.txn.TxOut) != 2 {
|
|
t.Fatalf("incorrect # of outputs: expected %v, got %v",
|
|
2, len(commitment.txn.TxOut))
|
|
}
|
|
if aliceChannel.channelState.TotalMSatSent != htlcAmount {
|
|
t.Fatalf("alice satoshis sent incorrect: expected %v, got %v",
|
|
htlcAmount, aliceChannel.channelState.TotalMSatSent)
|
|
}
|
|
}
|
|
|
|
// TestHTLCSigNumber tests that a received commitment is only accepted if it
|
|
// comes with the exact number of valid HTLC signatures.
|
|
func TestHTLCSigNumber(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// createChanWithHTLC is a helper method that sets ut two channels, and
|
|
// adds HTLCs with the passed values to the channels.
|
|
createChanWithHTLC := func(htlcValues ...btcutil.Amount) (
|
|
*LightningChannel, *LightningChannel, func()) {
|
|
|
|
// Create a test channel funded evenly with Alice having 5 BTC,
|
|
// and Bob having 5 BTC. Alice's dustlimit is 200 sat, while
|
|
// Bob has 1300 sat.
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
|
|
for i, htlcSat := range htlcValues {
|
|
htlcMsat := lnwire.NewMSatFromSatoshis(htlcSat)
|
|
htlc, _ := createHTLC(i, htlcMsat)
|
|
_, err := aliceChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
_, err = bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to receive htlc: %v", err)
|
|
}
|
|
}
|
|
|
|
return aliceChannel, bobChannel, cleanUp
|
|
}
|
|
|
|
// Calculate two values that will be below and above Bob's dust limit.
|
|
estimator := &StaticFeeEstimator{24}
|
|
feePerVSize, err := estimator.EstimateFeePerVSize(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to get fee: %v", err)
|
|
}
|
|
feePerKw := feePerVSize.FeePerKWeight()
|
|
|
|
belowDust := btcutil.Amount(500) + htlcTimeoutFee(feePerKw)
|
|
aboveDust := btcutil.Amount(1400) + htlcSuccessFee(feePerKw)
|
|
|
|
// ===================================================================
|
|
// Test that Bob will reject a commitment if Alice doesn't send enough
|
|
// HTLC signatures.
|
|
// ===================================================================
|
|
aliceChannel, bobChannel, cleanUp := createChanWithHTLC(aboveDust,
|
|
aboveDust)
|
|
defer cleanUp()
|
|
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("Error signing next commitment: %v", err)
|
|
}
|
|
|
|
if len(aliceHtlcSigs) != 2 {
|
|
t.Fatalf("expected 2 htlc sig, instead got %v",
|
|
len(aliceHtlcSigs))
|
|
}
|
|
|
|
// Now discard one signature from the htlcSig slice. Bob should reject
|
|
// the commitment because of this.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs[1:])
|
|
if err == nil {
|
|
t.Fatalf("Expected Bob to reject signatures")
|
|
}
|
|
|
|
// ===================================================================
|
|
// Test that Bob will reject a commitment if Alice doesn't send any
|
|
// HTLC signatures.
|
|
// ===================================================================
|
|
aliceChannel, bobChannel, cleanUp = createChanWithHTLC(aboveDust)
|
|
defer cleanUp()
|
|
|
|
aliceSig, aliceHtlcSigs, err = aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("Error signing next commitment: %v", err)
|
|
}
|
|
|
|
if len(aliceHtlcSigs) != 1 {
|
|
t.Fatalf("expected 1 htlc sig, instead got %v",
|
|
len(aliceHtlcSigs))
|
|
}
|
|
|
|
// Now just give Bob an empty htlcSig slice. He should reject the
|
|
// commitment because of this.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, []lnwire.Sig{})
|
|
if err == nil {
|
|
t.Fatalf("Expected Bob to reject signatures")
|
|
}
|
|
|
|
// ==============================================================
|
|
// Test that sigs are not returned for HTLCs below dust limit.
|
|
// ==============================================================
|
|
aliceChannel, bobChannel, cleanUp = createChanWithHTLC(belowDust)
|
|
defer cleanUp()
|
|
|
|
aliceSig, aliceHtlcSigs, err = aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("Error signing next commitment: %v", err)
|
|
}
|
|
|
|
// Since the HTLC is below Bob's dust limit, Alice won't need to send
|
|
// any signatures for this HTLC.
|
|
if len(aliceHtlcSigs) != 0 {
|
|
t.Fatalf("expected no htlc sigs, instead got %v",
|
|
len(aliceHtlcSigs))
|
|
}
|
|
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("Bob failed receiving commitment: %v", err)
|
|
}
|
|
|
|
// ================================================================
|
|
// Test that sigs are correctly returned for HTLCs above dust limit.
|
|
// ================================================================
|
|
aliceChannel, bobChannel, cleanUp = createChanWithHTLC(aboveDust)
|
|
defer cleanUp()
|
|
|
|
aliceSig, aliceHtlcSigs, err = aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("Error signing next commitment: %v", err)
|
|
}
|
|
|
|
// Since the HTLC is above Bob's dust limit, Alice should send a
|
|
// signature for this HTLC.
|
|
if len(aliceHtlcSigs) != 1 {
|
|
t.Fatalf("expected 1 htlc sig, instead got %v",
|
|
len(aliceHtlcSigs))
|
|
}
|
|
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("Bob failed receiving commitment: %v", err)
|
|
}
|
|
|
|
// ====================================================================
|
|
// Test that Bob will not validate a received commitment if Alice sends
|
|
// signatures for HTLCs below the dust limit.
|
|
// ====================================================================
|
|
aliceChannel, bobChannel, cleanUp = createChanWithHTLC(belowDust,
|
|
aboveDust)
|
|
defer cleanUp()
|
|
|
|
// Alice should produce only one signature, since one HTLC is below
|
|
// dust.
|
|
aliceSig, aliceHtlcSigs, err = aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("Error signing next commitment: %v", err)
|
|
}
|
|
|
|
if len(aliceHtlcSigs) != 1 {
|
|
t.Fatalf("expected 1 htlc sig, instead got %v",
|
|
len(aliceHtlcSigs))
|
|
}
|
|
|
|
// Add an extra signature.
|
|
aliceHtlcSigs = append(aliceHtlcSigs, aliceHtlcSigs[0])
|
|
|
|
// Bob should reject these signatures since they don't match the number
|
|
// of HTLCs above dust.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err == nil {
|
|
t.Fatalf("Expected Bob to reject signatures")
|
|
}
|
|
}
|
|
|
|
// TestChannelBalanceDustLimit tests the condition when the remaining balance
|
|
// for one of the channel participants is so small as to be considered dust. In
|
|
// this case, the output for that participant is removed and all funds (minus
|
|
// fees) in the commitment transaction are allocated to the remaining channel
|
|
// participant.
|
|
//
|
|
// TODO(roasbeef): test needs to be fixed after reserve limits are done
|
|
func TestChannelBalanceDustLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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()
|
|
|
|
// To allow Alice's balance to get beneath her dust limit, set the
|
|
// channel reserve to be 0.
|
|
aliceChannel.localChanCfg.ChanReserve = 0
|
|
bobChannel.remoteChanCfg.ChanReserve = 0
|
|
|
|
// This amount should leave an amount larger than Alice's dust limit
|
|
// once fees have been subtracted, but smaller than Bob's dust limit.
|
|
// We account in fees for the HTLC we will be adding.
|
|
defaultFee := calcStaticFee(1)
|
|
aliceBalance := aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis()
|
|
htlcSat := aliceBalance - defaultFee
|
|
htlcSat += htlcSuccessFee(
|
|
SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw),
|
|
)
|
|
|
|
htlcAmount := lnwire.NewMSatFromSatoshis(htlcSat)
|
|
|
|
htlc, preimage := createHTLC(0, htlcAmount)
|
|
aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to receive htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("state transition error: %v", err)
|
|
}
|
|
err = bobChannel.SettleHTLC(preimage, bobHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to settle inbound htlc: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveHTLCSettle(preimage, aliceHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("state transition error: %v", err)
|
|
}
|
|
|
|
// At the conclusion of this test, in Bob's commitment chains, the
|
|
// output for Alice's balance should have been removed as dust, leaving
|
|
// only a single output that will send the remaining funds in the
|
|
// channel to Bob.
|
|
commitment := bobChannel.localCommitChain.tip()
|
|
if len(commitment.txn.TxOut) != 1 {
|
|
t.Fatalf("incorrect # of outputs: expected %v, got %v",
|
|
1, len(commitment.txn.TxOut))
|
|
}
|
|
if aliceChannel.channelState.TotalMSatSent != htlcAmount {
|
|
t.Fatalf("alice satoshis sent incorrect: expected %v, got %v",
|
|
htlcAmount, aliceChannel.channelState.TotalMSatSent)
|
|
}
|
|
}
|
|
|
|
func TestStateUpdatePersistence(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
const numHtlcs = 4
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(5000)
|
|
|
|
var fakeOnionBlob [lnwire.OnionPacketSize]byte
|
|
copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize))
|
|
|
|
// Alice adds 3 HTLCs 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 := sha256.Sum256(alicePreimage[:])
|
|
h := &lnwire.UpdateAddHTLC{
|
|
ID: uint64(i),
|
|
PaymentHash: rHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(10),
|
|
OnionBlob: fakeOnionBlob,
|
|
}
|
|
|
|
if _, err := aliceChannel.AddHTLC(h, nil); err != nil {
|
|
t.Fatalf("unable to add alice's htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(h); err != nil {
|
|
t.Fatalf("unable to recv alice's htlc: %v", err)
|
|
}
|
|
}
|
|
rHash := sha256.Sum256(bobPreimage[:])
|
|
bobh := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(10),
|
|
OnionBlob: fakeOnionBlob,
|
|
}
|
|
if _, err := bobChannel.AddHTLC(bobh, nil); err != nil {
|
|
t.Fatalf("unable to add bob's htlc: %v", err)
|
|
}
|
|
if _, err := aliceChannel.ReceiveHTLC(bobh); err != nil {
|
|
t.Fatalf("unable to recv bob's htlc: %v", err)
|
|
}
|
|
|
|
// Next, Alice initiates a state transition to include the HTLC's she
|
|
// added above in a new commitment state.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete alice's state transition: %v", err)
|
|
}
|
|
|
|
// Since the HTLC Bob sent wasn't included in Bob's version of the
|
|
// commitment transaction (but it was in Alice's, as he ACK'd her
|
|
// changes before creating a new state), Bob needs to trigger another
|
|
// state update in order to re-sync their states.
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
|
}
|
|
|
|
// The latest commitment from both sides should have all the HTLCs.
|
|
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)
|
|
}
|
|
|
|
// TODO(roasbeef): also ensure signatures were stored
|
|
// * ensure expiry matches
|
|
|
|
// 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)
|
|
}
|
|
aliceChannelNew, err := NewLightningChannel(
|
|
aliceChannel.signer, nil, aliceChannels[0],
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create new channel: %v", err)
|
|
}
|
|
bobChannelNew, err := NewLightningChannel(
|
|
bobChannel.signer, nil, bobChannels[0],
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create new channel: %v", err)
|
|
}
|
|
|
|
// The state update logs of the new channels and the old channels
|
|
// should now be identical other than the height the HTLCs were added.
|
|
if aliceChannel.localUpdateLog.logIndex !=
|
|
aliceChannelNew.localUpdateLog.logIndex {
|
|
t.Fatalf("alice log counter: expected %v, got %v",
|
|
aliceChannel.localUpdateLog.logIndex,
|
|
aliceChannelNew.localUpdateLog.logIndex)
|
|
}
|
|
if aliceChannel.remoteUpdateLog.logIndex !=
|
|
aliceChannelNew.remoteUpdateLog.logIndex {
|
|
t.Fatalf("alice log counter: expected %v, got %v",
|
|
aliceChannel.remoteUpdateLog.logIndex,
|
|
aliceChannelNew.remoteUpdateLog.logIndex)
|
|
}
|
|
if aliceChannel.localUpdateLog.Len() !=
|
|
aliceChannelNew.localUpdateLog.Len() {
|
|
t.Fatalf("alice log len: expected %v, got %v",
|
|
aliceChannel.localUpdateLog.Len(),
|
|
aliceChannelNew.localUpdateLog.Len())
|
|
}
|
|
if aliceChannel.remoteUpdateLog.Len() !=
|
|
aliceChannelNew.remoteUpdateLog.Len() {
|
|
t.Fatalf("alice log len: expected %v, got %v",
|
|
aliceChannel.remoteUpdateLog.Len(),
|
|
aliceChannelNew.remoteUpdateLog.Len())
|
|
}
|
|
if bobChannel.localUpdateLog.logIndex !=
|
|
bobChannelNew.localUpdateLog.logIndex {
|
|
t.Fatalf("bob log counter: expected %v, got %v",
|
|
bobChannel.localUpdateLog.logIndex,
|
|
bobChannelNew.localUpdateLog.logIndex)
|
|
}
|
|
if bobChannel.remoteUpdateLog.logIndex !=
|
|
bobChannelNew.remoteUpdateLog.logIndex {
|
|
t.Fatalf("bob log counter: expected %v, got %v",
|
|
bobChannel.remoteUpdateLog.logIndex,
|
|
bobChannelNew.remoteUpdateLog.logIndex)
|
|
}
|
|
if bobChannel.localUpdateLog.Len() !=
|
|
bobChannelNew.localUpdateLog.Len() {
|
|
t.Fatalf("bob log len: expected %v, got %v",
|
|
bobChannel.localUpdateLog.Len(),
|
|
bobChannelNew.localUpdateLog.Len())
|
|
}
|
|
if bobChannel.remoteUpdateLog.Len() !=
|
|
bobChannelNew.remoteUpdateLog.Len() {
|
|
t.Fatalf("bob log len: expected %v, got %v",
|
|
bobChannel.remoteUpdateLog.Len(),
|
|
bobChannelNew.remoteUpdateLog.Len())
|
|
}
|
|
|
|
// TODO(roasbeef): expand test to also ensure state revocation log has
|
|
// proper pk scripts
|
|
|
|
// Newly generated pkScripts for HTLCs should be the same as in the old channel.
|
|
for _, entry := range aliceChannel.localUpdateLog.htlcIndex {
|
|
htlc := entry.Value.(*PaymentDescriptor)
|
|
restoredHtlc := aliceChannelNew.localUpdateLog.lookupHtlc(htlc.HtlcIndex)
|
|
if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) {
|
|
t.Fatalf("alice ourPkScript in ourLog: expected %X, got %X",
|
|
htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5])
|
|
}
|
|
if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) {
|
|
t.Fatalf("alice theirPkScript in ourLog: expected %X, got %X",
|
|
htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5])
|
|
}
|
|
}
|
|
for _, entry := range aliceChannel.remoteUpdateLog.htlcIndex {
|
|
htlc := entry.Value.(*PaymentDescriptor)
|
|
restoredHtlc := aliceChannelNew.remoteUpdateLog.lookupHtlc(htlc.HtlcIndex)
|
|
if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) {
|
|
t.Fatalf("alice ourPkScript in theirLog: expected %X, got %X",
|
|
htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5])
|
|
}
|
|
if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) {
|
|
t.Fatalf("alice theirPkScript in theirLog: expected %X, got %X",
|
|
htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5])
|
|
}
|
|
}
|
|
for _, entry := range bobChannel.localUpdateLog.htlcIndex {
|
|
htlc := entry.Value.(*PaymentDescriptor)
|
|
restoredHtlc := bobChannelNew.localUpdateLog.lookupHtlc(htlc.HtlcIndex)
|
|
if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) {
|
|
t.Fatalf("bob ourPkScript in ourLog: expected %X, got %X",
|
|
htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5])
|
|
}
|
|
if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) {
|
|
t.Fatalf("bob theirPkScript in ourLog: expected %X, got %X",
|
|
htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5])
|
|
}
|
|
}
|
|
for _, entry := range bobChannel.remoteUpdateLog.htlcIndex {
|
|
htlc := entry.Value.(*PaymentDescriptor)
|
|
restoredHtlc := bobChannelNew.remoteUpdateLog.lookupHtlc(htlc.HtlcIndex)
|
|
if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) {
|
|
t.Fatalf("bob ourPkScript in theirLog: expected %X, got %X",
|
|
htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5])
|
|
}
|
|
if !bytes.Equal(htlc.theirPkScript, restoredHtlc.theirPkScript) {
|
|
t.Fatalf("bob theirPkScript in theirLog: expected %X, got %X",
|
|
htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5])
|
|
}
|
|
}
|
|
|
|
// Now settle all the HTLCs, then force a state update. The state
|
|
// update should succeed as both sides have identical.
|
|
for i := 0; i < 3; i++ {
|
|
err := bobChannelNew.SettleHTLC(alicePreimage, uint64(i), nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc #%v: %v", i, err)
|
|
}
|
|
err = aliceChannelNew.ReceiveHTLCSettle(alicePreimage, uint64(i))
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc#%v: %v", i, err)
|
|
}
|
|
}
|
|
err = aliceChannelNew.SettleHTLC(bobPreimage, 0, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
err = bobChannelNew.ReceiveHTLCSettle(bobPreimage, 0)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
|
|
// Similar to the two transitions above, as both Bob and Alice added
|
|
// entries to the update log before a state transition was initiated by
|
|
// either side, both sides are required to trigger an update in order
|
|
// to lock in their changes.
|
|
if err := forceStateTransition(aliceChannelNew, bobChannelNew); err != nil {
|
|
t.Fatalf("unable to update commitments: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannelNew, aliceChannelNew); err != nil {
|
|
t.Fatalf("unable to update commitments: %v", err)
|
|
}
|
|
|
|
// The amounts transferred should been updated as per the amounts in
|
|
// the HTLCs
|
|
if aliceChannelNew.channelState.TotalMSatSent != htlcAmt*3 {
|
|
t.Fatalf("expected %v alice satoshis sent, got %v",
|
|
htlcAmt*3, aliceChannelNew.channelState.TotalMSatSent)
|
|
}
|
|
if aliceChannelNew.channelState.TotalMSatReceived != htlcAmt {
|
|
t.Fatalf("expected %v alice satoshis received, got %v",
|
|
htlcAmt, aliceChannelNew.channelState.TotalMSatReceived)
|
|
}
|
|
if bobChannelNew.channelState.TotalMSatSent != htlcAmt {
|
|
t.Fatalf("expected %v bob satoshis sent, got %v",
|
|
htlcAmt, bobChannel.channelState.TotalMSatSent)
|
|
}
|
|
if bobChannelNew.channelState.TotalMSatReceived != htlcAmt*3 {
|
|
t.Fatalf("expected %v bob satoshis sent, got %v",
|
|
htlcAmt*3, bobChannel.channelState.TotalMSatReceived)
|
|
}
|
|
|
|
// As a final test, we'll ensure that the HTLC counters for both sides
|
|
// has been persisted properly. If we instruct Alice to add a new HTLC,
|
|
// it should have an index of 3. If we instruct Bob to do the
|
|
// same, it should have an index of 1.
|
|
aliceHtlcIndex, err := aliceChannel.AddHTLC(bobh, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if aliceHtlcIndex != 3 {
|
|
t.Fatalf("wrong htlc index: expected %v, got %v", 3, aliceHtlcIndex)
|
|
}
|
|
bobHtlcIndex, err := bobChannel.AddHTLC(bobh, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if bobHtlcIndex != 1 {
|
|
t.Fatalf("wrong htlc index: expected %v, got %v", 1, aliceHtlcIndex)
|
|
}
|
|
}
|
|
|
|
func TestCancelHTLC(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(5)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// Add a new HTLC from Alice to Bob, then trigger a new state
|
|
// transition in order to include it in the latest state.
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
|
|
|
var preImage [32]byte
|
|
copy(preImage[:], bytes.Repeat([]byte{0xaa}, 32))
|
|
htlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: sha256.Sum256(preImage[:]),
|
|
Amount: htlcAmt,
|
|
Expiry: 10,
|
|
}
|
|
|
|
aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add alice htlc: %v", err)
|
|
}
|
|
bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to add bob htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to create new commitment state: %v", err)
|
|
}
|
|
|
|
// With the HTLC committed, Alice's balance should reflect the clearing
|
|
// of the new HTLC.
|
|
aliceExpectedBalance := btcutil.Amount(btcutil.SatoshiPerBitcoin*4) -
|
|
calcStaticFee(1)
|
|
if aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis() !=
|
|
aliceExpectedBalance {
|
|
t.Fatalf("Alice's balance is wrong: expected %v, got %v",
|
|
aliceExpectedBalance,
|
|
aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis())
|
|
}
|
|
|
|
// Now, with the HTLC committed on both sides, trigger a cancellation
|
|
// from Bob to Alice, removing the HTLC.
|
|
err = bobChannel.FailHTLC(bobHtlcIndex, []byte("failreason"), nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to cancel HTLC: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveFailHTLC(aliceHtlcIndex, []byte("bad"))
|
|
if err != nil {
|
|
t.Fatalf("unable to recv htlc cancel: %v", err)
|
|
}
|
|
|
|
// Now trigger another state transition, the HTLC should now be removed
|
|
// from both sides, with balances reflected.
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to create new commitment: %v", err)
|
|
}
|
|
|
|
// Now HTLCs should be present on the commitment transaction for either
|
|
// side.
|
|
if len(aliceChannel.localCommitChain.tip().outgoingHTLCs) != 0 ||
|
|
len(aliceChannel.remoteCommitChain.tip().outgoingHTLCs) != 0 {
|
|
t.Fatalf("htlc's still active from alice's POV")
|
|
}
|
|
if len(aliceChannel.localCommitChain.tip().incomingHTLCs) != 0 ||
|
|
len(aliceChannel.remoteCommitChain.tip().incomingHTLCs) != 0 {
|
|
t.Fatalf("htlc's still active from alice's POV")
|
|
}
|
|
if len(bobChannel.localCommitChain.tip().outgoingHTLCs) != 0 ||
|
|
len(bobChannel.remoteCommitChain.tip().outgoingHTLCs) != 0 {
|
|
t.Fatalf("htlc's still active from bob's POV")
|
|
}
|
|
if len(bobChannel.localCommitChain.tip().incomingHTLCs) != 0 ||
|
|
len(bobChannel.remoteCommitChain.tip().incomingHTLCs) != 0 {
|
|
t.Fatalf("htlc's still active from bob's POV")
|
|
}
|
|
|
|
expectedBalance := btcutil.Amount(btcutil.SatoshiPerBitcoin * 5)
|
|
if aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis() !=
|
|
expectedBalance-calcStaticFee(0) {
|
|
|
|
t.Fatalf("balance is wrong: expected %v, got %v",
|
|
aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis(),
|
|
expectedBalance-calcStaticFee(0))
|
|
}
|
|
if aliceChannel.channelState.LocalCommitment.RemoteBalance.ToSatoshis() !=
|
|
expectedBalance {
|
|
|
|
t.Fatalf("balance is wrong: expected %v, got %v",
|
|
aliceChannel.channelState.LocalCommitment.RemoteBalance.ToSatoshis(),
|
|
expectedBalance)
|
|
}
|
|
if bobChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis() !=
|
|
expectedBalance {
|
|
|
|
t.Fatalf("balance is wrong: expected %v, got %v",
|
|
bobChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis(),
|
|
expectedBalance)
|
|
}
|
|
if bobChannel.channelState.LocalCommitment.RemoteBalance.ToSatoshis() !=
|
|
expectedBalance-calcStaticFee(0) {
|
|
|
|
t.Fatalf("balance is wrong: expected %v, got %v",
|
|
bobChannel.channelState.LocalCommitment.RemoteBalance.ToSatoshis(),
|
|
expectedBalance-calcStaticFee(0))
|
|
}
|
|
}
|
|
|
|
func TestCooperativeCloseDustAdherence(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(5)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
aliceFeeRate := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw)
|
|
bobFeeRate := SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw)
|
|
|
|
setDustLimit := func(dustVal btcutil.Amount) {
|
|
aliceChannel.channelState.LocalChanCfg.DustLimit = dustVal
|
|
aliceChannel.channelState.RemoteChanCfg.DustLimit = dustVal
|
|
bobChannel.channelState.LocalChanCfg.DustLimit = dustVal
|
|
bobChannel.channelState.RemoteChanCfg.DustLimit = dustVal
|
|
}
|
|
|
|
resetChannelState := func() {
|
|
aliceChannel.status = channelOpen
|
|
bobChannel.status = channelOpen
|
|
}
|
|
|
|
setBalances := func(aliceBalance, bobBalance lnwire.MilliSatoshi) {
|
|
aliceChannel.channelState.LocalCommitment.LocalBalance = aliceBalance
|
|
aliceChannel.channelState.LocalCommitment.RemoteBalance = bobBalance
|
|
bobChannel.channelState.LocalCommitment.LocalBalance = bobBalance
|
|
bobChannel.channelState.LocalCommitment.RemoteBalance = aliceBalance
|
|
}
|
|
|
|
aliceDeliveryScript := bobsPrivKey[:]
|
|
bobDeliveryScript := testHdSeed[:]
|
|
|
|
// We'll start be initializing the limit of both Alice and Bob to 10k
|
|
// satoshis.
|
|
dustLimit := btcutil.Amount(10000)
|
|
setDustLimit(dustLimit)
|
|
|
|
// Both sides currently have over 1 BTC settled as part of their
|
|
// balances. As a result, performing a cooperative closure now result
|
|
// in both sides having an output within the closure transaction.
|
|
aliceFee := btcutil.Amount(aliceChannel.CalcFee(aliceFeeRate)) + 1000
|
|
aliceSig, _, _, err := aliceChannel.CreateCloseProposal(aliceFee,
|
|
aliceDeliveryScript, bobDeliveryScript)
|
|
if err != nil {
|
|
t.Fatalf("unable to close channel: %v", err)
|
|
}
|
|
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
|
|
|
|
bobFee := btcutil.Amount(bobChannel.CalcFee(bobFeeRate)) + 1000
|
|
bobSig, _, _, err := bobChannel.CreateCloseProposal(bobFee,
|
|
bobDeliveryScript, aliceDeliveryScript)
|
|
if err != nil {
|
|
t.Fatalf("unable to close channel: %v", err)
|
|
}
|
|
bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
|
|
|
|
closeTx, _, err := bobChannel.CompleteCooperativeClose(
|
|
bobCloseSig, aliceCloseSig,
|
|
bobDeliveryScript, aliceDeliveryScript, bobFee)
|
|
if err != nil {
|
|
t.Fatalf("unable to accept channel close: %v", err)
|
|
}
|
|
|
|
// The closure transaction should have exactly two outputs.
|
|
if len(closeTx.TxOut) != 2 {
|
|
t.Fatalf("close tx has wrong number of outputs: expected %v "+
|
|
"got %v", 2, len(closeTx.TxOut))
|
|
}
|
|
|
|
// We'll reset the channel states before proceeding to our nest test.
|
|
resetChannelState()
|
|
|
|
// Next we'll modify the current balances and dust limits such that
|
|
// Bob's current balance is above _below_ his dust limit.
|
|
aliceBal := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
|
bobBal := lnwire.NewMSatFromSatoshis(250)
|
|
setBalances(aliceBal, bobBal)
|
|
|
|
// Attempt another cooperative channel closure. It should succeed
|
|
// without any issues.
|
|
aliceSig, _, _, err = aliceChannel.CreateCloseProposal(aliceFee,
|
|
aliceDeliveryScript, bobDeliveryScript)
|
|
if err != nil {
|
|
t.Fatalf("unable to close channel: %v", err)
|
|
}
|
|
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
|
|
|
|
bobSig, _, _, err = bobChannel.CreateCloseProposal(bobFee,
|
|
bobDeliveryScript, aliceDeliveryScript)
|
|
if err != nil {
|
|
t.Fatalf("unable to close channel: %v", err)
|
|
}
|
|
bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
|
|
|
|
closeTx, _, err = bobChannel.CompleteCooperativeClose(
|
|
bobCloseSig, aliceCloseSig,
|
|
bobDeliveryScript, aliceDeliveryScript, bobFee)
|
|
if err != nil {
|
|
t.Fatalf("unable to accept channel close: %v", err)
|
|
}
|
|
|
|
// The closure transaction should only have a single output, and that
|
|
// output should be Alice's balance.
|
|
if len(closeTx.TxOut) != 1 {
|
|
t.Fatalf("close tx has wrong number of outputs: expected %v "+
|
|
"got %v", 1, len(closeTx.TxOut))
|
|
}
|
|
commitFee := aliceChannel.channelState.LocalCommitment.CommitFee
|
|
aliceExpectedBalance := aliceBal.ToSatoshis() - aliceFee + commitFee
|
|
if closeTx.TxOut[0].Value != int64(aliceExpectedBalance) {
|
|
t.Fatalf("alice's balance is incorrect: expected %v, got %v",
|
|
aliceExpectedBalance,
|
|
int64(closeTx.TxOut[0].Value))
|
|
}
|
|
|
|
// Finally, we'll modify the current balances and dust limits such that
|
|
// Alice's current balance is _below_ his her limit.
|
|
setBalances(bobBal, aliceBal)
|
|
resetChannelState()
|
|
|
|
// Our final attempt at another cooperative channel closure. It should
|
|
// succeed without any issues.
|
|
aliceSig, _, _, err = aliceChannel.CreateCloseProposal(
|
|
aliceFee, aliceDeliveryScript, bobDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to close channel: %v", err)
|
|
}
|
|
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
|
|
|
|
bobSig, _, _, err = bobChannel.CreateCloseProposal(
|
|
bobFee, bobDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to close channel: %v", err)
|
|
}
|
|
bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
|
|
|
|
closeTx, _, err = bobChannel.CompleteCooperativeClose(
|
|
bobCloseSig, aliceCloseSig,
|
|
bobDeliveryScript, aliceDeliveryScript, bobFee)
|
|
if err != nil {
|
|
t.Fatalf("unable to accept channel close: %v", err)
|
|
}
|
|
|
|
// The closure transaction should only have a single output, and that
|
|
// output should be Bob's balance.
|
|
if len(closeTx.TxOut) != 1 {
|
|
t.Fatalf("close tx has wrong number of outputs: expected %v "+
|
|
"got %v", 1, len(closeTx.TxOut))
|
|
}
|
|
if closeTx.TxOut[0].Value != int64(aliceBal.ToSatoshis()) {
|
|
t.Fatalf("bob's balance is incorrect: expected %v, got %v",
|
|
aliceBal.ToSatoshis(), closeTx.TxOut[0].Value)
|
|
}
|
|
}
|
|
|
|
// TestUpdateFeeFail tests that the signature verification will fail if they
|
|
// fee updates are out of sync.
|
|
func TestUpdateFeeFail(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// Bob receives the update, that will apply to his commitment
|
|
// transaction.
|
|
bobChannel.ReceiveUpdateFee(111)
|
|
|
|
// Alice sends signature for commitment that does not cover any fee
|
|
// update.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to sign commitment: %v", err)
|
|
}
|
|
|
|
// Bob verifies this commit, meaning that he checks that it is
|
|
// consistent everything he has received. This should fail, since he got
|
|
// the fee update, but Alice never sent it.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err == nil {
|
|
t.Fatalf("expected bob to fail receiving alice's signature")
|
|
}
|
|
|
|
}
|
|
|
|
// TestUpdateFeeSenderCommits verifies that the state machine progresses as
|
|
// expected if we send a fee update, and then the sender of the fee update
|
|
// sends a commitment signature.
|
|
func TestUpdateFeeSenderCommits(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
paymentPreimage := bytes.Repeat([]byte{1}, 32)
|
|
paymentHash := sha256.Sum256(paymentPreimage)
|
|
htlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: paymentHash,
|
|
Amount: btcutil.SatoshiPerBitcoin,
|
|
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
|
|
// adds this htlc to his remote state update log.
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Simulate Alice sending update fee message to bob.
|
|
fee := SatPerKWeight(111)
|
|
aliceChannel.UpdateFee(fee)
|
|
bobChannel.ReceiveUpdateFee(fee)
|
|
|
|
// Alice signs a commitment, which will cover everything sent to Bob
|
|
// (the HTLC and the fee update), and everything acked by Bob (nothing
|
|
// so far).
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to sign commitment: %v", err)
|
|
}
|
|
|
|
// Bob receives this signature message, and verifies that it is
|
|
// consistent with the state he had for Alice, including the received
|
|
// HTLC and fee update.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's new commitment: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee {
|
|
t.Fatalf("bob's feePerKw was unexpectedly locked in")
|
|
}
|
|
|
|
// Bob can revoke the prior commitment he had. This should lock in the
|
|
// fee update for him.
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate bob revocation: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != fee {
|
|
t.Fatalf("bob's feePerKw was not locked in")
|
|
}
|
|
|
|
// Bob commits to all updates he has received from Alice. This includes
|
|
// the HTLC he received, and the fee update.
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign alice's commitment: %v", err)
|
|
}
|
|
|
|
// Alice receives the revocation of the old one, and can now assume
|
|
// that Bob's received everything up to the signature she sent,
|
|
// including the HTLC and fee update.
|
|
if _, _, _, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
|
|
t.Fatalf("alice unable to process bob's revocation: %v", err)
|
|
}
|
|
|
|
// Alice receives new signature from Bob, and assumes this covers the
|
|
// changes.
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) == fee {
|
|
t.Fatalf("alice's feePerKw was unexpectedly locked in")
|
|
}
|
|
|
|
// Alice can revoke the old commitment, which will lock in the fee
|
|
// update.
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to revoke alice channel: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) != fee {
|
|
t.Fatalf("alice's feePerKw was not locked in")
|
|
}
|
|
|
|
// Bob receives revocation from Alice.
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("bob unable to process alice's revocation: %v", err)
|
|
}
|
|
|
|
}
|
|
|
|
// TestUpdateFeeReceiverCommits tests that the state machine progresses as
|
|
// expected if we send a fee update, and then the receiver of the fee update
|
|
// sends a commitment signature.
|
|
func TestUpdateFeeReceiverCommits(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
paymentPreimage := bytes.Repeat([]byte{1}, 32)
|
|
paymentHash := sha256.Sum256(paymentPreimage)
|
|
htlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: paymentHash,
|
|
Amount: btcutil.SatoshiPerBitcoin,
|
|
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
|
|
// adds this htlc to his remote state update log.
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Simulate Alice sending update fee message to bob
|
|
fee := SatPerKWeight(111)
|
|
aliceChannel.UpdateFee(fee)
|
|
bobChannel.ReceiveUpdateFee(fee)
|
|
|
|
// Bob commits to every change he has sent since last time (none). He
|
|
// does not commit to the received HTLC and fee update, since Alice
|
|
// cannot know if he has received them.
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to sign commitment: %v", err)
|
|
}
|
|
|
|
// Alice receives this signature message, and verifies that it is
|
|
// consistent with the remote state, not including any of the updates.
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's new commitment: %v", err)
|
|
}
|
|
|
|
// Alice can revoke the prior commitment she had, this will ack
|
|
// everything received before last commitment signature, but in this
|
|
// case that is nothing.
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate bob revocation: %v", err)
|
|
}
|
|
|
|
// Bob receives the revocation of the old commitment
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("alice unable to process bob's revocation: %v", err)
|
|
}
|
|
|
|
// Alice will sign next commitment. Since she sent the revocation, she
|
|
// also ack'ed everything received, but in this case this is nothing.
|
|
// Since she sent the two updates, this signature will cover those two.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign alice's commitment: %v", err)
|
|
}
|
|
|
|
// Bob gets the signature for the new commitment from Alice. He assumes
|
|
// this covers everything received from alice, including the two updates.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee {
|
|
t.Fatalf("bob's feePerKw was unexpectedly locked in")
|
|
}
|
|
|
|
// Bob can revoke the old commitment. This will ack what he has
|
|
// received, including the HTLC and fee update. This will lock in the
|
|
// fee update for bob.
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to revoke alice channel: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != fee {
|
|
t.Fatalf("bob's feePerKw was not locked in")
|
|
}
|
|
|
|
// Bob will send a new signature, which will cover what he just acked:
|
|
// the HTLC and fee update.
|
|
bobSig, bobHtlcSigs, err = bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to sign commitment: %v", err)
|
|
}
|
|
|
|
// Alice receives revocation from Bob, and can now be sure that Bob
|
|
// received the two updates, and they are considered locked in.
|
|
if _, _, _, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
|
|
t.Fatalf("bob unable to process alice's revocation: %v", err)
|
|
}
|
|
|
|
// Alice will receive the signature from Bob, which will cover what was
|
|
// just acked by his revocation.
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) == fee {
|
|
t.Fatalf("alice's feePerKw was unexpectedly locked in")
|
|
}
|
|
|
|
// After Alice now revokes her old commitment, the fee update should
|
|
// lock in.
|
|
aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate bob revocation: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) != fee {
|
|
t.Fatalf("Alice's feePerKw was not locked in")
|
|
}
|
|
|
|
// Bob receives revocation from Alice.
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("bob unable to process alice's revocation: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestUpdateFeeReceiverSendsUpdate tests that receiving a fee update as channel
|
|
// initiator fails, and that trying to initiate fee update as non-initiation
|
|
// fails.
|
|
func TestUpdateFeeReceiverSendsUpdate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// Since Alice is the channel initiator, she should fail when receiving
|
|
// fee update
|
|
fee := SatPerKWeight(111)
|
|
err = aliceChannel.ReceiveUpdateFee(fee)
|
|
if err == nil {
|
|
t.Fatalf("expected alice to fail receiving fee update")
|
|
}
|
|
|
|
// Similarly, initiating fee update should fail for Bob.
|
|
err = bobChannel.UpdateFee(fee)
|
|
if err == nil {
|
|
t.Fatalf("expected bob to fail initiating fee update")
|
|
}
|
|
}
|
|
|
|
// Test that if multiple update fee messages are sent consecutively, then the
|
|
// last one is the one that is being committed to.
|
|
func TestUpdateFeeMultipleUpdates(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// Simulate Alice sending update fee message to bob.
|
|
fee1 := SatPerKWeight(111)
|
|
fee2 := SatPerKWeight(222)
|
|
fee := SatPerKWeight(333)
|
|
aliceChannel.UpdateFee(fee1)
|
|
aliceChannel.UpdateFee(fee2)
|
|
aliceChannel.UpdateFee(fee)
|
|
|
|
// Alice signs a commitment, which will cover everything sent to Bob
|
|
// (the HTLC and the fee update), and everything acked by Bob (nothing
|
|
// so far).
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to sign commitment: %v", err)
|
|
}
|
|
|
|
bobChannel.ReceiveUpdateFee(fee1)
|
|
bobChannel.ReceiveUpdateFee(fee2)
|
|
bobChannel.ReceiveUpdateFee(fee)
|
|
|
|
// Bob receives this signature message, and verifies that it is
|
|
// consistent with the state he had for Alice, including the received
|
|
// HTLC and fee update.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's new commitment: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee {
|
|
t.Fatalf("bob's feePerKw was unexpectedly locked in")
|
|
}
|
|
|
|
// Alice sending more fee updates now should not mess up the old fee
|
|
// they both committed to.
|
|
fee3 := SatPerKWeight(444)
|
|
fee4 := SatPerKWeight(555)
|
|
fee5 := SatPerKWeight(666)
|
|
aliceChannel.UpdateFee(fee3)
|
|
aliceChannel.UpdateFee(fee4)
|
|
aliceChannel.UpdateFee(fee5)
|
|
bobChannel.ReceiveUpdateFee(fee3)
|
|
bobChannel.ReceiveUpdateFee(fee4)
|
|
bobChannel.ReceiveUpdateFee(fee5)
|
|
|
|
// Bob can revoke the prior commitment he had. This should lock in the
|
|
// fee update for him.
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate bob revocation: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != fee {
|
|
t.Fatalf("bob's feePerKw was not locked in")
|
|
}
|
|
|
|
// Bob commits to all updates he has received from Alice. This includes
|
|
// the HTLC he received, and the fee update.
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign alice's commitment: %v", err)
|
|
}
|
|
|
|
// Alice receives the revocation of the old one, and can now assume that
|
|
// Bob's received everything up to the signature she sent, including the
|
|
// HTLC and fee update.
|
|
if _, _, _, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil {
|
|
t.Fatalf("alice unable to process bob's revocation: %v", err)
|
|
}
|
|
|
|
// Alice receives new signature from Bob, and assumes this covers the
|
|
// changes.
|
|
if err := aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil {
|
|
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) == fee {
|
|
t.Fatalf("alice's feePerKw was unexpectedly locked in")
|
|
}
|
|
|
|
// Alice can revoke the old commitment, which will lock in the fee
|
|
// update.
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to revoke alice channel: %v", err)
|
|
}
|
|
|
|
if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) != fee {
|
|
t.Fatalf("alice's feePerKw was not locked in")
|
|
}
|
|
|
|
// Bob receives revocation from Alice.
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("bob unable to process alice's revocation: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestAddHTLCNegativeBalance tests that if enough HTLC's are added to the
|
|
// state machine to drive the balance to zero, then the next HTLC attempted to
|
|
// be added will result in an error being returned.
|
|
func TestAddHTLCNegativeBalance(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We'll kick off the test by creating our channels which both are
|
|
// loaded with 5 BTC each.
|
|
aliceChannel, _, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// We set the channel reserve to 0, such that we can add HTLCs all the
|
|
// way to a negative balance.
|
|
aliceChannel.localChanCfg.ChanReserve = 0
|
|
|
|
// First, we'll add 3 HTLCs of 1 BTC each to Alice's commitment.
|
|
const numHTLCs = 3
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
|
for i := 0; i < numHTLCs; i++ {
|
|
htlc, _ := createHTLC(i, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
}
|
|
|
|
// Alice now has an available balance of 2 BTC. We'll add a new HTLC of
|
|
// value 2 BTC, which should make Alice's balance negative (since she
|
|
// has to pay a commitment fee).
|
|
htlcAmt = lnwire.NewMSatFromSatoshis(2 * btcutil.SatoshiPerBitcoin)
|
|
htlc, _ := createHTLC(numHTLCs+1, htlcAmt)
|
|
_, err = aliceChannel.AddHTLC(htlc, nil)
|
|
if err != ErrBelowChanReserve {
|
|
t.Fatalf("expected balance below channel reserve, instead "+
|
|
"got: %v", err)
|
|
}
|
|
}
|
|
|
|
// assertNoChanSyncNeeded is a helper function that asserts that upon restart,
|
|
// two channels conclude that they're fully synchronized and don't need to
|
|
// retransmit any new messages.
|
|
func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel,
|
|
bobChannel *LightningChannel) {
|
|
|
|
_, _, line, _ := runtime.Caller(1)
|
|
|
|
aliceChanSyncMsg, err := aliceChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("line #%v: unable to produce chan sync msg: %v",
|
|
line, err)
|
|
}
|
|
bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceChanSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("line #%v: unable to process ChannelReestablish "+
|
|
"msg: %v", line, err)
|
|
}
|
|
if len(bobMsgsToSend) != 0 {
|
|
t.Fatalf("line #%v: bob shouldn't have to send any messages, "+
|
|
"instead wants to send: %v", line, spew.Sdump(bobMsgsToSend))
|
|
}
|
|
|
|
bobChanSyncMsg, err := bobChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("line #%v: unable to produce chan sync msg: %v",
|
|
line, err)
|
|
}
|
|
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(bobChanSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("line #%v: unable to process ChannelReestablish "+
|
|
"msg: %v", line, err)
|
|
}
|
|
if len(bobMsgsToSend) != 0 {
|
|
t.Fatalf("line #%v: alice shouldn't have to send any "+
|
|
"messages, instead wants to send: %v", line,
|
|
spew.Sdump(aliceMsgsToSend))
|
|
}
|
|
}
|
|
|
|
// TestChanSyncFullySynced tests that after a successful commitment exchange,
|
|
// and a forced restart, both nodes conclude that they're fully synchronized
|
|
// and don't need to retransmit any messages.
|
|
func TestChanSyncFullySynced(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// If we exchange channel sync messages from the get-go , then both
|
|
// sides should conclude that no further synchronization is needed.
|
|
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
|
|
|
|
// Next, we'll create an HTLC for Alice to extend to Bob.
|
|
var paymentPreimage [32]byte
|
|
copy(paymentPreimage[:], bytes.Repeat([]byte{1}, 32))
|
|
paymentHash := sha256.Sum256(paymentPreimage[:])
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
|
htlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: paymentHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(5),
|
|
}
|
|
aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Then we'll initiate a state transition to lock in this new HTLC.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete alice's state transition: %v", err)
|
|
}
|
|
|
|
// At this point, if both sides generate a ChannelReestablish message,
|
|
// they should both conclude that they're fully in sync.
|
|
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
|
|
|
|
// If bob settles the HTLC, and then initiates a state transition, they
|
|
// should both still think that they're in sync.
|
|
err = bobChannel.SettleHTLC(paymentPreimage, bobHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveHTLCSettle(paymentPreimage, aliceHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
|
|
// Next, we'll complete Bob's state transition, and assert again that
|
|
// they think they're fully synced.
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
|
}
|
|
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
|
|
|
|
// Finally, if we simulate a restart on both sides, then both should
|
|
// still conclude that they don't need to synchronize their state.
|
|
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)
|
|
}
|
|
aliceChannelNew, err := NewLightningChannel(
|
|
aliceChannel.signer, nil, aliceChannels[0],
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create new channel: %v", err)
|
|
}
|
|
defer aliceChannelNew.Stop()
|
|
bobChannelNew, err := NewLightningChannel(
|
|
bobChannel.signer, nil, bobChannels[0],
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create new channel: %v", err)
|
|
}
|
|
defer bobChannelNew.Stop()
|
|
|
|
assertNoChanSyncNeeded(t, aliceChannelNew, bobChannelNew)
|
|
}
|
|
|
|
// restartChannel reads the passe channel from disk, and returns a newly
|
|
// initialized instance. This simulates one party restarting and losing their
|
|
// in memory state.
|
|
func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
|
|
nodePub := channelOld.channelState.IdentityPub
|
|
nodeChannels, err := channelOld.channelState.Db.FetchOpenChannels(
|
|
nodePub,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
channelNew, err := NewLightningChannel(
|
|
channelOld.signer, channelOld.pCache, nodeChannels[0],
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return channelNew, nil
|
|
}
|
|
|
|
// TestChanSyncOweCommitment tests that if Bob restarts (and then Alice) before
|
|
// he receives Alice's CommitSig message, then Alice concludes that she needs
|
|
// to re-send the CommitDiff. After the diff has been sent, both nodes should
|
|
// resynchronize and be able to complete the dangling commit.
|
|
func TestChanSyncOweCommitment(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
var fakeOnionBlob [lnwire.OnionPacketSize]byte
|
|
copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize))
|
|
|
|
// We'll start off the scenario with Bob sending 3 HTLC's to Alice in a
|
|
// single state update.
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
|
|
const numBobHtlcs = 3
|
|
var bobPreimage [32]byte
|
|
copy(bobPreimage[:], bytes.Repeat([]byte{0xbb}, 32))
|
|
for i := 0; i < 3; i++ {
|
|
rHash := sha256.Sum256(bobPreimage[:])
|
|
h := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(10),
|
|
OnionBlob: fakeOnionBlob,
|
|
}
|
|
|
|
htlcIndex, err := bobChannel.AddHTLC(h, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add bob's htlc: %v", err)
|
|
}
|
|
|
|
h.ID = htlcIndex
|
|
if _, err := aliceChannel.ReceiveHTLC(h); err != nil {
|
|
t.Fatalf("unable to recv bob's htlc: %v", err)
|
|
}
|
|
}
|
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(
|
|
&aliceChannel.channelState.FundingOutpoint,
|
|
)
|
|
|
|
// With the HTLC's applied to both update logs, we'll initiate a state
|
|
// transition from Bob.
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
|
}
|
|
|
|
// Next, Alice's settles all 3 HTLC's from Bob, and also adds a new
|
|
// HTLC of her own.
|
|
for i := 0; i < 3; i++ {
|
|
err := aliceChannel.SettleHTLC(bobPreimage, uint64(i), nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
err = bobChannel.ReceiveHTLCSettle(bobPreimage, uint64(i))
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
}
|
|
var alicePreimage [32]byte
|
|
copy(alicePreimage[:], bytes.Repeat([]byte{0xaa}, 32))
|
|
rHash := sha256.Sum256(alicePreimage[:])
|
|
aliceHtlc := &lnwire.UpdateAddHTLC{
|
|
ChanID: chanID,
|
|
PaymentHash: rHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(10),
|
|
OnionBlob: fakeOnionBlob,
|
|
}
|
|
aliceHtlcIndex, err := aliceChannel.AddHTLC(aliceHtlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add alice's htlc: %v", err)
|
|
}
|
|
bobHtlcIndex, err := bobChannel.ReceiveHTLC(aliceHtlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to recv alice's htlc: %v", err)
|
|
}
|
|
|
|
// Now we'll begin the core of the test itself. Alice will extend a new
|
|
// commitment to Bob, but the connection drops before Bob can process
|
|
// it.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to sign commitment: %v", err)
|
|
}
|
|
|
|
// Bob doesn't get this message so upon reconnection, they need to
|
|
// synchronize. Alice should conclude that she owes Bob a commitment,
|
|
// while Bob should think he's properly synchronized.
|
|
aliceSyncMsg, err := aliceChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
bobSyncMsg, err := bobChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
|
|
// This is a helper function that asserts Alice concludes that she
|
|
// needs to retransmit the exact commitment that we failed to send
|
|
// above.
|
|
assertAliceCommitRetransmit := func() {
|
|
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(
|
|
bobSyncMsg,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(aliceMsgsToSend) != 5 {
|
|
t.Fatalf("expected alice to send %v messages instead "+
|
|
"will send %v: %v", 5, len(aliceMsgsToSend),
|
|
spew.Sdump(aliceMsgsToSend))
|
|
}
|
|
|
|
// Each of the settle messages that Alice sent should match her
|
|
// original intent.
|
|
for i := 0; i < 3; i++ {
|
|
settleMsg, ok := aliceMsgsToSend[i].(*lnwire.UpdateFulfillHTLC)
|
|
if !ok {
|
|
t.Fatalf("expected a htlc settle message, "+
|
|
"instead have %v", spew.Sdump(settleMsg))
|
|
}
|
|
if settleMsg.ID != uint64(i) {
|
|
t.Fatalf("wrong ID in settle msg: expected %v, "+
|
|
"got %v", i, settleMsg.ID)
|
|
}
|
|
if settleMsg.ChanID != chanID {
|
|
t.Fatalf("incorrect chan id: expected %v, got %v",
|
|
chanID, settleMsg.ChanID)
|
|
}
|
|
if settleMsg.PaymentPreimage != bobPreimage {
|
|
t.Fatalf("wrong pre-image: expected %v, got %v",
|
|
alicePreimage, settleMsg.PaymentPreimage)
|
|
}
|
|
}
|
|
|
|
// The HTLC add message should be identical.
|
|
if _, ok := aliceMsgsToSend[3].(*lnwire.UpdateAddHTLC); !ok {
|
|
t.Fatalf("expected a htlc add message, instead have %v",
|
|
spew.Sdump(aliceMsgsToSend[3]))
|
|
}
|
|
if !reflect.DeepEqual(aliceHtlc, aliceMsgsToSend[3]) {
|
|
t.Fatalf("htlc msg doesn't match exactly: "+
|
|
"expected %v got %v", spew.Sdump(aliceHtlc),
|
|
spew.Sdump(aliceMsgsToSend[3]))
|
|
}
|
|
|
|
// Next, we'll ensure that the CommitSig message exactly
|
|
// matches what Alice originally intended to send.
|
|
commitSigMsg, ok := aliceMsgsToSend[4].(*lnwire.CommitSig)
|
|
if !ok {
|
|
t.Fatalf("expected a CommitSig message, instead have %v",
|
|
spew.Sdump(aliceMsgsToSend[4]))
|
|
}
|
|
if commitSigMsg.CommitSig != aliceSig {
|
|
t.Fatalf("commit sig msgs don't match: expected %x got %x",
|
|
aliceSig, commitSigMsg.CommitSig)
|
|
}
|
|
if len(commitSigMsg.HtlcSigs) != len(aliceHtlcSigs) {
|
|
t.Fatalf("wrong number of htlc sigs: expected %v, got %v",
|
|
len(aliceHtlcSigs), len(commitSigMsg.HtlcSigs))
|
|
}
|
|
for i, htlcSig := range commitSigMsg.HtlcSigs {
|
|
if htlcSig != aliceHtlcSigs[i] {
|
|
t.Fatalf("htlc sig msgs don't match: "+
|
|
"expected %x got %x",
|
|
aliceHtlcSigs[i],
|
|
htlcSig)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Alice should detect that she needs to re-send 5 messages: the 3
|
|
// settles, her HTLC add, and finally her commit sig message.
|
|
assertAliceCommitRetransmit()
|
|
|
|
// From Bob's Pov he has nothing else to send, so he should conclude he
|
|
// has no further action remaining.
|
|
bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(bobMsgsToSend) != 0 {
|
|
t.Fatalf("expected bob to send %v messages instead will "+
|
|
"send %v: %v", 5, len(bobMsgsToSend),
|
|
spew.Sdump(bobMsgsToSend))
|
|
}
|
|
|
|
// If we restart Alice, she should still conclude that she needs to
|
|
// send the exact same set of messages.
|
|
aliceChannel, err = restartChannel(aliceChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart alice: %v", err)
|
|
}
|
|
defer aliceChannel.Stop()
|
|
assertAliceCommitRetransmit()
|
|
|
|
// TODO(roasbeef): restart bob as well???
|
|
|
|
// At this point, we should be able to resume the prior state update
|
|
// without any issues, resulting in Alice settling the 3 htlc's, and
|
|
// adding one of her own.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's commitment: %v", err)
|
|
}
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to revoke bob commitment: %v", err)
|
|
}
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign commitment: %v", err)
|
|
}
|
|
_, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to recv revocation: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to rev bob's commitment: %v", err)
|
|
}
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to revoke commitment: %v", err)
|
|
}
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("bob unable to recv revocation: %v", err)
|
|
}
|
|
|
|
// At this point, we'll now assert that their log states are what we
|
|
// expect.
|
|
//
|
|
// Alice's local log counter should be 4 and her HTLC index 3. She
|
|
// should detect Bob's remote log counter as being 3 and his HTLC index
|
|
// 3 as well.
|
|
if aliceChannel.localUpdateLog.logIndex != 4 {
|
|
t.Fatalf("incorrect log index: expected %v, got %v", 4,
|
|
aliceChannel.localUpdateLog.logIndex)
|
|
}
|
|
if aliceChannel.localUpdateLog.htlcCounter != 1 {
|
|
t.Fatalf("incorrect htlc index: expected %v, got %v", 1,
|
|
aliceChannel.localUpdateLog.htlcCounter)
|
|
}
|
|
if aliceChannel.remoteUpdateLog.logIndex != 3 {
|
|
t.Fatalf("incorrect log index: expected %v, got %v", 3,
|
|
aliceChannel.localUpdateLog.logIndex)
|
|
}
|
|
if aliceChannel.remoteUpdateLog.htlcCounter != 3 {
|
|
t.Fatalf("incorrect htlc index: expected %v, got %v", 3,
|
|
aliceChannel.localUpdateLog.htlcCounter)
|
|
}
|
|
|
|
// Bob should also have the same state, but mirrored.
|
|
if bobChannel.localUpdateLog.logIndex != 3 {
|
|
t.Fatalf("incorrect log index: expected %v, got %v", 3,
|
|
bobChannel.localUpdateLog.logIndex)
|
|
}
|
|
if bobChannel.localUpdateLog.htlcCounter != 3 {
|
|
t.Fatalf("incorrect htlc index: expected %v, got %v", 3,
|
|
bobChannel.localUpdateLog.htlcCounter)
|
|
}
|
|
if bobChannel.remoteUpdateLog.logIndex != 4 {
|
|
t.Fatalf("incorrect log index: expected %v, got %v", 4,
|
|
bobChannel.localUpdateLog.logIndex)
|
|
}
|
|
if bobChannel.remoteUpdateLog.htlcCounter != 1 {
|
|
t.Fatalf("incorrect htlc index: expected %v, got %v", 1,
|
|
bobChannel.localUpdateLog.htlcCounter)
|
|
}
|
|
|
|
// We'll conclude the test by having Bob settle Alice's HTLC, then
|
|
// initiate a state transition.
|
|
err = bobChannel.SettleHTLC(alicePreimage, bobHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveHTLCSettle(alicePreimage, aliceHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
|
}
|
|
|
|
// At this point, the final balances of both parties should properly
|
|
// reflect the amount of HTLC's sent.
|
|
bobMsatSent := numBobHtlcs * htlcAmt
|
|
if aliceChannel.channelState.TotalMSatSent != htlcAmt {
|
|
t.Fatalf("wrong value for msat sent: expected %v, got %v",
|
|
htlcAmt, aliceChannel.channelState.TotalMSatSent)
|
|
}
|
|
if aliceChannel.channelState.TotalMSatReceived != bobMsatSent {
|
|
t.Fatalf("wrong value for msat recv: expected %v, got %v",
|
|
bobMsatSent, aliceChannel.channelState.TotalMSatReceived)
|
|
}
|
|
if bobChannel.channelState.TotalMSatSent != bobMsatSent {
|
|
t.Fatalf("wrong value for msat sent: expected %v, got %v",
|
|
bobMsatSent, bobChannel.channelState.TotalMSatSent)
|
|
}
|
|
if bobChannel.channelState.TotalMSatReceived != htlcAmt {
|
|
t.Fatalf("wrong value for msat recv: expected %v, got %v",
|
|
htlcAmt, bobChannel.channelState.TotalMSatReceived)
|
|
}
|
|
}
|
|
|
|
// TestChanSyncOweRevocation tests that if Bob restarts (and then Alice) before
|
|
// he receiver's Alice's RevokeAndAck message, then Alice concludes that she
|
|
// needs to re-send the RevokeAndAck. After the revocation has been sent, both
|
|
// nodes should be able to successfully complete another state transition.
|
|
func TestChanSyncOweRevocation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(
|
|
&aliceChannel.channelState.FundingOutpoint,
|
|
)
|
|
|
|
// We'll start the test with Bob extending a single HTLC to Alice, and
|
|
// then initiating a state transition.
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
|
|
var bobPreimage [32]byte
|
|
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
|
|
rHash := sha256.Sum256(bobPreimage[:])
|
|
bobHtlc := &lnwire.UpdateAddHTLC{
|
|
ChanID: chanID,
|
|
PaymentHash: rHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(10),
|
|
}
|
|
bobHtlcIndex, err := bobChannel.AddHTLC(bobHtlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add bob's htlc: %v", err)
|
|
}
|
|
aliceHtlcIndex, err := aliceChannel.ReceiveHTLC(bobHtlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to recv bob's htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
|
}
|
|
|
|
// Next, Alice will settle that single HTLC, the _begin_ the start of a
|
|
// state transition.
|
|
err = aliceChannel.SettleHTLC(bobPreimage, aliceHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
err = bobChannel.ReceiveHTLCSettle(bobPreimage, bobHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
|
|
// We'll model the state transition right up until Alice needs to send
|
|
// her revocation message to complete the state transition.
|
|
//
|
|
// Alice signs the next state, then Bob receives and sends his
|
|
// revocation message.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to sign commitment: %v", err)
|
|
}
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's commitment: %v", err)
|
|
}
|
|
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to revoke bob commitment: %v", err)
|
|
}
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign commitment: %v", err)
|
|
}
|
|
|
|
_, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to recv revocation: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to rev bob's commitment: %v", err)
|
|
}
|
|
|
|
// At this point, we'll simulate the connection breaking down by Bob's
|
|
// lack of knowledge of the revocation message that Alice just sent.
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to revoke commitment: %v", err)
|
|
}
|
|
|
|
// If we fetch the channel sync messages at this state, then Alice
|
|
// should report that she owes Bob a revocation message, while Bob
|
|
// thinks they're fully in sync.
|
|
aliceSyncMsg, err := aliceChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
bobSyncMsg, err := bobChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
|
|
assertAliceOwesRevoke := func() {
|
|
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(aliceMsgsToSend) != 1 {
|
|
t.Fatalf("expected single message retransmission from Alice, "+
|
|
"instead got %v", spew.Sdump(aliceMsgsToSend))
|
|
}
|
|
aliceReRevoke, ok := aliceMsgsToSend[0].(*lnwire.RevokeAndAck)
|
|
if !ok {
|
|
t.Fatalf("expected to retransmit revocation msg, instead "+
|
|
"have: %v", spew.Sdump(aliceMsgsToSend[0]))
|
|
}
|
|
|
|
// Alice should re-send the revocation message for her prior
|
|
// state.
|
|
expectedRevocation, err := aliceChannel.generateRevocation(
|
|
aliceChannel.currentHeight - 1,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to regenerate revocation: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(expectedRevocation, aliceReRevoke) {
|
|
t.Fatalf("wrong re-revocation: expected %v, got %v",
|
|
expectedRevocation, aliceReRevoke)
|
|
}
|
|
}
|
|
|
|
// From Bob's PoV he shouldn't think that he owes Alice any messages.
|
|
bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(bobMsgsToSend) != 0 {
|
|
t.Fatalf("expected bob to not retransmit, instead has: %v",
|
|
spew.Sdump(bobMsgsToSend))
|
|
}
|
|
|
|
// Alice should detect that she owes Bob a revocation message, and only
|
|
// that single message.
|
|
assertAliceOwesRevoke()
|
|
|
|
// If we restart Alice, then she should still decide that she owes a
|
|
// revocation message to Bob.
|
|
aliceChannel, err = restartChannel(aliceChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart alice: %v", err)
|
|
}
|
|
defer aliceChannel.Stop()
|
|
assertAliceOwesRevoke()
|
|
|
|
// TODO(roasbeef): restart bob too???
|
|
|
|
// We'll continue by then allowing bob to process Alice's revocation message.
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("bob unable to recv revocation: %v", err)
|
|
}
|
|
|
|
// Finally, Alice will add an HTLC over her own such that we assert the
|
|
// channel can continue to receive updates.
|
|
var alicePreimage [32]byte
|
|
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
|
|
rHash = sha256.Sum256(alicePreimage[:])
|
|
aliceHtlc := &lnwire.UpdateAddHTLC{
|
|
ChanID: chanID,
|
|
PaymentHash: rHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(10),
|
|
}
|
|
if _, err := aliceChannel.AddHTLC(aliceHtlc, nil); err != nil {
|
|
t.Fatalf("unable to add alice's htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(aliceHtlc); err != nil {
|
|
t.Fatalf("unable to recv alice's htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete alice's state transition: %v", err)
|
|
}
|
|
|
|
// At this point, both sides should detect that they're fully synced.
|
|
assertNoChanSyncNeeded(t, aliceChannel, bobChannel)
|
|
}
|
|
|
|
// TestChanSyncOweRevocationAndCommit tests that if Alice initiates a state
|
|
// transition with Bob and Bob sends both a RevokeAndAck and CommitSig message
|
|
// but Alice doesn't receive them before the connection dies, then he'll
|
|
// retransmit them both.
|
|
func TestChanSyncOweRevocationAndCommit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
|
|
|
|
// We'll kick off the test by having Bob send Alice an HTLC, then lock
|
|
// it in with a state transition.
|
|
var bobPreimage [32]byte
|
|
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
|
|
rHash := sha256.Sum256(bobPreimage[:])
|
|
bobHtlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(10),
|
|
}
|
|
bobHtlcIndex, err := bobChannel.AddHTLC(bobHtlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add bob's htlc: %v", err)
|
|
}
|
|
aliceHtlcIndex, err := aliceChannel.ReceiveHTLC(bobHtlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to recv bob's htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
|
}
|
|
|
|
// Next, Alice will settle that incoming HTLC, then we'll start the
|
|
// core of the test itself.
|
|
err = aliceChannel.SettleHTLC(bobPreimage, aliceHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
err = bobChannel.ReceiveHTLCSettle(bobPreimage, bobHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
|
|
// Progressing the exchange: Alice will send her signature, Bob will
|
|
// receive, send a revocation and also a signature for Alice's state.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to sign commitment: %v", err)
|
|
}
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's commitment: %v", err)
|
|
}
|
|
|
|
// Bob generates the revoke and sig message, but the messages don't
|
|
// reach Alice before the connection dies.
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to revoke bob commitment: %v", err)
|
|
}
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign commitment: %v", err)
|
|
}
|
|
|
|
// If we now attempt to resync, then Alice should conclude that she
|
|
// doesn't need any further updates, while Bob concludes that he needs
|
|
// to re-send both his revocation and commit sig message.
|
|
aliceSyncMsg, err := aliceChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
bobSyncMsg, err := bobChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
|
|
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(aliceMsgsToSend) != 0 {
|
|
t.Fatalf("expected alice to not retransmit, instead she's "+
|
|
"sending: %v", spew.Sdump(aliceMsgsToSend))
|
|
}
|
|
|
|
assertBobSendsRevokeAndCommit := func() {
|
|
bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(bobMsgsToSend) != 2 {
|
|
t.Fatalf("expected bob to send %v messages, instead "+
|
|
"sends: %v", 2, spew.Sdump(bobMsgsToSend))
|
|
}
|
|
bobReRevoke, ok := bobMsgsToSend[0].(*lnwire.RevokeAndAck)
|
|
if !ok {
|
|
t.Fatalf("expected bob to re-send revoke, instead sending: %v",
|
|
spew.Sdump(bobMsgsToSend[0]))
|
|
}
|
|
if !reflect.DeepEqual(bobReRevoke, bobRevocation) {
|
|
t.Fatalf("revocation msgs don't match: expected %v, got %v",
|
|
bobRevocation, bobReRevoke)
|
|
}
|
|
|
|
bobReCommitSigMsg, ok := bobMsgsToSend[1].(*lnwire.CommitSig)
|
|
if !ok {
|
|
t.Fatalf("expected bob to re-send commit sig, instead sending: %v",
|
|
spew.Sdump(bobMsgsToSend[1]))
|
|
}
|
|
if bobReCommitSigMsg.CommitSig != bobSig {
|
|
t.Fatalf("commit sig msgs don't match: expected %x got %x",
|
|
bobSig, bobReCommitSigMsg.CommitSig)
|
|
}
|
|
if len(bobReCommitSigMsg.HtlcSigs) != len(bobHtlcSigs) {
|
|
t.Fatalf("wrong number of htlc sigs: expected %v, got %v",
|
|
len(bobHtlcSigs), len(bobReCommitSigMsg.HtlcSigs))
|
|
}
|
|
for i, htlcSig := range bobReCommitSigMsg.HtlcSigs {
|
|
if htlcSig != aliceHtlcSigs[i] {
|
|
t.Fatalf("htlc sig msgs don't match: "+
|
|
"expected %x got %x",
|
|
bobHtlcSigs[i], htlcSig)
|
|
}
|
|
}
|
|
}
|
|
|
|
// We expect Bob to send exactly two messages: first his revocation
|
|
// message to Alice, and second his original commit sig message.
|
|
assertBobSendsRevokeAndCommit()
|
|
|
|
// At this point we simulate the connection failing with a restart from
|
|
// Bob. He should still re-send the exact same set of messages.
|
|
bobChannel, err = restartChannel(bobChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart channel: %v", err)
|
|
}
|
|
defer bobChannel.Stop()
|
|
assertBobSendsRevokeAndCommit()
|
|
|
|
// We'll now finish the state transition by having Alice process both
|
|
// messages, and send her final revocation.
|
|
_, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to recv revocation: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to rev bob's commitment: %v", err)
|
|
}
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to revoke commitment: %v", err)
|
|
}
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("bob unable to recv revocation: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestChanSyncOweRevocationAndCommitForceTransition tests that if Alice
|
|
// initiates a state transition with Bob, but Alice fails to receive his
|
|
// RevokeAndAck and the connection dies before Bob sends his CommitSig message,
|
|
// then Bob will re-send her RevokeAndAck message. Bob will also send and
|
|
// _identical_ CommitSig as he detects his commitment chain is ahead of
|
|
// Alice's.
|
|
func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
|
|
|
|
// We'll kick off the test by having Bob send Alice an HTLC, then lock
|
|
// it in with a state transition.
|
|
var bobPreimage [32]byte
|
|
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
|
|
rHash := sha256.Sum256(bobPreimage[:])
|
|
bobHtlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(10),
|
|
}
|
|
bobHtlcIndex, err := bobChannel.AddHTLC(bobHtlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add bob's htlc: %v", err)
|
|
}
|
|
aliceHtlcIndex, err := aliceChannel.ReceiveHTLC(bobHtlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to recv bob's htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
|
}
|
|
|
|
// Next, Alice will settle that incoming HTLC, then we'll start the
|
|
// core of the test itself.
|
|
err = aliceChannel.SettleHTLC(bobPreimage, aliceHtlcIndex, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
err = bobChannel.ReceiveHTLCSettle(bobPreimage, bobHtlcIndex)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
|
|
// Progressing the exchange: Alice will send her signature, with Bob
|
|
// processing the new state locally.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to sign commitment: %v", err)
|
|
}
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's commitment: %v", err)
|
|
}
|
|
|
|
// Bob then sends his revocation message, but before Alice can process
|
|
// it (and before he scan send his CommitSig message), then connection
|
|
// dies.
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to revoke bob commitment: %v", err)
|
|
}
|
|
|
|
// Now if we attempt to synchronize states at this point, Alice should
|
|
// detect that she owes nothing, while Bob should re-send both his
|
|
// RevokeAndAck as well as his commitment message.
|
|
aliceSyncMsg, err := aliceChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
bobSyncMsg, err := bobChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
|
|
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(bobSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(aliceMsgsToSend) != 0 {
|
|
t.Fatalf("expected alice to not retransmit, instead she's "+
|
|
"sending: %v", spew.Sdump(aliceMsgsToSend))
|
|
}
|
|
|
|
// If we process Alice's sync message from Bob's PoV, then he should
|
|
// send his RevokeAndAck message again. Additionally, the CommitSig
|
|
// message that he sends should be sufficient to finalize the state
|
|
// transition.
|
|
bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(bobMsgsToSend) != 2 {
|
|
t.Fatalf("expected bob to send %v messages, instead "+
|
|
"sends: %v", 2, spew.Sdump(bobMsgsToSend))
|
|
}
|
|
bobReRevoke, ok := bobMsgsToSend[0].(*lnwire.RevokeAndAck)
|
|
if !ok {
|
|
t.Fatalf("expected bob to re-send revoke, instead sending: %v",
|
|
spew.Sdump(bobMsgsToSend[0]))
|
|
}
|
|
if !reflect.DeepEqual(bobReRevoke, bobRevocation) {
|
|
t.Fatalf("revocation msgs don't match: expected %v, got %v",
|
|
bobRevocation, bobReRevoke)
|
|
}
|
|
|
|
// The second message should be his CommitSig message that he never
|
|
// sent, but will send in order to force both states to synchronize.
|
|
bobReCommitSigMsg, ok := bobMsgsToSend[1].(*lnwire.CommitSig)
|
|
if !ok {
|
|
t.Fatalf("expected bob to re-send commit sig, instead sending: %v",
|
|
spew.Sdump(bobMsgsToSend[1]))
|
|
}
|
|
|
|
// At this point we simulate the connection failing with a restart from
|
|
// Bob. He should still re-send the exact same set of messages.
|
|
bobChannel, err = restartChannel(bobChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart channel: %v", err)
|
|
}
|
|
defer bobChannel.Stop()
|
|
if len(bobMsgsToSend) != 2 {
|
|
t.Fatalf("expected bob to send %v messages, instead "+
|
|
"sends: %v", 2, spew.Sdump(bobMsgsToSend))
|
|
}
|
|
bobReRevoke, ok = bobMsgsToSend[0].(*lnwire.RevokeAndAck)
|
|
if !ok {
|
|
t.Fatalf("expected bob to re-send revoke, instead sending: %v",
|
|
spew.Sdump(bobMsgsToSend[0]))
|
|
}
|
|
bobSigMsg, ok := bobMsgsToSend[1].(*lnwire.CommitSig)
|
|
if !ok {
|
|
t.Fatalf("expected bob to re-send commit sig, instead sending: %v",
|
|
spew.Sdump(bobMsgsToSend[1]))
|
|
}
|
|
if !reflect.DeepEqual(bobReRevoke, bobRevocation) {
|
|
t.Fatalf("revocation msgs don't match: expected %v, got %v",
|
|
bobRevocation, bobReRevoke)
|
|
}
|
|
if bobReCommitSigMsg.CommitSig != bobSigMsg.CommitSig {
|
|
t.Fatalf("commit sig msgs don't match: expected %x got %x",
|
|
bobSigMsg.CommitSig,
|
|
bobReCommitSigMsg.CommitSig)
|
|
}
|
|
if len(bobReCommitSigMsg.HtlcSigs) != len(bobSigMsg.HtlcSigs) {
|
|
t.Fatalf("wrong number of htlc sigs: expected %v, got %v",
|
|
len(bobSigMsg.HtlcSigs), len(bobReCommitSigMsg.HtlcSigs))
|
|
}
|
|
for i, htlcSig := range bobReCommitSigMsg.HtlcSigs {
|
|
if htlcSig != bobSigMsg.HtlcSigs[i] {
|
|
t.Fatalf("htlc sig msgs don't match: "+
|
|
"expected %x got %x",
|
|
bobSigMsg.HtlcSigs[i], htlcSig)
|
|
}
|
|
}
|
|
|
|
// Now, we'll continue the exchange, sending Bob's revocation and
|
|
// signature message to Alice, ending with Alice sending her revocation
|
|
// message to Bob.
|
|
_, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to recv revocation: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveNewCommitment(
|
|
bobSigMsg.CommitSig, bobSigMsg.HtlcSigs,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to rev bob's commitment: %v", err)
|
|
}
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to revoke commitment: %v", err)
|
|
}
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("bob unable to recv revocation: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestFeeUpdateRejectInsaneFee tests that if the initiator tries to attach a
|
|
// fee that would put them below their current reserve, then it's rejected by
|
|
// the state machine.
|
|
func TestFeeUpdateRejectInsaneFee(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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, _, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// Next, we'll try to add a fee rate to Alice which is 1,000,000x her
|
|
// starting fee rate.
|
|
startingFeeRate := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw)
|
|
newFeeRate := startingFeeRate * 1000000
|
|
|
|
// Both Alice and Bob should reject this new fee rate as it it far too
|
|
// large.
|
|
if err := aliceChannel.UpdateFee(newFeeRate); err == nil {
|
|
t.Fatalf("alice should have rejected fee update")
|
|
}
|
|
}
|
|
|
|
// TestChannelRetransmissionFeeUpdate tests that the initiator will include any
|
|
// pending fee updates if it needs to retransmit signatures.
|
|
func TestChannelRetransmissionFeeUpdate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// First, we'll fetch the current fee rate present within the
|
|
// commitment transactions.
|
|
startingFeeRate := SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw)
|
|
|
|
// Next, we'll start a commitment update, with Alice sending a new
|
|
// update to double the fee rate of the commitment.
|
|
newFeeRate := startingFeeRate * 2
|
|
if err := aliceChannel.UpdateFee(newFeeRate); err != nil {
|
|
t.Fatalf("unable to update fee for Alice's channel: %v", err)
|
|
}
|
|
if err := bobChannel.ReceiveUpdateFee(newFeeRate); err != nil {
|
|
t.Fatalf("unable to update fee for Bob's channel: %v", err)
|
|
}
|
|
|
|
// Now, Alice will send a new commitment to Bob, but we'll simulate a
|
|
// connection failure, so Bob doesn't get her signature.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to sign commitment: %v", err)
|
|
}
|
|
|
|
// Restart both channels to simulate a connection restart.
|
|
aliceChannel, err = restartChannel(aliceChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart alice: %v", err)
|
|
}
|
|
defer aliceChannel.Stop()
|
|
bobChannel, err = restartChannel(bobChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart channel: %v", err)
|
|
}
|
|
defer bobChannel.Stop()
|
|
|
|
// Bob doesn't get this message so upon reconnection, they need to
|
|
// synchronize. Alice should conclude that she owes Bob a commitment,
|
|
// while Bob should think he's properly synchronized.
|
|
aliceSyncMsg, err := aliceChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
bobSyncMsg, err := bobChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to produce chan sync msg: %v", err)
|
|
}
|
|
|
|
// Bob should detect that he doesn't need to send anything to Alice.
|
|
bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(bobMsgsToSend) != 0 {
|
|
t.Fatalf("expected bob to send %v messages instead "+
|
|
"will send %v: %v", 0, len(bobMsgsToSend),
|
|
spew.Sdump(bobMsgsToSend))
|
|
}
|
|
|
|
// When Alice processes Bob's chan sync message, she should realize
|
|
// that she needs to first send a new UpdateFee message, and also a
|
|
// CommitSig.
|
|
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(
|
|
bobSyncMsg,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to process chan sync msg: %v", err)
|
|
}
|
|
if len(aliceMsgsToSend) != 2 {
|
|
t.Fatalf("expected alice to send %v messages instead "+
|
|
"will send %v: %v", 2, len(aliceMsgsToSend),
|
|
spew.Sdump(aliceMsgsToSend))
|
|
}
|
|
|
|
// The first message should be an UpdateFee message.
|
|
retransFeeMsg, ok := aliceMsgsToSend[0].(*lnwire.UpdateFee)
|
|
if !ok {
|
|
t.Fatalf("expected UpdateFee message, instead have: %v",
|
|
spew.Sdump(aliceMsgsToSend[0]))
|
|
}
|
|
|
|
// The fee should match exactly the new fee update we applied above.
|
|
if retransFeeMsg.FeePerKw != uint32(newFeeRate) {
|
|
t.Fatalf("fee update doesn't match: expected %v, got %v",
|
|
uint32(newFeeRate), retransFeeMsg)
|
|
}
|
|
|
|
// The second, should be a CommitSig message, and be identical to the
|
|
// sig message she sent prior.
|
|
commitSigMsg, ok := aliceMsgsToSend[1].(*lnwire.CommitSig)
|
|
if !ok {
|
|
t.Fatalf("expected a CommitSig message, instead have %v",
|
|
spew.Sdump(aliceMsgsToSend[1]))
|
|
}
|
|
if commitSigMsg.CommitSig != aliceSig {
|
|
t.Fatalf("commit sig msgs don't match: expected %x got %x",
|
|
aliceSig, commitSigMsg.CommitSig)
|
|
}
|
|
if len(commitSigMsg.HtlcSigs) != len(aliceHtlcSigs) {
|
|
t.Fatalf("wrong number of htlc sigs: expected %v, got %v",
|
|
len(aliceHtlcSigs), len(commitSigMsg.HtlcSigs))
|
|
}
|
|
for i, htlcSig := range commitSigMsg.HtlcSigs {
|
|
if htlcSig != aliceHtlcSigs[i] {
|
|
t.Fatalf("htlc sig msgs don't match: "+
|
|
"expected %x got %x",
|
|
aliceHtlcSigs[i], htlcSig)
|
|
}
|
|
}
|
|
|
|
// Now, we if re-apply the updates to Bob, we should be able to resume
|
|
// the commitment update as normal.
|
|
if err := bobChannel.ReceiveUpdateFee(newFeeRate); err != nil {
|
|
t.Fatalf("unable to update fee for Bob's channel: %v", err)
|
|
}
|
|
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("bob unable to process alice's commitment: %v", err)
|
|
}
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to revoke bob commitment: %v", err)
|
|
}
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("bob unable to sign commitment: %v", err)
|
|
}
|
|
_, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to recv revocation: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatalf("alice unable to rev bob's commitment: %v", err)
|
|
}
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatalf("alice unable to revoke commitment: %v", err)
|
|
}
|
|
if _, _, _, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil {
|
|
t.Fatalf("bob unable to recv revocation: %v", err)
|
|
}
|
|
|
|
// Both parties should now have the latest fee rate locked-in.
|
|
if SatPerKWeight(aliceChannel.channelState.LocalCommitment.FeePerKw) != newFeeRate {
|
|
t.Fatalf("alice's feePerKw was not locked in")
|
|
}
|
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != newFeeRate {
|
|
t.Fatalf("bob's feePerKw was not locked in")
|
|
}
|
|
|
|
// Finally, we'll add with adding a new HTLC, then forcing a state
|
|
// transition. This should also proceed as normal.
|
|
var bobPreimage [32]byte
|
|
copy(bobPreimage[:], bytes.Repeat([]byte{0xaa}, 32))
|
|
rHash := sha256.Sum256(bobPreimage[:])
|
|
bobHtlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rHash,
|
|
Amount: lnwire.NewMSatFromSatoshis(20000),
|
|
Expiry: uint32(10),
|
|
}
|
|
if _, err := bobChannel.AddHTLC(bobHtlc, nil); err != nil {
|
|
t.Fatalf("unable to add bob's htlc: %v", err)
|
|
}
|
|
if _, err := aliceChannel.ReceiveHTLC(bobHtlc); err != nil {
|
|
t.Fatalf("unable to recv bob's htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestChanSyncUnableToSync tests that if Alice or Bob receive an invalid
|
|
// ChannelReestablish messages,then they reject the message and declare the
|
|
// channel un-continuable by returning ErrCannotSyncCommitChains.
|
|
func TestChanSyncUnableToSync(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// If we immediately send both sides a "bogus" ChanSync message, then
|
|
// they both should conclude that they're unable to synchronize the
|
|
// state.
|
|
badChanSync := &lnwire.ChannelReestablish{
|
|
ChanID: lnwire.NewChanIDFromOutPoint(
|
|
&aliceChannel.channelState.FundingOutpoint,
|
|
),
|
|
NextLocalCommitHeight: 1000,
|
|
RemoteCommitTailHeight: 9000,
|
|
}
|
|
_, _, _, err = bobChannel.ProcessChanSyncMsg(badChanSync)
|
|
if err != ErrCannotSyncCommitChains {
|
|
t.Fatalf("expected error instead have: %v", err)
|
|
}
|
|
_, _, _, err = aliceChannel.ProcessChanSyncMsg(badChanSync)
|
|
if err != ErrCannotSyncCommitChains {
|
|
t.Fatalf("expected error instead have: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestChanSyncInvalidLastSecret ensures that if Alice and Bob have completed
|
|
// state transitions in an existing channel, and then send a ChannelReestablish
|
|
// message after a restart, the following holds: if Alice has lost data, so she
|
|
// sends an invalid commit secret then both parties recognize this as possible
|
|
// data loss.
|
|
func TestChanSyncInvalidLastSecret(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// We'll create a new instances of Alice before doing any state updates
|
|
// such that we have the initial in memory state at the start of the
|
|
// channel.
|
|
aliceOld, err := restartChannel(aliceChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart alice")
|
|
}
|
|
|
|
// First, we'll add an HTLC, and then initiate a state transition
|
|
// between the two parties such that we actually have a prior
|
|
// revocation to send.
|
|
var paymentPreimage [32]byte
|
|
copy(paymentPreimage[:], bytes.Repeat([]byte{1}, 32))
|
|
paymentHash := sha256.Sum256(paymentPreimage[:])
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
|
htlc := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: paymentHash,
|
|
Amount: htlcAmt,
|
|
Expiry: uint32(5),
|
|
}
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Then we'll initiate a state transition to lock in this new HTLC.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete alice's state transition: %v", err)
|
|
}
|
|
|
|
// Next, we'll restart both parties in order to simulate a connection
|
|
// re-establishment.
|
|
aliceChannel, err = restartChannel(aliceChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart alice: %v", err)
|
|
}
|
|
bobChannel, err = restartChannel(bobChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart bob: %v", err)
|
|
}
|
|
|
|
// Next, we'll produce the ChanSync messages for both parties.
|
|
aliceChanSync, err := aliceChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate chan sync msg: %v", err)
|
|
}
|
|
bobChanSync, err := bobChannel.ChanSyncMsg()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate chan sync msg: %v", err)
|
|
}
|
|
|
|
// We'll modify Alice's sync message to have an invalid commitment
|
|
// secret.
|
|
aliceChanSync.LastRemoteCommitSecret[4] ^= 0x01
|
|
|
|
// Alice's former self should conclude that she possibly lost data as
|
|
// Bob is sending a valid commit secret for the latest state.
|
|
_, _, _, err = aliceOld.ProcessChanSyncMsg(bobChanSync)
|
|
if err != ErrCommitSyncDataLoss {
|
|
t.Fatalf("wrong error, expected ErrCommitSyncDataLoss "+
|
|
"instead got: %v", err)
|
|
}
|
|
|
|
// Bob should conclude that he should force close the channel, as Alice
|
|
// cannot continue operation.
|
|
_, _, _, err = bobChannel.ProcessChanSyncMsg(aliceChanSync)
|
|
if err != ErrInvalidLastCommitSecret {
|
|
t.Fatalf("wrong error, expected ErrInvalidLastCommitSecret, "+
|
|
"instead got: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestChanAvailableBandwidth tests the accuracy of the AvailableBalance()
|
|
// method. The value returned from this message should reflect the value
|
|
// returned within the commitment state of a channel after the transition is
|
|
// initiated.
|
|
func TestChanAvailableBandwidth(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
assertBandwidthEstimateCorrect := func(aliceInitiate bool) {
|
|
// With the HTLC's added, we'll now query the AvailableBalance
|
|
// method for the current available channel bandwidth from
|
|
// Alice's PoV.
|
|
aliceAvailableBalance := aliceChannel.AvailableBalance()
|
|
|
|
// With this balance obtained, we'll now trigger a state update
|
|
// to actually determine what the current up to date balance
|
|
// is.
|
|
if aliceInitiate {
|
|
err := forceStateTransition(aliceChannel, bobChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to complete alice's state "+
|
|
"transition: %v", err)
|
|
}
|
|
} else {
|
|
err := forceStateTransition(bobChannel, aliceChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to complete alice's state "+
|
|
"transition: %v", err)
|
|
}
|
|
}
|
|
|
|
// Now, we'll obtain the current available bandwidth in Alice's
|
|
// latest commitment and compare that to the prior estimate.
|
|
aliceBalance := aliceChannel.channelState.LocalCommitment.LocalBalance
|
|
if aliceBalance != aliceAvailableBalance {
|
|
_, _, line, _ := runtime.Caller(1)
|
|
t.Fatalf("line: %v, incorrect balance: expected %v, "+
|
|
"got %v", line, aliceBalance,
|
|
aliceAvailableBalance)
|
|
}
|
|
}
|
|
|
|
// First, we'll add 3 outgoing HTLC's from Alice to Bob.
|
|
const numHtlcs = 3
|
|
var htlcAmt lnwire.MilliSatoshi = 100000
|
|
alicePreimages := make([][32]byte, numHtlcs)
|
|
for i := 0; i < numHtlcs; i++ {
|
|
htlc, preImage := createHTLC(i, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
alicePreimages[i] = preImage
|
|
}
|
|
|
|
assertBandwidthEstimateCorrect(true)
|
|
|
|
// We'll repeat the same exercise, but with non-dust HTLCs. So we'll
|
|
// crank up the value of the HTLC's we're adding to the commitment
|
|
// transaction.
|
|
htlcAmt = lnwire.NewMSatFromSatoshis(30000)
|
|
for i := 0; i < numHtlcs; i++ {
|
|
htlc, preImage := createHTLC(numHtlcs+i, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
alicePreimages = append(alicePreimages, preImage)
|
|
}
|
|
|
|
assertBandwidthEstimateCorrect(true)
|
|
|
|
// Next, we'll have Bob 5 of Alice's HTLC's, and cancel one of them (in
|
|
// the update log).
|
|
for i := 0; i < (numHtlcs*2)-1; i++ {
|
|
preImage := alicePreimages[i]
|
|
err := bobChannel.SettleHTLC(preImage, uint64(i), nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveHTLCSettle(preImage, uint64(i))
|
|
if err != nil {
|
|
t.Fatalf("unable to settle htlc: %v", err)
|
|
}
|
|
}
|
|
|
|
htlcIndex := uint64((numHtlcs * 2) - 1)
|
|
err = bobChannel.FailHTLC(htlcIndex, []byte("f"), nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to cancel HTLC: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveFailHTLC(htlcIndex, []byte("bad"))
|
|
if err != nil {
|
|
t.Fatalf("unable to recv htlc cancel: %v", err)
|
|
}
|
|
|
|
// We must do a state transition before the balance is available
|
|
// for Alice.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete alice's state "+
|
|
"transition: %v", err)
|
|
}
|
|
|
|
// With the HTLC's settled in the log, we'll now assert that if we
|
|
// initiate a state transition, then our guess was correct.
|
|
assertBandwidthEstimateCorrect(false)
|
|
|
|
// TODO(roasbeef): additional tests from diff starting conditions
|
|
}
|
|
|
|
// TestSignCommitmentFailNotLockedIn tests that a channel will not attempt to
|
|
// create a new state if it doesn't yet know of the next revocation point for
|
|
// the remote party.
|
|
func TestSignCommitmentFailNotLockedIn(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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, _, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// Next, we'll modify Alice's internal state to omit knowledge of Bob's
|
|
// next revocation point.
|
|
aliceChannel.channelState.RemoteNextRevocation = nil
|
|
|
|
// If we now try to initiate a state update, then it should fail as
|
|
// Alice is unable to actually create a new state.
|
|
_, _, err = aliceChannel.SignNextCommitment()
|
|
if err != ErrNoWindow {
|
|
t.Fatalf("expected ErrNoWindow, instead have: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestLockedInHtlcForwardingSkipAfterRestart ensures that after a restart, a
|
|
// state machine doesn't attempt to re-forward any HTLC's that were already
|
|
// locked in, but in a prior state.
|
|
func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// First, we'll make a channel between Alice and Bob.
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// We'll now add two HTLC's from Bob to Alice, then Bob will initiate a
|
|
// state transition.
|
|
var htlcAmt lnwire.MilliSatoshi = 100000
|
|
htlc, _ := createHTLC(0, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
htlc2, _ := createHTLC(1, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc2, nil); err != nil {
|
|
t.Fatalf("unable to add htlc2: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc2); err != nil {
|
|
t.Fatalf("unable to recv htlc2: %v", err)
|
|
}
|
|
|
|
// We'll now manually initiate a state transition between Alice and
|
|
// bob.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bobRevocation, _, err := bobChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Alice should detect that she doesn't need to forward any HTLC's.
|
|
_, aliceHtlcsToForward, _, err := aliceChannel.ReceiveRevocation(
|
|
bobRevocation,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(aliceHtlcsToForward) != 0 {
|
|
t.Fatalf("alice shouldn't forward any HTLC's, instead wants to "+
|
|
"forward %v htlcs", len(aliceHtlcsToForward))
|
|
}
|
|
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Bob on the other hand, should detect that he now has 2 incoming
|
|
// HTLC's that he can forward along.
|
|
_, bobHtlcsToForward, _, err := bobChannel.ReceiveRevocation(aliceRevocation)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(bobHtlcsToForward) != 2 {
|
|
t.Fatalf("bob should forward 2 hltcs, instead has %v",
|
|
len(bobHtlcsToForward))
|
|
}
|
|
|
|
// We'll now restart both Alice and Bob. This emulates a reconnection
|
|
// between the two peers.
|
|
aliceChannel, err = restartChannel(aliceChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart alice: %v", err)
|
|
}
|
|
bobChannel, err = restartChannel(bobChannel)
|
|
if err != nil {
|
|
t.Fatalf("unable to restart bob: %v", err)
|
|
}
|
|
|
|
// With both nodes restarted, Bob will now attempt to cancel one of
|
|
// Alice's HTLC's.
|
|
err = bobChannel.FailHTLC(htlc2.ID, []byte("failreason"), nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to cancel HTLC: %v", err)
|
|
}
|
|
err = aliceChannel.ReceiveFailHTLC(htlc2.ID, []byte("bad"))
|
|
if err != nil {
|
|
t.Fatalf("unable to recv htlc cancel: %v", err)
|
|
}
|
|
|
|
// We'll now initiate another state transition, but this time Bob will
|
|
// lead.
|
|
bobSig, bobHtlcSigs, err = bobChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
aliceSig, aliceHtlcSigs, err = aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// At this point, Bob receives the revocation from Alice, which is now
|
|
// his signal to examine all the HTLC's that have been locked in to
|
|
// process.
|
|
_, bobHtlcsToForward, _, err = bobChannel.ReceiveRevocation(aliceRevocation)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Bob should detect that he doesn't need to forward *any* HTLC's, as
|
|
// he was the one that initiated extending the commitment chain of
|
|
// Alice.
|
|
if len(bobHtlcsToForward) != 0 {
|
|
t.Fatalf("bob shouldn't forward any htlcs, but has: %v",
|
|
spew.Sdump(bobHtlcsToForward))
|
|
}
|
|
}
|
|
|
|
// TestInvalidCommitSigError tests that if the remote party sends us an invalid
|
|
// commitment signature, then we'll reject it and return a special error that
|
|
// contains information to allow the remote party to debug their issues.
|
|
func TestInvalidCommitSigError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// First, we'll make a channel between Alice and Bob.
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// With the channel established, we'll now send a single HTLC from
|
|
// Alice to Bob.
|
|
var htlcAmt lnwire.MilliSatoshi = 100000
|
|
htlc, _ := createHTLC(0, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Alice will now attempt to initiate a state transition.
|
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
|
if err != nil {
|
|
t.Fatalf("unable to sign new commit: %v", err)
|
|
}
|
|
|
|
// Before the signature gets to Bob, we'll mutate it, such that the
|
|
// signature is now actually invalid.
|
|
aliceSig[0] ^= 88
|
|
|
|
// Bob should reject this new state, and return the proper error.
|
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
|
if err == nil {
|
|
t.Fatalf("bob accepted invalid state but shouldn't have")
|
|
}
|
|
if _, ok := err.(*InvalidCommitSigError); !ok {
|
|
t.Fatalf("bob sent incorrect error, expected %T, got %T",
|
|
&InvalidCommitSigError{}, err)
|
|
}
|
|
}
|
|
|
|
// TestChannelUnilateralCloseHtlcResolution tests that in the case of a
|
|
// unilateral channel closure, then the party that didn't broadcast the
|
|
// commitment is able to properly sweep all relevant outputs.
|
|
func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// 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()
|
|
|
|
// We'll start off the test by adding an HTLC in both directions, then
|
|
// initiating enough state transitions to lock both of them in.
|
|
htlcAmount := lnwire.NewMSatFromSatoshis(20000)
|
|
htlcAlice, _ := createHTLC(0, htlcAmount)
|
|
if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil {
|
|
t.Fatalf("alice unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil {
|
|
t.Fatalf("bob unable to recv add htlc: %v", err)
|
|
}
|
|
htlcBob, preimageBob := createHTLC(0, htlcAmount)
|
|
if _, err := bobChannel.AddHTLC(htlcBob, nil); err != nil {
|
|
t.Fatalf("bob unable to add htlc: %v", err)
|
|
}
|
|
if _, err := aliceChannel.ReceiveHTLC(htlcBob); err != nil {
|
|
t.Fatalf("alice unable to recv add htlc: %v", err)
|
|
}
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("Can't update the channel state: %v", err)
|
|
}
|
|
if err := forceStateTransition(bobChannel, aliceChannel); err != nil {
|
|
t.Fatalf("Can't update the channel state: %v", err)
|
|
}
|
|
|
|
// With both HTLC's locked in, we'll now simulate Bob force closing the
|
|
// transaction on Alice.
|
|
bobForceClose, err := bobChannel.ForceClose()
|
|
if err != nil {
|
|
t.Fatalf("unable to close: %v", err)
|
|
}
|
|
|
|
// Now that Bob has force closed, we'll modify Alice's pre image cache
|
|
// such that she now gains the ability to also settle the incoming HTLC
|
|
// from Bob.
|
|
aliceChannel.pCache.AddPreimage(preimageBob[:])
|
|
|
|
// We'll then use Bob's transaction to trigger a spend notification for
|
|
// Alice.
|
|
closeTx := bobForceClose.CloseTx
|
|
commitTxHash := closeTx.TxHash()
|
|
spendDetail := &chainntnfs.SpendDetail{
|
|
SpendingTx: closeTx,
|
|
SpenderTxHash: &commitTxHash,
|
|
}
|
|
aliceCloseSummary, err := NewUnilateralCloseSummary(
|
|
aliceChannel.channelState, aliceChannel.signer, aliceChannel.pCache,
|
|
spendDetail, aliceChannel.channelState.RemoteCommitment,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice close summary: %v", err)
|
|
}
|
|
|
|
// She should detect that she can sweep both the outgoing HTLC as well
|
|
// as the incoming one from Bob.
|
|
if len(aliceCloseSummary.HtlcResolutions.OutgoingHTLCs) != 1 {
|
|
t.Fatalf("alice out htlc resolutions not populated: expected %v "+
|
|
"htlcs, got %v htlcs",
|
|
1, len(aliceCloseSummary.HtlcResolutions.OutgoingHTLCs))
|
|
}
|
|
if len(aliceCloseSummary.HtlcResolutions.IncomingHTLCs) != 1 {
|
|
t.Fatalf("alice in htlc resolutions not populated: expected %v "+
|
|
"htlcs, got %v htlcs",
|
|
1, len(aliceCloseSummary.HtlcResolutions.IncomingHTLCs))
|
|
}
|
|
|
|
outHtlcResolution := aliceCloseSummary.HtlcResolutions.OutgoingHTLCs[0]
|
|
inHtlcResolution := aliceCloseSummary.HtlcResolutions.IncomingHTLCs[0]
|
|
|
|
// First, we'll ensure that Alice can directly spend the outgoing HTLC
|
|
// given a transaction with the proper lock time set.
|
|
receiverHtlcScript := closeTx.TxOut[outHtlcResolution.ClaimOutpoint.Index].PkScript
|
|
sweepTx := wire.NewMsgTx(2)
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: outHtlcResolution.ClaimOutpoint,
|
|
})
|
|
sweepTx.AddTxOut(&wire.TxOut{
|
|
PkScript: receiverHtlcScript,
|
|
Value: outHtlcResolution.SweepSignDesc.Output.Value,
|
|
})
|
|
outHtlcResolution.SweepSignDesc.InputIndex = 0
|
|
outHtlcResolution.SweepSignDesc.SigHashes = txscript.NewTxSigHashes(
|
|
sweepTx,
|
|
)
|
|
sweepTx.LockTime = outHtlcResolution.Expiry
|
|
|
|
// With the transaction constructed, we'll generate a witness that
|
|
// should be valid for it, and verify using an instance of Script.
|
|
sweepTx.TxIn[0].Witness, err = receiverHtlcSpendTimeout(
|
|
aliceChannel.signer, &outHtlcResolution.SweepSignDesc,
|
|
sweepTx, int32(outHtlcResolution.Expiry),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to witness: %v", err)
|
|
}
|
|
vm, err := txscript.NewEngine(
|
|
outHtlcResolution.SweepSignDesc.Output.PkScript,
|
|
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
|
nil, outHtlcResolution.SweepSignDesc.Output.Value,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create engine: %v", err)
|
|
}
|
|
if err := vm.Execute(); err != nil {
|
|
t.Fatalf("htlc timeout spend is invalid: %v", err)
|
|
}
|
|
|
|
// Next, we'll ensure that we're able to sweep the incoming HTLC with a
|
|
// similar sweep transaction, this time using the payment pre-image.
|
|
senderHtlcScript := closeTx.TxOut[inHtlcResolution.ClaimOutpoint.Index].PkScript
|
|
sweepTx = wire.NewMsgTx(2)
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: inHtlcResolution.ClaimOutpoint,
|
|
})
|
|
sweepTx.AddTxOut(&wire.TxOut{
|
|
PkScript: senderHtlcScript,
|
|
Value: inHtlcResolution.SweepSignDesc.Output.Value,
|
|
})
|
|
inHtlcResolution.SweepSignDesc.InputIndex = 0
|
|
inHtlcResolution.SweepSignDesc.SigHashes = txscript.NewTxSigHashes(
|
|
sweepTx,
|
|
)
|
|
sweepTx.TxIn[0].Witness, err = SenderHtlcSpendRedeem(
|
|
aliceChannel.signer, &inHtlcResolution.SweepSignDesc,
|
|
sweepTx, preimageBob[:],
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate witness for success "+
|
|
"output: %v", err)
|
|
}
|
|
|
|
// Finally, we'll verify the constructed witness to ensure that Alice
|
|
// can properly sweep the output.
|
|
vm, err = txscript.NewEngine(
|
|
inHtlcResolution.SweepSignDesc.Output.PkScript,
|
|
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
|
nil, inHtlcResolution.SweepSignDesc.Output.Value,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create engine: %v", err)
|
|
}
|
|
if err := vm.Execute(); err != nil {
|
|
t.Fatalf("htlc timeout spend is invalid: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestDesyncHTLCs checks that we cannot add HTLCs that would make the
|
|
// balance negative, when the remote and local update logs are desynced.
|
|
func TestDesyncHTLCs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We'll kick off the test by creating our channels which both are
|
|
// loaded with 5 BTC each.
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// First add one HTLC of value 4.1 BTC.
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(4.1 * btcutil.SatoshiPerBitcoin)
|
|
htlc, _ := createHTLC(0, htlcAmt)
|
|
aliceIndex, err := aliceChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
bobIndex, err := bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Lock this HTLC in.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete state update: %v", err)
|
|
}
|
|
|
|
// Now let let Bob fail this HTLC.
|
|
err = bobChannel.FailHTLC(bobIndex, []byte("failreason"), nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to cancel HTLC: %v", err)
|
|
}
|
|
if err := aliceChannel.ReceiveFailHTLC(aliceIndex, []byte("bad")); err != nil {
|
|
t.Fatalf("unable to recv htlc cancel: %v", err)
|
|
}
|
|
|
|
// Alice now has gotten all her original balance (5 BTC) back, however,
|
|
// adding a new HTLC at this point SHOULD fail, since if she adds the
|
|
// HTLC and signs the next state, Bob cannot assume she received the
|
|
// FailHTLC, and must assume she doesn't have the necessary balance
|
|
// available.
|
|
//
|
|
// We try adding an HTLC of value 1 BTC, which should fail because the
|
|
// balance is unavailable.
|
|
htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin)
|
|
htlc, _ = createHTLC(1, htlcAmt)
|
|
if _, err = aliceChannel.AddHTLC(htlc, nil); err != ErrBelowChanReserve {
|
|
t.Fatalf("expected ErrInsufficientBalance, instead received: %v",
|
|
err)
|
|
}
|
|
|
|
// Now do a state transition, which will ACK the FailHTLC, making Alice
|
|
// able to add the new HTLC.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete state update: %v", err)
|
|
}
|
|
if _, err = aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
}
|
|
|
|
// TODO(roasbeef): testing.Quick test case for retrans!!!
|
|
|
|
// TestMaxAcceptedHTLCs tests that the correct error message (ErrMaxHTLCNumber)
|
|
// is thrown when a node tries to accept more than MaxAcceptedHTLCs in a
|
|
// channel.
|
|
func TestMaxAcceptedHTLCs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We'll kick off the test by creating our channels which both are
|
|
// loaded with 5 BTC each.
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// One over the maximum number of HTLCs that either can accept.
|
|
const numHTLCs = 20
|
|
const numHTLCsReceived = 12
|
|
|
|
// Set the remote's required MaxAcceptedHtlcs. This means that alice
|
|
// can only offer the remote up to numHTLCs HTLCs.
|
|
aliceChannel.localChanCfg.MaxAcceptedHtlcs = numHTLCs
|
|
bobChannel.remoteChanCfg.MaxAcceptedHtlcs = numHTLCs
|
|
|
|
// Similarly, set the remote config's MaxAcceptedHtlcs. This means
|
|
// that the remote will be aware that Alice will only accept up to
|
|
// numHTLCsRecevied at a time.
|
|
aliceChannel.remoteChanCfg.MaxAcceptedHtlcs = numHTLCsReceived
|
|
bobChannel.localChanCfg.MaxAcceptedHtlcs = numHTLCsReceived
|
|
|
|
// Each HTLC amount is 0.1 BTC.
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(0.1 * btcutil.SatoshiPerBitcoin)
|
|
|
|
// Send the maximum allowed number of HTLCs.
|
|
for i := 0; i < numHTLCs; i++ {
|
|
htlc, _ := createHTLC(i, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
}
|
|
|
|
// The next HTLC should fail with ErrMaxHTLCNumber.
|
|
htlc, _ := createHTLC(numHTLCs, htlcAmt)
|
|
_, err = aliceChannel.AddHTLC(htlc, nil)
|
|
if err != ErrMaxHTLCNumber {
|
|
t.Fatalf("expected ErrMaxHTLCNumber, instead received: %v", err)
|
|
}
|
|
|
|
// After receiving the next HTLC, next state transition should fail
|
|
// with ErrMaxHTLCNumber.
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
err = forceStateTransition(aliceChannel, bobChannel)
|
|
if err != ErrMaxHTLCNumber {
|
|
t.Fatalf("expected ErrMaxHTLCNumber, instead received: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestMaxPendingAmount tests that the maximum overall pending HTLC value is met
|
|
// given several HTLCs that, combined, exceed this value. An ErrMaxPendingAmount
|
|
// error should be returned.
|
|
func TestMaxPendingAmount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We'll kick off the test by creating our channels which both are
|
|
// loaded with 5 BTC each.
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// We set the remote required MaxPendingAmount to 3 BTC. We will
|
|
// attempt to overflow this value and see if it gives us the
|
|
// ErrMaxPendingAmount error.
|
|
maxPending := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin * 3)
|
|
|
|
// We set the max pending amount of Alice's config. This mean that she
|
|
// cannot offer Bob HTLCs with a total value above this limit at a given
|
|
// time.
|
|
aliceChannel.localChanCfg.MaxPendingAmount = maxPending
|
|
bobChannel.remoteChanCfg.MaxPendingAmount = maxPending
|
|
|
|
// First, we'll add 2 HTLCs of 1.5 BTC each to Alice's commitment.
|
|
// This won't trigger Alice's ErrMaxPendingAmount error.
|
|
const numHTLCs = 2
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(1.5 * btcutil.SatoshiPerBitcoin)
|
|
for i := 0; i < numHTLCs; i++ {
|
|
htlc, _ := createHTLC(i, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
}
|
|
|
|
// We finally add one more HTLC of 0.1 BTC to Alice's commitment. This
|
|
// SHOULD trigger Alice's ErrMaxPendingAmount error.
|
|
htlcAmt = lnwire.NewMSatFromSatoshis(0.1 * btcutil.SatoshiPerBitcoin)
|
|
htlc, _ := createHTLC(numHTLCs, htlcAmt)
|
|
_, err = aliceChannel.AddHTLC(htlc, nil)
|
|
if err != ErrMaxPendingAmount {
|
|
t.Fatalf("expected ErrMaxPendingAmount, instead received: %v", err)
|
|
}
|
|
|
|
// And also Bob shouldn't be accepting this HTLC in the next state
|
|
// transition.
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
err = forceStateTransition(aliceChannel, bobChannel)
|
|
if err != ErrMaxPendingAmount {
|
|
t.Fatalf("expected ErrMaxPendingAmount, instead received: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestChanReserve tests that the ErrBelowChanReserve error is thrown when
|
|
// an HTLC is added that causes a node's balance to dip below its channel
|
|
// reserve limit.
|
|
func TestChanReserve(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
setupChannels := func() (*LightningChannel, *LightningChannel, func()) {
|
|
// We'll kick off the test by creating our channels which both are
|
|
// loaded with 5 BTC each.
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
|
|
// We set the remote required ChanReserve to 0.5 BTC. We will
|
|
// attempt to cause Alice's balance to dip below this amount and test
|
|
// whether it triggers the ErrBelowChanReserve error.
|
|
aliceMinReserve := btcutil.Amount(0.5 * btcutil.SatoshiPerBitcoin)
|
|
|
|
// Alice will need to keep her reserve above aliceMinReserve, so
|
|
// set this limit to here local config.
|
|
aliceChannel.localChanCfg.ChanReserve = aliceMinReserve
|
|
|
|
// During channel opening Bob will also get to know Alice's minimum
|
|
// reserve, and this will be found in his remote config.
|
|
bobChannel.remoteChanCfg.ChanReserve = aliceMinReserve
|
|
|
|
// We set Bob's channel reserve to a value that is larger than his
|
|
// current balance in the channel. This will ensure that after a
|
|
// channel is first opened, Bob can still receive HTLCs
|
|
// even though his balance is less than his channel reserve.
|
|
bobMinReserve := btcutil.Amount(6 * btcutil.SatoshiPerBitcoin)
|
|
bobChannel.localChanCfg.ChanReserve = bobMinReserve
|
|
aliceChannel.remoteChanCfg.ChanReserve = bobMinReserve
|
|
|
|
return aliceChannel, bobChannel, cleanUp
|
|
}
|
|
aliceChannel, bobChannel, cleanUp := setupChannels()
|
|
defer cleanUp()
|
|
|
|
aliceIndex := 0
|
|
bobIndex := 0
|
|
|
|
// Add an HTLC that will increase Bob's balance. This should
|
|
// succeed, since Alice stays above her channel reserve, and
|
|
// Bob increases his balance (while still being below his
|
|
// channel reserve).
|
|
// Resulting balances:
|
|
// Alice: 4.5
|
|
// Bob: 5.5
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(0.5 * btcutil.SatoshiPerBitcoin)
|
|
htlc, _ := createHTLC(aliceIndex, htlcAmt)
|
|
aliceIndex++
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Force a state transation, making sure this HTLC is considered
|
|
// valid even though the channel reserves are not met.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete state update: %v", err)
|
|
}
|
|
|
|
// Now let Bob try to add an HTLC. This should fail, since it
|
|
// will decrease his balance, which is already below the channel
|
|
// reserve.
|
|
// Resulting balances:
|
|
// Alice: 4.5
|
|
// Bob: 5.5
|
|
htlc, _ = createHTLC(bobIndex, htlcAmt)
|
|
bobIndex++
|
|
_, err := bobChannel.AddHTLC(htlc, nil)
|
|
if err != ErrBelowChanReserve {
|
|
t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err)
|
|
}
|
|
|
|
// Alice will reject this htlc when a state transition is attempted.
|
|
if _, err := aliceChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
err = forceStateTransition(aliceChannel, bobChannel)
|
|
if err != ErrBelowChanReserve {
|
|
t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err)
|
|
}
|
|
|
|
// We must setup the channels again, since a violation of the channel
|
|
// constraints leads to channel shutdown.
|
|
aliceChannel, bobChannel, cleanUp = setupChannels()
|
|
defer cleanUp()
|
|
|
|
aliceIndex = 0
|
|
bobIndex = 0
|
|
|
|
// Now we'll add HTLC of 3.5 BTC to Alice's commitment, this should
|
|
// put Alice's balance at 1.5 BTC.
|
|
// Resulting balances:
|
|
// Alice: 1.5
|
|
// Bob: 9.5
|
|
htlcAmt = lnwire.NewMSatFromSatoshis(3.5 * btcutil.SatoshiPerBitcoin)
|
|
|
|
// The first HTLC should successfully be sent.
|
|
htlc, _ = createHTLC(aliceIndex, htlcAmt)
|
|
aliceIndex++
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// Add a second HTLC of 1 BTC. This should fail because it will take
|
|
// Alice's balance all the way down to her channel reserve, but
|
|
// since she is the initiator the additional transaction fee makes
|
|
// her balance dip below.
|
|
htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin)
|
|
htlc, _ = createHTLC(aliceIndex, htlcAmt)
|
|
aliceIndex++
|
|
_, err = aliceChannel.AddHTLC(htlc, nil)
|
|
if err != ErrBelowChanReserve {
|
|
t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err)
|
|
}
|
|
|
|
// Likewise, Bob will reject a state transition after this htlc is
|
|
// received, of the same reason.
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
err = forceStateTransition(aliceChannel, bobChannel)
|
|
if err != ErrBelowChanReserve {
|
|
t.Fatalf("expected ErrBelowChanReserve, instead received: %v", err)
|
|
}
|
|
|
|
// We must setup the channels again, since a violation of the channel
|
|
// constraints leads to channel shutdown.
|
|
aliceChannel, bobChannel, cleanUp = setupChannels()
|
|
defer cleanUp()
|
|
|
|
aliceIndex = 0
|
|
bobIndex = 0
|
|
|
|
// Add a HTLC of 2 BTC to Alice, and the settle it.
|
|
// Resulting balances:
|
|
// Alice: 3.0
|
|
// Bob: 7.0
|
|
htlcAmt = lnwire.NewMSatFromSatoshis(2 * btcutil.SatoshiPerBitcoin)
|
|
htlc, preimage := createHTLC(aliceIndex, htlcAmt)
|
|
aliceIndex++
|
|
aliceHtlcIndex, err := aliceChannel.AddHTLC(htlc, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
bobHtlcIndex, err := bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
if err := bobChannel.SettleHTLC(preimage, bobHtlcIndex, nil, nil, nil); err != nil {
|
|
t.Fatalf("bob unable to settle inbound htlc: %v", err)
|
|
}
|
|
if err := aliceChannel.ReceiveHTLCSettle(preimage, aliceHtlcIndex); err != nil {
|
|
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
|
|
}
|
|
|
|
// And now let Bob add an HTLC of 1 BTC. This will take Bob's balance
|
|
// all the way down to his channel reserve, but since he is not paying the
|
|
// fee this is okay.
|
|
htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin)
|
|
htlc, _ = createHTLC(bobIndex, htlcAmt)
|
|
bobIndex++
|
|
if _, err := bobChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := aliceChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
// Do a last state transition, which should succeed.
|
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
|
t.Fatalf("unable to complete state update: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestMinHTLC tests that the ErrBelowMinHTLC error is thrown if an HTLC is added
|
|
// that is below the minimm allowed value for HTLCs.
|
|
func TestMinHTLC(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// We'll kick off the test by creating our channels which both are
|
|
// loaded with 5 BTC each.
|
|
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// We set Alice's MinHTLC to 0.1 BTC. We will attempt to send an
|
|
// HTLC BELOW this value to trigger the ErrBelowMinHTLC error.
|
|
minValue := lnwire.NewMSatFromSatoshis(0.1 * btcutil.SatoshiPerBitcoin)
|
|
|
|
// Setting the min value in Alice's local config means that the
|
|
// remote will not accept any HTLCs of value less than specified.
|
|
aliceChannel.localChanCfg.MinHTLC = minValue
|
|
bobChannel.remoteChanCfg.MinHTLC = minValue
|
|
|
|
// First, we will add an HTLC of 0.5 BTC. This will not trigger
|
|
// ErrBelowMinHTLC.
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(0.5 * btcutil.SatoshiPerBitcoin)
|
|
htlc, _ := createHTLC(0, htlcAmt)
|
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
|
t.Fatalf("unable to add htlc: %v", err)
|
|
}
|
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
|
t.Fatalf("unable to recv htlc: %v", err)
|
|
}
|
|
|
|
// We add an HTLC below the min value, this should result in
|
|
// an ErrBelowMinHTLC error.
|
|
amt := minValue - 100
|
|
htlc, _ = createHTLC(1, amt)
|
|
_, err = aliceChannel.AddHTLC(htlc, nil)
|
|
if err != ErrBelowMinHTLC {
|
|
t.Fatalf("expected ErrBelowMinHTLC, instead received: %v", err)
|
|
}
|
|
|
|
// Bob will receive this HTLC, but reject the next state update, since
|
|
// the htlc is too small.
|
|
_, err = bobChannel.ReceiveHTLC(htlc)
|
|
if err != nil {
|
|
t.Fatalf("error receiving htlc: %v", err)
|
|
}
|
|
err = forceStateTransition(aliceChannel, bobChannel)
|
|
if err != ErrBelowMinHTLC {
|
|
t.Fatalf("expected ErrBelowMinHTLC, instead received: %v", err)
|
|
}
|
|
}
|