a6e7dce7b7
This commit adds a check that will make LightningChannel reject a received commitment if it is accompanied with too many HTLC signatures. This enforces the requirement in BOLT-2, saying: if num_htlcs is not equal to the number of HTLC outputs in the local commitment transaction: * MUST fail the channel. A test exercising the behaviour is added.
5011 lines
173 KiB
Go
5011 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 := btcutil.Amount(10 * 1e8)
|
|
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)
|
|
}
|
|
}
|