219 lines
7.2 KiB
Go
219 lines
7.2 KiB
Go
package lnwallet
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"li.lan/labs/plasma/chainntfs"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/btcutil/txsort"
|
|
)
|
|
|
|
const (
|
|
// TODO(roasbeef): make not random value
|
|
MaxPendingPayments = 10
|
|
)
|
|
|
|
// LightningChannel...
|
|
// TODO(roasbeef): future peer struct should embed this struct
|
|
type LightningChannel struct {
|
|
wallet *LightningWallet
|
|
channelEvents *chainntnfs.ChainNotifier
|
|
|
|
// TODO(roasbeef): Stores all previous R values + timeouts for each
|
|
// commitment update, plus some other meta-data...Or just use OP_RETURN
|
|
// to help out?
|
|
// currently going for: nSequence/nLockTime overloading
|
|
channelDB *ChannelDB
|
|
|
|
// stateMtx protects concurrent access to the state struct.
|
|
stateMtx sync.RWMutex
|
|
channelState OpenChannelState
|
|
|
|
// TODO(roasbeef): create and embed 'Service' interface w/ below?
|
|
started int32
|
|
shutdown int32
|
|
|
|
quit chan struct{}
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// newLightningChannel...
|
|
func newLightningChannel(wallet *LightningWallet, events *chainntnfs.ChainNotifier,
|
|
chanDB *ChannelDB, state OpenChannelState) (*LightningChannel, error) {
|
|
|
|
return &LightningChannel{
|
|
wallet: wallet,
|
|
channelEvents: events,
|
|
channelDB: chanDB,
|
|
channelState: state,
|
|
}, nil
|
|
}
|
|
|
|
// AddHTLC...
|
|
func (lc *LightningChannel) AddHTLC() {
|
|
}
|
|
|
|
// SettleHTLC...
|
|
func (lc *LightningChannel) SettleHTLC() {
|
|
}
|
|
|
|
// OurBalance...
|
|
func (lc *LightningChannel) OurBalance() btcutil.Amount {
|
|
return 0
|
|
}
|
|
|
|
// TheirBalance...
|
|
func (lc *LightningChannel) TheirBalance() btcutil.Amount {
|
|
return 0
|
|
}
|
|
|
|
// CurrentCommitTx...
|
|
func (lc *LightningChannel) CurrentCommitTx() *btcutil.Tx {
|
|
return nil
|
|
}
|
|
|
|
// SignTheirCommitTx...
|
|
func (lc *LightningChannel) SignTheirCommitTx(commitTx *btcutil.Tx) error {
|
|
return nil
|
|
}
|
|
|
|
// AddTheirSig...
|
|
func (lc *LightningChannel) AddTheirSig(sig []byte) error {
|
|
return nil
|
|
}
|
|
|
|
// VerifyCommitmentUpdate...
|
|
func (lc *LightningChannel) VerifyCommitmentUpdate() error {
|
|
return nil
|
|
}
|
|
|
|
// createCommitTx...
|
|
func createCommitTx(fundingOutput *wire.TxIn, ourKey, theirKey *btcec.PublicKey,
|
|
revokeHash [wire.HashSize]byte, csvTimeout uint32, channelAmt btcutil.Amount) (*wire.MsgTx, error) {
|
|
|
|
// First, we create the script paying to us. This script is spendable
|
|
// under two conditions: either the 'csvTimeout' has passed and we can
|
|
// redeem our funds, or they have the pre-image to 'revokeHash'.
|
|
scriptToUs := txscript.NewScriptBuilder()
|
|
|
|
// If the pre-image for the revocation hash is presented, then allow a
|
|
// spend provided the proper signature.
|
|
scriptToUs.AddOp(txscript.OP_HASH160)
|
|
scriptToUs.AddData(revokeHash[:])
|
|
scriptToUs.AddOp(txscript.OP_EQUAL)
|
|
scriptToUs.AddOp(txscript.OP_IF)
|
|
scriptToUs.AddData(theirKey.SerializeCompressed())
|
|
scriptToUs.AddOp(txscript.OP_ELSE)
|
|
|
|
// Otherwise, we can re-claim our funds after a CSV delay of
|
|
// 'csvTimeout' timeout blocks, and a valid signature.
|
|
scriptToUs.AddInt64(int64(csvTimeout))
|
|
scriptToUs.AddOp(txscript.OP_NOP3) // CSV
|
|
scriptToUs.AddOp(txscript.OP_DROP)
|
|
scriptToUs.AddData(ourKey.SerializeCompressed())
|
|
scriptToUs.AddOp(txscript.OP_ENDIF)
|
|
scriptToUs.AddOp(txscript.OP_CHECKSIG)
|
|
|
|
// TODO(roasbeef): store
|
|
ourRedeemScript, err := scriptToUs.Script()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payToUsScriptHash, err := scriptHashPkScript(ourRedeemScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Next, we create the script paying to them. This is just a regular
|
|
// P2PKH-ike output. However, we instead use P2SH.
|
|
scriptToThem := txscript.NewScriptBuilder()
|
|
scriptToThem.AddOp(txscript.OP_DUP)
|
|
scriptToThem.AddOp(txscript.OP_HASH160)
|
|
scriptToThem.AddData(btcutil.Hash160(theirKey.SerializeCompressed()))
|
|
scriptToThem.AddOp(txscript.OP_EQUALVERIFY)
|
|
scriptToThem.AddOp(txscript.OP_CHECKSIG)
|
|
|
|
theirRedeemScript, err := scriptToThem.Script()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payToThemScriptHash, err := scriptHashPkScript(theirRedeemScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Now that both output scripts have been created, we can finally create
|
|
// the transaction itself.
|
|
commitTx := wire.NewMsgTx()
|
|
commitTx.AddTxIn(fundingOutput)
|
|
commitTx.AddTxOut(wire.NewTxOut(int64(channelAmt), payToUsScriptHash))
|
|
commitTx.AddTxOut(wire.NewTxOut(int64(channelAmt), payToThemScriptHash))
|
|
|
|
// Sort the transaction according to the agreed upon cannonical
|
|
// ordering. This lets us skip sending the entire transaction over,
|
|
// instead we'll just send signatures.
|
|
txsort.InPlaceSort(commitTx)
|
|
return commitTx, nil
|
|
}
|
|
|
|
//TODO(j): Creates a CLTV-only funding Tx (reserve is *REQUIRED*)
|
|
//This works for only CLTV soft-fork (no CSV/segwit soft-fork in yet)
|
|
//
|
|
//Commit funds to Funding Tx, will timeout after the fundingTimeLock and refund
|
|
//back using CLTV. As there is no way to enforce HTLCs, we rely upon a reserve
|
|
//and have each party's HTLCs in-transit be less than their Commitment reserve.
|
|
//In the event that someone incorrectly broadcasts an old Commitment TX, then
|
|
//the counterparty claims the full reserve. It may be possible for either party
|
|
//to claim the HTLC(!!! But it's okay because the "honest" party is made whole
|
|
//via the reserve). If it's two-funder there are two outputs and the
|
|
//Commitments spends from both outputs in the Funding Tx. Two-funder requires
|
|
//the ourKey/theirKey sig positions to be swapped (should be in 1 funding tx).
|
|
//
|
|
//Quick note before I forget: The revocation hash is used in CLTV-only for
|
|
//single-funder (without an initial payment) *as part of an additional output
|
|
//in the Commitment Tx for the reserve*. This is to establish a unidirectional
|
|
//channel UNITL the recipient has sufficient funds. When the recipient has
|
|
//sufficient funds, the revocation is exchanged and allows the recipient to
|
|
//claim the full reserve as penalty if the incorrect Commitment is broadcast
|
|
//(otherwise it's timelocked refunded back to the sender). From then on, there
|
|
//is no additional output in Commitment Txes. [side caveat, first payment must
|
|
//be above minimum UTXO output size in single-funder] For now, let's keep it
|
|
//simple and assume dual funder (with both funding above reserve)
|
|
func createCLTVFundingTx(fundingTimeLock int64, ourKey *btcec.PublicKey, theirKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
|
script := txscript.NewScriptBuilder()
|
|
//In the scriptSig on the top of the stack, there will be either a 0 or
|
|
//1 pushed.
|
|
//So the scriptSig will be either:
|
|
//<BobSig> <AliceSig> <1>
|
|
//<BobSig> <RevocationHash> <0>
|
|
//(Alice and Bob can be swapped depending on who's funding)
|
|
|
|
//If this is a 2-of-2 multisig, read the first sig
|
|
script.AddOp(txscript.OP_IF)
|
|
//Sig2 (not P2PKH, the pubkey is in the redeemScript)
|
|
script.AddData(ourKey.SerializeCompressed())
|
|
script.AddOp(txscript.OP_CHECKSIGVERIFY) //gotta be verify!
|
|
|
|
//If this is timed out
|
|
script.AddOp(txscript.OP_ELSE)
|
|
script.AddInt64(fundingTimeLock)
|
|
script.AddOp(txscript.OP_NOP2) //CLTV
|
|
//Sig (not P2PKH, the pubkey is in the redeemScript)
|
|
script.AddOp(txscript.OP_CHECKSIG)
|
|
script.AddOp(txscript.OP_DROP)
|
|
script.AddOp(txscript.OP_ENDIF)
|
|
|
|
//Read the other sig if it's 2-of-2, only one if it's timed out
|
|
script.AddData(theirKey.SerializeCompressed())
|
|
script.AddOp(txscript.OP_CHECKSIG)
|
|
|
|
fundingTx := wire.NewMsgTx()
|
|
//TODO(j) Add the inputs/outputs
|
|
|
|
return fundingTx, nil
|
|
}
|