lnwallet: add commitment transaction estimation
This commit is contained in:
parent
5b9e4ae61e
commit
391d5cd401
@ -25,6 +25,10 @@ var (
|
||||
ErrChanClosing = fmt.Errorf("channel is being closed, operation disallowed")
|
||||
ErrNoWindow = fmt.Errorf("unable to sign new commitment, the current" +
|
||||
" revocation window is exhausted")
|
||||
ErrMaxWeightCost = fmt.Errorf("commitment transaction exceed max " +
|
||||
"available weight")
|
||||
ErrMaxHTLCNumber = fmt.Errorf("commitment transaction exceed max " +
|
||||
"htlc number")
|
||||
)
|
||||
|
||||
const (
|
||||
@ -1321,7 +1325,6 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.CommitRevocation,
|
||||
// commitment, and a log compaction is attempted. In addition, a slice of
|
||||
// HTLC's which can be forwarded upstream are returned.
|
||||
func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.CommitRevocation) ([]*PaymentDescriptor, error) {
|
||||
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
@ -1736,24 +1739,9 @@ type ForceCloseSummary struct {
|
||||
SelfOutputSignDesc *SignDescriptor
|
||||
}
|
||||
|
||||
// ForceClose executes a unilateral closure of the transaction at the current
|
||||
// lowest commitment height of the channel. Following a force closure, all
|
||||
// state transitions, or modifications to the state update logs will be
|
||||
// rejected. Additionally, this function also returns a ForceCloseSummary which
|
||||
// includes the necessary details required to sweep all the time-locked within
|
||||
// the commitment transaction.
|
||||
//
|
||||
// TODO(roasbeef): all methods need to abort if in dispute state
|
||||
// TODO(roasbeef): method to generate CloseSummaries for when the remote peer
|
||||
// does a unilateral close
|
||||
func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
// Set the channel state to indicate that the channel is now in a
|
||||
// contested state.
|
||||
lc.status = channelDispute
|
||||
|
||||
// getSignedCommitTx function take the latest commitment transaction and populate
|
||||
// it with witness data.
|
||||
func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
|
||||
// Fetch the current commitment transaction, along with their signature
|
||||
// for the transaction.
|
||||
commitTx := lc.channelState.OurCommitTx
|
||||
@ -1773,9 +1761,35 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
|
||||
// required to spend from the multi-sig output.
|
||||
ourKey := lc.channelState.OurMultiSigKey.SerializeCompressed()
|
||||
theirKey := lc.channelState.TheirMultiSigKey.SerializeCompressed()
|
||||
witness := SpendMultiSig(lc.FundingWitnessScript, ourKey, ourSig,
|
||||
theirKey, theirSig)
|
||||
commitTx.TxIn[0].Witness = witness
|
||||
|
||||
commitTx.TxIn[0].Witness = SpendMultiSig(lc.FundingWitnessScript, ourKey,
|
||||
ourSig, theirKey, theirSig)
|
||||
|
||||
return commitTx, nil
|
||||
}
|
||||
|
||||
// ForceClose executes a unilateral closure of the transaction at the current
|
||||
// lowest commitment height of the channel. Following a force closure, all
|
||||
// state transitions, or modifications to the state update logs will be
|
||||
// rejected. Additionally, this function also returns a ForceCloseSummary which
|
||||
// includes the necessary details required to sweep all the time-locked within
|
||||
// the commitment transaction.
|
||||
//
|
||||
// TODO(roasbeef): all methods need to abort if in dispute state
|
||||
// TODO(roasbeef): method to generate CloseSummaries for when the remote peer
|
||||
// does a unilateral close
|
||||
func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
// Set the channel state to indicate that the channel is now in a
|
||||
// contested state.
|
||||
lc.status = channelDispute
|
||||
|
||||
commitTx, err := lc.getSignedCommitTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Locate the output index of the delayed commitment output back to us.
|
||||
// We'll return the details of this output to the caller so they can
|
||||
|
@ -9,10 +9,12 @@ import (
|
||||
|
||||
"github.com/btcsuite/fastsha256"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/elkrem"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/roasbeef/btcd/blockchain"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/txscript"
|
||||
@ -253,6 +255,7 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
|
||||
OurBalance: channelBal,
|
||||
TheirBalance: channelBal,
|
||||
OurCommitTx: aliceCommitTx,
|
||||
OurCommitSig: bytes.Repeat([]byte{1}, 71),
|
||||
FundingOutpoint: prevOut,
|
||||
OurMultiSigKey: aliceKeyPub,
|
||||
TheirMultiSigKey: bobKeyPub,
|
||||
@ -276,6 +279,7 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
|
||||
OurBalance: channelBal,
|
||||
TheirBalance: channelBal,
|
||||
OurCommitTx: bobCommitTx,
|
||||
OurCommitSig: bytes.Repeat([]byte{1}, 71),
|
||||
FundingOutpoint: prevOut,
|
||||
OurMultiSigKey: bobKeyPub,
|
||||
TheirMultiSigKey: aliceKeyPub,
|
||||
@ -592,6 +596,94 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckCommitTxSize checks that estimation size of commitment
|
||||
// transaction with some degree of error corresponds to the actual size.
|
||||
func TestCheckCommitTxSize(t *testing.T) {
|
||||
checkSize := func(channel *LightningChannel, count int) {
|
||||
// Due to variable size of the signatures (71-73) we may have
|
||||
// an estimation error.
|
||||
BaseCommitmentTxSizeEstimationError := 4
|
||||
|
||||
commitTx, err := channel.getSignedCommitTx()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to initiate alice force close: %v", err)
|
||||
}
|
||||
|
||||
actualCost := blockchain.GetMsgTxCost(commitTx)
|
||||
estimatedCost := estimateCommitTxCost(count, false)
|
||||
|
||||
diff := int(estimatedCost - actualCost)
|
||||
if 0 > diff || BaseCommitmentTxSizeEstimationError < diff {
|
||||
t.Fatalf("estimation is wrong")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
createHTLC := func(i int) (*lnwire.HTLCAddRequest, [32]byte) {
|
||||
preimage := bytes.Repeat([]byte{byte(i)}, 32)
|
||||
paymentHash := fastsha256.Sum256(preimage)
|
||||
|
||||
var returnPreimage [32]byte
|
||||
copy(returnPreimage[:], preimage)
|
||||
|
||||
return &lnwire.HTLCAddRequest{
|
||||
RedemptionHashes: [][32]byte{paymentHash},
|
||||
Amount: lnwire.CreditsAmount(1e7),
|
||||
Expiry: uint32(5),
|
||||
}, returnPreimage
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
// 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 := 1; i <= 10; i++ {
|
||||
htlc, _ := createHTLC(i)
|
||||
|
||||
if _, err := aliceChannel.AddHTLC(htlc); 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)
|
||||
}
|
||||
|
||||
forceStateTransition(aliceChannel, bobChannel)
|
||||
checkSize(aliceChannel, i)
|
||||
checkSize(bobChannel, i)
|
||||
}
|
||||
|
||||
// Settle HTLCs and check that estimation is counting cost of settle
|
||||
// HTLCs properly.
|
||||
for i := 10; i >= 1; i-- {
|
||||
_, preimage := createHTLC(i)
|
||||
|
||||
settleIndex, err := bobChannel.SettleHTLC(preimage)
|
||||
if err != nil {
|
||||
t.Fatalf("bob unable to settle inbound htlc: %v", err)
|
||||
}
|
||||
err = aliceChannel.ReceiveHTLCSettle(preimage, settleIndex)
|
||||
if err != nil {
|
||||
t.Fatalf("alice unable to accept settle of outbound htlc: %v", err)
|
||||
}
|
||||
|
||||
forceStateTransition(aliceChannel, bobChannel)
|
||||
checkSize(aliceChannel, i-1)
|
||||
checkSize(bobChannel, i-1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCooperativeChannelClosure(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
|
136
lnwallet/size.go
Normal file
136
lnwallet/size.go
Normal file
@ -0,0 +1,136 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"github.com/roasbeef/btcd/blockchain"
|
||||
)
|
||||
|
||||
const (
|
||||
WitnessFactor = blockchain.WitnessScaleFactor
|
||||
MaxTransactionWeightPolicy = blockchain.MaxBlockCost / 10
|
||||
|
||||
// The weight(cost), which is different from the !size! (see BIP-141),
|
||||
// is calculated as:
|
||||
// Weight = 4 * BaseSize + WitnessSize (weight).
|
||||
// BaseSize - size of the transaction without witness data (bytes).
|
||||
// WitnessSize - witness size (bytes).
|
||||
// Weight - the metric for determining the cost of the transaction.
|
||||
|
||||
// P2WSH: 34 bytes
|
||||
// - OP_0: 1 byte
|
||||
// - OP_DATA: 1 byte (WitnessScriptSHA256 length)
|
||||
// - WitnessScriptSHA256: 32 bytes
|
||||
P2WSHSize = 1 + 1 + 32
|
||||
|
||||
// P2PKH: 22 bytes
|
||||
// - OP_0: 1 byte
|
||||
// - OP_DATA: 1 byte (PublicKeyHASH160 length)
|
||||
// - PublicKeyHASH160: 20 bytes
|
||||
P2WPKHSize = 1 + 1 + 20
|
||||
|
||||
// MultiSig: 71 bytes
|
||||
// - OP_2: 1 byte
|
||||
// - OP_DATA: 1 byte (pubKeyAlice length)
|
||||
// - pubKeyAlice: 33 bytes
|
||||
// - OP_DATA: 1 byte (pubKeyBob length)
|
||||
// - pubKeyBob: 33 bytes
|
||||
// - OP_2: 1 byte
|
||||
// - OP_CHECKMULTISIG: 1 byte
|
||||
MultiSigSize = 1 + 1 + 33 + 1 + 33 + 1 + 1
|
||||
|
||||
// Witness: 222 bytes
|
||||
// - NumberOfWitnessElements: 1 byte
|
||||
// - NilLength: 1 byte
|
||||
// - sigAliceLength: 1 byte
|
||||
// - sigAlice: 73 bytes
|
||||
// - sigBobLength: 1 byte
|
||||
// - sigBob: 73 bytes
|
||||
// - WitnessScriptLength: 1 byte
|
||||
// - WitnessScript (MultiSig)
|
||||
WitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + MultiSigSize
|
||||
|
||||
// FundingInput: 41 bytes
|
||||
// - PreviousOutPoint:
|
||||
// - Hash: 32 bytes
|
||||
// - Index: 4 bytes
|
||||
// - OP_DATA: 1 byte (ScriptSigLength)
|
||||
// - ScriptSig: 0 bytes
|
||||
// - Witness <---- we use "Witness" instead of "ScriptSig" for
|
||||
// transaction validation, but "Witness" is stored
|
||||
// separately and cost for it size is smaller. So
|
||||
// we separate the calculation of ordinary data
|
||||
// from witness data.
|
||||
// - Sequence: 4 bytes
|
||||
FundingInputSize = 32 + 4 + 1 + 4
|
||||
|
||||
// OutputPayingToUs: 43 bytes
|
||||
// - Value: 8 bytes
|
||||
// - VarInt: 1 byte (PkScript length)
|
||||
// - PkScript (P2WSH)
|
||||
CommitmentDelayOutput = 8 + 1 + P2WSHSize
|
||||
|
||||
// OutputPayingToThem: 31 bytes
|
||||
// - Value: 8 bytes
|
||||
// - VarInt: 1 byte (PkScript length)
|
||||
// - PkScript (P2WPKH)
|
||||
CommitmentKeyHashOutput = 8 + 1 + P2WPKHSize
|
||||
|
||||
// HTLCOutput: 43 bytes
|
||||
// - Value: 8 bytes
|
||||
// - VarInt: 1 byte (PkScript length)
|
||||
// - PkScript (PW2SH)
|
||||
HTLCSize = 8 + 1 + P2WSHSize
|
||||
|
||||
// WitnessHeader: 2 bytes
|
||||
// - Flag: 1 byte
|
||||
// - Marker: 1 byte
|
||||
WitnessHeaderSize = 1 + 1
|
||||
|
||||
// CommitmentTransaction: 125 bytes
|
||||
// - Version: 4 bytes
|
||||
// - WitnessHeader <---- part of the witness data
|
||||
// - CountTxIn: 1 byte
|
||||
// - TxIn:
|
||||
// FundingInput
|
||||
// - CountTxOut: 1 byte
|
||||
// - TxOut:
|
||||
// OutputPayingToThem,
|
||||
// OutputPayingToUs,
|
||||
// ....HTLCOutputs...
|
||||
// - LockTime: 4 bytes
|
||||
BaseCommitmentTxSize = 4 + 1 + FundingInputSize + 1 +
|
||||
CommitmentDelayOutput + CommitmentKeyHashOutput + 4
|
||||
|
||||
// CommitmentTransactionCost: 500 weight
|
||||
BaseCommitmentTxCost = WitnessFactor * BaseCommitmentTxSize
|
||||
|
||||
// WitnessCommitmentTxCost: 224 weight
|
||||
WitnessCommitmentTxCost = WitnessHeaderSize + WitnessSize
|
||||
|
||||
// HTLCCost: 172 weight
|
||||
HTLCCost = WitnessFactor * HTLCSize
|
||||
|
||||
// MaxHTLCNumber shows as the maximum number HTLCs which can be
|
||||
// included in commitment transaction. This numbers was calculated by
|
||||
// Rusty Russel in "BOLT #5: Recommendations for On-chain Transaction
|
||||
// Handling", based on the fact that we need to sweep all HTLCs within
|
||||
// one penalty transaction.
|
||||
MaxHTLCNumber = 1253
|
||||
)
|
||||
|
||||
|
||||
// estimateCommitTxCost estimate commitment transaction cost depending on the
|
||||
// precalculated cost of base transaction, witness data, which is needed for
|
||||
// paying for funding tx, and htlc cost multiplied by their count.
|
||||
func estimateCommitTxCost(count int, prediction bool) int64 {
|
||||
// Make prediction about the size of commitment transaction with
|
||||
// additional HTLC.
|
||||
if prediction {
|
||||
count++
|
||||
}
|
||||
|
||||
htlcCost := int64(count * HTLCCost)
|
||||
baseCost := int64(BaseCommitmentTxCost)
|
||||
witnessCost := int64(WitnessCommitmentTxCost)
|
||||
|
||||
return htlcCost + baseCost + witnessCost
|
||||
}
|
@ -15,10 +15,6 @@ const (
|
||||
// ErrorMaxPendingChannels is returned by remote peer when the number
|
||||
// of active pending channels exceeds their maximum policy limit.
|
||||
ErrorMaxPendingChannels ErrorCode = 1
|
||||
|
||||
// ErrorMaxTransactionWeight is returned by remote peer when transaction
|
||||
// weight exceed maximum allowable value.
|
||||
ErrorMaxTransactionWeight ErrorCode = 2
|
||||
)
|
||||
|
||||
// ErrorGeneric represents a generic error bound to an exact channel. The
|
||||
|
Loading…
Reference in New Issue
Block a user