2015-11-05 23:36:33 +03:00
|
|
|
package wallet
|
2015-11-13 05:43:32 +03:00
|
|
|
|
|
|
|
import (
|
2015-12-18 22:29:35 +03:00
|
|
|
"fmt"
|
2015-11-14 22:52:07 +03:00
|
|
|
"sync"
|
|
|
|
|
2015-12-18 22:29:35 +03:00
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
2015-11-13 05:43:32 +03:00
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ChannelReservation...
|
|
|
|
type ChannelReservation struct {
|
|
|
|
FundingType FundingType
|
|
|
|
|
2015-12-18 22:29:35 +03:00
|
|
|
//All of these are *our* values/requirements
|
|
|
|
//Their requirements can be the same or lower
|
|
|
|
FundingAmount btcutil.Amount //The amount we want to fund with
|
|
|
|
ReserveAmount btcutil.Amount //Our reserve. assume symmetric reserve amounts
|
|
|
|
MinFeePerKb btcutil.Amount
|
|
|
|
MinTotalFundingAmount btcutil.Amount //Our minimum value for the entire channel
|
|
|
|
|
|
|
|
//for CLTV it is nLockTime, for CSV it's nSequence, for segwit it's not needed
|
|
|
|
FundingLockTime uint32
|
2015-11-13 05:43:32 +03:00
|
|
|
|
2015-11-29 03:20:57 +03:00
|
|
|
sync.RWMutex // All fields below owned by the lnwallet.
|
2015-11-14 22:52:07 +03:00
|
|
|
|
2015-12-18 22:29:35 +03:00
|
|
|
//Current state of the channel, progesses through until complete
|
|
|
|
//Makes sure we can't go backwards and only accept messages once
|
|
|
|
channelState uint8
|
|
|
|
|
2015-11-14 22:52:07 +03:00
|
|
|
theirInputs []*wire.TxIn
|
|
|
|
ourInputs []*wire.TxIn
|
2015-11-13 05:43:32 +03:00
|
|
|
|
2015-12-18 22:29:35 +03:00
|
|
|
//NOTE(j): FundRequest assumes there is only one change (see ChangePkScript)
|
2015-11-14 22:52:07 +03:00
|
|
|
theirChange []*wire.TxOut
|
|
|
|
ourChange []*wire.TxOut
|
2015-11-13 05:43:32 +03:00
|
|
|
|
2015-11-14 22:52:07 +03:00
|
|
|
ourKey *btcec.PrivateKey
|
|
|
|
theirKey *btcec.PublicKey
|
2015-11-13 05:43:32 +03:00
|
|
|
|
|
|
|
// In order of sorted inputs. Sorting is done in accordance
|
|
|
|
// to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
2015-11-14 22:52:07 +03:00
|
|
|
theirSigs [][]byte
|
|
|
|
ourSigs [][]byte
|
2015-11-13 05:43:32 +03:00
|
|
|
|
2015-11-14 22:52:07 +03:00
|
|
|
normalizedTxID wire.ShaHash
|
2015-11-13 05:43:32 +03:00
|
|
|
|
2015-11-14 22:52:07 +03:00
|
|
|
fundingTx *wire.MsgTx
|
2015-11-13 05:43:32 +03:00
|
|
|
// TODO(roasbef): time locks, who pays fee etc.
|
|
|
|
// TODO(roasbeef): record Bob's ln-ID?
|
|
|
|
|
2015-11-14 22:52:07 +03:00
|
|
|
completedFundingTx *btcutil.Tx
|
2015-11-13 05:43:32 +03:00
|
|
|
|
|
|
|
reservationID uint64
|
|
|
|
wallet *LightningWallet
|
2015-12-16 00:22:18 +03:00
|
|
|
|
2015-12-18 22:29:35 +03:00
|
|
|
//For CSV/CLTV revocation
|
|
|
|
ourRevocation []byte
|
|
|
|
theirRevocation []byte
|
|
|
|
theirRevocationHash []byte
|
|
|
|
|
|
|
|
//Final delivery address (P2PKH for now)
|
|
|
|
ourDeliveryAddress []byte
|
|
|
|
theirDeliveryAddress []byte
|
|
|
|
|
2015-12-16 00:22:18 +03:00
|
|
|
chanOpen chan *LightningChannel
|
2015-11-13 05:43:32 +03:00
|
|
|
}
|
|
|
|
|
2015-12-18 22:29:35 +03:00
|
|
|
//FundRequest serialize
|
|
|
|
//(reading from ChannelReservation directly to reduce the amount of copies)
|
|
|
|
//We can move this stuff to another file too if it's too big...
|
|
|
|
func (r *ChannelReservation) SerializeFundRequest() ([]byte, error) {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
//Buffer to dump in the serialized data
|
|
|
|
b := new(bytes.Buffer)
|
|
|
|
|
|
|
|
//Fund Request
|
|
|
|
err = b.WriteByte(0x30)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//ChannelType (1)
|
|
|
|
//Default to current type
|
|
|
|
err = b.WriteByte(uint8(0))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//RequesterFundingAmount - The amount we are going to fund (8)
|
|
|
|
//check for positive values
|
|
|
|
err = binary.Write(b, binary.BigEndian, r.FundingAmount)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//RequesterChannelMinCapacity (8)
|
|
|
|
//The amount needed to accept and sign the channel commit later
|
|
|
|
err = binary.Write(b, binary.BigEndian, r.MinTotalFundingAmount)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//RevocationHash (20)
|
|
|
|
//Our revocation hash being contributed (for CLTV/CSV)
|
|
|
|
_, err = b.Write(btcutil.Hash160(r.ourRevocation))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//CommitPubkey (33)
|
|
|
|
//Our public key being used for the commitment
|
|
|
|
ourPubKey := r.ourKey.PubKey().SerializeCompressed()
|
|
|
|
if len(ourPubKey) != 33 { //validation, can remove later? (NO UNCOMPRESSED KEYS!)
|
|
|
|
return nil, fmt.Errorf("Serialize FundReq: our Pubkey length incorrect")
|
|
|
|
}
|
|
|
|
_, err = b.Write(ourPubKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//DeliveryPkHash (20)
|
|
|
|
//For now it's a P2PKH, but we will add an extra byte later for the
|
|
|
|
//option for P2SH
|
|
|
|
//This is the address to send funds to when complete or refunded
|
|
|
|
_, err = b.Write(r.ourDeliveryAddress)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//ReserveAmount (8)
|
|
|
|
//Our own reserve amount
|
|
|
|
err = binary.Write(b, binary.BigEndian, r.ReserveAmount)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//Minimum transaction fee per kb (8)
|
|
|
|
err = binary.Write(b, binary.BigEndian, r.MinFeePerKb)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//LockTime (4)
|
|
|
|
err = binary.Write(b, binary.BigEndian, r.FundingLockTime)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//Fee payer (default to split currently) (1)
|
|
|
|
err = binary.Write(b, binary.BigEndian, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//ChangePkScript
|
|
|
|
//Length (1)
|
|
|
|
changeScriptLength := len(r.ourChange[0].PkScript)
|
|
|
|
if changeScriptLength > 255 {
|
|
|
|
return nil, fmt.Errorf("Your changeScriptLength is too long!")
|
|
|
|
}
|
|
|
|
err = binary.Write(b, binary.BigEndian, uint8(changeScriptLength))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
//For now it's a P2PKH, but we will add an extra byte later for the
|
|
|
|
//option for P2SH
|
|
|
|
//This is the address to send change to (only allow one)
|
|
|
|
//ChangePkScript (length of script)
|
|
|
|
_, err = b.Write(r.ourChange[0].PkScript)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//Append the unsigned(!!) txins
|
|
|
|
//First one byte for the amount of txins (1)
|
|
|
|
if len(r.ourInputs) > 127 {
|
|
|
|
return nil, fmt.Errorf("Too many txins")
|
|
|
|
}
|
|
|
|
err = b.WriteByte(uint8(len(r.ourInputs)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
//Append the actual Txins (NumOfTxins * 36)
|
|
|
|
//Do not include the sequence number to eliminate funny business
|
|
|
|
for _, in := range r.ourInputs {
|
|
|
|
//Hash
|
|
|
|
_, err = b.Write(in.PreviousOutPoint.Hash.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
//Index
|
|
|
|
err = binary.Write(b, binary.BigEndian, in.PreviousOutPoint.Index)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.Bytes(), err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *ChannelReservation) DeserializeFundRequest(wireMsg []byte) error {
|
|
|
|
//Make sure we're not overwriting stuff...
|
|
|
|
//Update the channelState to 1 before progressing if you want to re-do it.
|
|
|
|
//Assumes only one thread is writing at a time
|
|
|
|
if r.channelState > 1 {
|
|
|
|
return fmt.Errorf("FundRequest: Channel State Mismatch")
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
b := bytes.NewBuffer(wireMsg)
|
|
|
|
msgid, _ := b.ReadByte()
|
|
|
|
if msgid != 0x30 {
|
|
|
|
return fmt.Errorf("Cannot deserialize: not a funding request")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
//Update the channel state as complete
|
|
|
|
r.channelState = 2
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//Validation on the data being supplied from the fund request
|
|
|
|
func (r *ChannelReservation) ValidateFundRequest(wireMsg []byte) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//Serialize CSV Revocation
|
|
|
|
//After the Commitment Transaction has been created, send a message to revoke this tx
|
|
|
|
func (r *ChannelReservation) SerializeCSVRefundRevocation() ([]byte, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//Deserialize CSV Revocation
|
|
|
|
//Validate the revocation, after this step, the channel is fully set up
|
|
|
|
func (r *ChannelReservation) DeserializeCSVRefundRevocation() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-13 05:43:32 +03:00
|
|
|
// newChannelReservation...
|
|
|
|
func newChannelReservation(t FundingType, fundingAmt btcutil.Amount,
|
|
|
|
minFeeRate btcutil.Amount, wallet *LightningWallet, id uint64) *ChannelReservation {
|
|
|
|
return &ChannelReservation{
|
|
|
|
FundingType: t,
|
|
|
|
FundingAmount: fundingAmt,
|
|
|
|
MinFeePerKb: minFeeRate,
|
|
|
|
wallet: wallet,
|
|
|
|
reservationID: id,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-14 22:52:07 +03:00
|
|
|
// OurFunds...
|
|
|
|
func (r *ChannelReservation) OurFunds() ([]*wire.TxIn, []*wire.TxOut, *btcec.PublicKey) {
|
|
|
|
r.RLock()
|
2015-11-29 03:20:57 +03:00
|
|
|
defer r.RUnlock()
|
2015-11-14 22:52:07 +03:00
|
|
|
return r.ourInputs, r.ourChange, r.ourKey.PubKey()
|
|
|
|
}
|
|
|
|
|
2015-11-13 05:43:32 +03:00
|
|
|
// AddCounterPartyFunds...
|
|
|
|
func (r *ChannelReservation) AddFunds(theirInputs []*wire.TxIn, theirChangeOutputs []*wire.TxOut, multiSigKey *btcec.PublicKey) error {
|
|
|
|
errChan := make(chan error, 1)
|
|
|
|
|
|
|
|
r.wallet.msgChan <- &addCounterPartyFundsMsg{
|
|
|
|
pendingFundingID: r.reservationID,
|
|
|
|
theirInputs: theirInputs,
|
|
|
|
theirChangeOutputs: theirChangeOutputs,
|
|
|
|
theirKey: multiSigKey,
|
2015-11-29 23:19:13 +03:00
|
|
|
err: errChan,
|
2015-11-13 05:43:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return <-errChan
|
|
|
|
}
|
|
|
|
|
2015-11-14 22:52:07 +03:00
|
|
|
// OurSigs...
|
|
|
|
func (r *ChannelReservation) OurSigs() [][]byte {
|
|
|
|
r.RLock()
|
2015-11-29 03:20:57 +03:00
|
|
|
defer r.RUnlock()
|
2015-11-14 22:52:07 +03:00
|
|
|
return r.ourSigs
|
|
|
|
}
|
|
|
|
|
|
|
|
// TheirFunds...
|
2015-11-29 03:20:57 +03:00
|
|
|
// TODO(roasbeef): return error if accessors not yet populated?
|
2015-11-14 22:52:07 +03:00
|
|
|
func (r *ChannelReservation) TheirFunds() ([]*wire.TxIn, []*wire.TxOut, *btcec.PublicKey) {
|
|
|
|
r.RLock()
|
2015-11-29 03:20:57 +03:00
|
|
|
defer r.RUnlock()
|
2015-11-14 22:52:07 +03:00
|
|
|
return r.theirInputs, r.theirChange, r.theirKey
|
|
|
|
}
|
|
|
|
|
2015-11-13 05:43:32 +03:00
|
|
|
// CompleteFundingReservation...
|
2015-11-27 09:52:14 +03:00
|
|
|
func (r *ChannelReservation) CompleteReservation(theirSigs [][]byte) error {
|
2015-11-13 05:43:32 +03:00
|
|
|
errChan := make(chan error, 1)
|
|
|
|
|
|
|
|
r.wallet.msgChan <- &addCounterPartySigsMsg{
|
2015-11-27 09:52:14 +03:00
|
|
|
pendingFundingID: r.reservationID,
|
2015-11-13 05:43:32 +03:00
|
|
|
theirSigs: theirSigs,
|
|
|
|
err: errChan,
|
|
|
|
}
|
|
|
|
|
|
|
|
return <-errChan
|
|
|
|
}
|
|
|
|
|
2015-11-14 22:52:07 +03:00
|
|
|
// FinalFundingTransaction...
|
|
|
|
func (r *ChannelReservation) FinalFundingTx() *btcutil.Tx {
|
|
|
|
r.RLock()
|
2015-11-29 03:20:57 +03:00
|
|
|
defer r.RUnlock()
|
2015-11-14 22:52:07 +03:00
|
|
|
return r.completedFundingTx
|
|
|
|
}
|
|
|
|
|
2015-11-13 05:43:32 +03:00
|
|
|
// RequestFundingReserveCancellation...
|
2015-11-14 22:52:07 +03:00
|
|
|
// TODO(roasbeef): also return mutated state?
|
2015-12-16 23:40:44 +03:00
|
|
|
func (r *ChannelReservation) Cancel() error {
|
|
|
|
errChan := make(chan error, 1)
|
2015-11-13 05:43:32 +03:00
|
|
|
r.wallet.msgChan <- &fundingReserveCancelMsg{
|
2015-11-27 09:52:14 +03:00
|
|
|
pendingFundingID: r.reservationID,
|
2015-12-16 23:40:44 +03:00
|
|
|
err: errChan,
|
2015-11-13 05:43:32 +03:00
|
|
|
}
|
|
|
|
|
2015-12-16 23:40:44 +03:00
|
|
|
return <-errChan
|
2015-11-13 05:43:32 +03:00
|
|
|
}
|
2015-12-03 03:49:41 +03:00
|
|
|
|
|
|
|
// WaitForChannelOpen...
|
|
|
|
func (r *ChannelReservation) WaitForChannelOpen() *LightningChannel {
|
|
|
|
return nil
|
|
|
|
}
|
2015-12-16 00:22:33 +03:00
|
|
|
|
|
|
|
// * finish reset of tests
|
|
|
|
// * comment out stuff that'll need a node.
|
|
|
|
// * start on commitment side
|
|
|
|
// * implement rusty's shachain
|
|
|
|
// * set up logic to get notification from node when funding tx gets 6 deep.
|
|
|
|
// * prob spawn into ChainNotifier struct
|
|
|
|
// * create builder for initial funding transaction
|
|
|
|
// * fascade through the wallet, for signing and such.
|
|
|
|
// * channel should have active namespace to it's bucket, query at that point fo past commits etc
|