From e17ba7859ea4687e3d4b0ad0b0c4fc3a771fdddd Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Sat, 14 Nov 2015 11:52:07 -0800 Subject: [PATCH] add concurrent-safe interface to ChannelReservation state --- wallet/reservation.go | 55 ++++++++++++++++++++++------ wallet/wallet.go | 85 +++++++++++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 43 deletions(-) diff --git a/wallet/reservation.go b/wallet/reservation.go index e812b8be..88d6b6c2 100644 --- a/wallet/reservation.go +++ b/wallet/reservation.go @@ -1,6 +1,8 @@ package wallet import ( + "sync" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" @@ -14,27 +16,29 @@ type ChannelReservation struct { ReserveAmount btcutil.Amount MinFeePerKb btcutil.Amount - TheirInputs []*wire.TxIn - OurInputs []*wire.TxIn + sync.RWMutex // All fields below owned by the wallet. - TheirChange []*wire.TxOut - OurChange []*wire.TxOut + theirInputs []*wire.TxIn + ourInputs []*wire.TxIn - OurKey *btcec.PrivateKey - TheirKey *btcec.PublicKey + theirChange []*wire.TxOut + ourChange []*wire.TxOut + + ourKey *btcec.PrivateKey + theirKey *btcec.PublicKey // In order of sorted inputs. Sorting is done in accordance // to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki. - TheirSigs [][]byte - OurSigs [][]byte + theirSigs [][]byte + ourSigs [][]byte - NormalizedTxID wire.ShaHash + normalizedTxID wire.ShaHash - FundingTx *wire.MsgTx + fundingTx *wire.MsgTx // TODO(roasbef): time locks, who pays fee etc. // TODO(roasbeef): record Bob's ln-ID? - CompletedFundingTx *btcutil.Tx + completedFundingTx *btcutil.Tx reservationID uint64 wallet *LightningWallet @@ -52,6 +56,13 @@ func newChannelReservation(t FundingType, fundingAmt btcutil.Amount, } } +// OurFunds... +func (r *ChannelReservation) OurFunds() ([]*wire.TxIn, []*wire.TxOut, *btcec.PublicKey) { + r.RLock() + defer r.Unlock() + return r.ourInputs, r.ourChange, r.ourKey.PubKey() +} + // AddCounterPartyFunds... func (r *ChannelReservation) AddFunds(theirInputs []*wire.TxIn, theirChangeOutputs []*wire.TxOut, multiSigKey *btcec.PublicKey) error { errChan := make(chan error, 1) @@ -66,6 +77,20 @@ func (r *ChannelReservation) AddFunds(theirInputs []*wire.TxIn, theirChangeOutpu return <-errChan } +// OurSigs... +func (r *ChannelReservation) OurSigs() [][]byte { + r.RLock() + defer r.Unlock() + return r.ourSigs +} + +// TheirFunds... +func (r *ChannelReservation) TheirFunds() ([]*wire.TxIn, []*wire.TxOut, *btcec.PublicKey) { + r.RLock() + defer r.Unlock() + return r.theirInputs, r.theirChange, r.theirKey +} + // CompleteFundingReservation... func (r *ChannelReservation) CompleteReservation(reservationID uint64, theirSigs [][]byte) error { errChan := make(chan error, 1) @@ -79,7 +104,15 @@ func (r *ChannelReservation) CompleteReservation(reservationID uint64, theirSigs return <-errChan } +// FinalFundingTransaction... +func (r *ChannelReservation) FinalFundingTx() *btcutil.Tx { + r.RLock() + defer r.Unlock() + return r.completedFundingTx +} + // RequestFundingReserveCancellation... +// TODO(roasbeef): also return mutated state? func (r *ChannelReservation) Cancel(reservationID uint64) { doneChan := make(chan struct{}, 1) r.wallet.msgChan <- &fundingReserveCancelMsg{ diff --git a/wallet/wallet.go b/wallet/wallet.go index f8a2ab46..f5d888e2 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -155,7 +155,7 @@ type LightningWallet struct { // NewLightningWallet... // TODO(roasbeef): fin... -func NewLightningWallet(db walletdb.DB, walletPass []byte) (*LightningWallet, error) { +func NewLightningWallet(db walletdb.DB, walletPass []byte, create bool) (*LightningWallet, error) { lnNamespace, err := db.Namespace(lightningNamespaceKey) if err != nil { return nil, err @@ -171,6 +171,9 @@ func NewLightningWallet(db walletdb.DB, walletPass []byte) (*LightningWallet, er return nil, err } + if create { + } + // TODO(roasbeef): create vs open wallet, use tadge's seed format read func. cbs := &waddrmgr.OpenCallbacks{ ObtainSeed: func() ([]byte, error) { @@ -274,6 +277,10 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg l.limboMtx.Unlock() + // Grab the mutex on the ChannelReservation to ensure thead-safety + partialState.Lock() + defer partialState.Unlock() + // Find all unlocked unspent outputs with greater than 6 confirmations. maxConfs := ^int32(0) unspentOutputs, err := l.ListUnspent(6, maxConfs, nil) @@ -305,7 +312,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg // Lock the selected coins. These coins are now "reserved", this // prevents concurrent funding requests from referring to and this // double-spending the same set of coins. - partialState.OurInputs = make([]*wire.TxIn, len(selectedCoins.Coins())) + partialState.ourInputs = make([]*wire.TxIn, len(selectedCoins.Coins())) for i, coin := range selectedCoins.Coins() { txout := wire.NewOutPoint(coin.Hash(), coin.Index()) l.LockOutpoint(*txout) @@ -313,12 +320,12 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg // Empty sig script, we'll actually sign if this reservation is // queued up to be completed (the other side accepts). outPoint := wire.NewOutPoint(coin.Hash(), coin.Index()) - partialState.OurInputs[i] = wire.NewTxIn(outPoint, nil) + partialState.ourInputs[i] = wire.NewTxIn(outPoint, nil) } // Create some possibly neccessary change outputs. selectedTotalValue := coinset.NewCoinSet(coins).TotalValue() - partialState.OurChange = make([]*wire.TxOut, 0, len(selectedCoins.Coins())) + partialState.ourChange = make([]*wire.TxOut, 0, len(selectedCoins.Coins())) if selectedTotalValue > req.fundingAmount { // Change is necessary. Query for an available change address to // send the remainder to. @@ -329,7 +336,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg return } - partialState.OurChange = append(partialState.OurChange, + partialState.ourChange = append(partialState.ourChange, wire.NewTxOut(int64(changeAmount), changeAddr.ScriptAddress())) } @@ -341,7 +348,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg return } - partialState.OurKey = multiSigKey + partialState.ourKey = multiSigKey // Funding reservation request succesfully handled. The funding inputs // will be marked as unavailable until the reservation is either @@ -363,9 +370,13 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs return } + // Grab the mutex on the ChannelReservation to ensure thead-safety + pendingReservation.Lock() + defer pendingReservation.Unlock() + // Mark all previously locked outpoints as usuable for future funding // requests. - for _, unusedInput := range pendingReservation.OurInputs { + for _, unusedInput := range pendingReservation.ourInputs { l.UnlockOutpoint(unusedInput.PreviousOutPoint) } @@ -390,38 +401,42 @@ func (l *LightningWallet) handleFundingCounterPartyFunds(req *addCounterPartyFun return } + // Grab the mutex on the ChannelReservation to ensure thead-safety + pendingReservation.Lock() + defer pendingReservation.Unlock() + // Create a blank, fresh transaction. Soon to be a complete funding // transaction which will allow opening a lightning channel. - pendingReservation.FundingTx = wire.NewMsgTx() + pendingReservation.fundingTx = wire.NewMsgTx() // First, add all multi-party inputs to the transaction // TODO(roasbeef); handle case that tx doesn't exist, fake input // TODO(roasbeef): validate SPV proof from other side if in SPV mode. - for _, ourInput := range pendingReservation.OurInputs { - pendingReservation.FundingTx.AddTxIn(ourInput) + for _, ourInput := range pendingReservation.ourInputs { + pendingReservation.fundingTx.AddTxIn(ourInput) } - pendingReservation.TheirInputs = req.theirInputs - for _, theirInput := range pendingReservation.TheirInputs { - pendingReservation.FundingTx.AddTxIn(theirInput) + pendingReservation.theirInputs = req.theirInputs + for _, theirInput := range pendingReservation.theirInputs { + pendingReservation.fundingTx.AddTxIn(theirInput) } // Next, add all multi-party outputs to the transaction. This includes // change outputs for both side. - for _, ourChangeOutput := range pendingReservation.OurChange { - pendingReservation.FundingTx.AddTxOut(ourChangeOutput) + for _, ourChangeOutput := range pendingReservation.ourChange { + pendingReservation.fundingTx.AddTxOut(ourChangeOutput) } - pendingReservation.TheirChange = req.theirChangeOutputs - for _, theirChangeOutput := range pendingReservation.TheirChange { - pendingReservation.FundingTx.AddTxOut(theirChangeOutput) + pendingReservation.theirChange = req.theirChangeOutputs + for _, theirChangeOutput := range pendingReservation.theirChange { + pendingReservation.fundingTx.AddTxOut(theirChangeOutput) } // Finally, add the 2-of-2 multi-sig output which will set up the lightning // channel. TODO(roasbeef): Cannonical sorting of keys here? keys := make([]*btcutil.AddressPubKey, 2) - ourKey := pendingReservation.OurKey.PubKey().SerializeCompressed() + ourKey := pendingReservation.ourKey.PubKey().SerializeCompressed() keys[0], _ = btcutil.NewAddressPubKey(ourKey, ActiveNetParams) - pendingReservation.TheirKey = req.theirKey - keys[1], _ = btcutil.NewAddressPubKey(pendingReservation.TheirKey.SerializeCompressed(), ActiveNetParams) + pendingReservation.theirKey = req.theirKey + keys[1], _ = btcutil.NewAddressPubKey(pendingReservation.theirKey.SerializeCompressed(), ActiveNetParams) multiSigScript, err := txscript.MultiSigScript(keys, 2) if err != nil { req.err <- err @@ -429,22 +444,22 @@ func (l *LightningWallet) handleFundingCounterPartyFunds(req *addCounterPartyFun } multiSigOut := wire.NewTxOut(int64(pendingReservation.FundingAmount), multiSigScript) - pendingReservation.FundingTx.AddTxOut(multiSigOut) + pendingReservation.fundingTx.AddTxOut(multiSigOut) // Sort the transaction. Since both side agree to a cannonical // ordering, by sorting we no longer need to send the entire // transaction. Only signatures will be exchanged. - txsort.InPlaceSort(pendingReservation.FundingTx) + txsort.InPlaceSort(pendingReservation.fundingTx) // Now that the transaction has been cannonically sorted, compute the // normalized transation ID before we attach our signatures. // TODO(roasbeef): this isn't the normalized txid, this isn't recursive - pendingReservation.NormalizedTxID = pendingReservation.FundingTx.TxSha() + pendingReservation.normalizedTxID = pendingReservation.fundingTx.TxSha() // Now, sign all inputs that are ours, collecting the signatures in // order of the inputs. - pendingReservation.OurSigs = make([][]byte, len(pendingReservation.OurInputs)) - for i, txIn := range pendingReservation.FundingTx.TxIn { + pendingReservation.ourSigs = make([][]byte, len(pendingReservation.ourInputs)) + for i, txIn := range pendingReservation.fundingTx.TxIn { // Does the wallet know about the txin? txDetail, _ := l.TxStore.TxDetails(&txIn.PreviousOutPoint.Hash) if txDetail == nil { @@ -473,7 +488,7 @@ func (l *LightningWallet) handleFundingCounterPartyFunds(req *addCounterPartyFun return } - sigscript, err := txscript.SignatureScript(pendingReservation.FundingTx, i, + sigscript, err := txscript.SignatureScript(pendingReservation.fundingTx, i, prevOut.PkScript, txscript.SigHashAll, privkey, ai.Compressed()) if err != nil { @@ -481,8 +496,8 @@ func (l *LightningWallet) handleFundingCounterPartyFunds(req *addCounterPartyFun return } - pendingReservation.FundingTx.TxIn[i].SignatureScript = sigscript - pendingReservation.OurSigs[i] = sigscript + pendingReservation.fundingTx.TxIn[i].SignatureScript = sigscript + pendingReservation.ourSigs[i] = sigscript } req.err <- nil @@ -498,15 +513,19 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs return } + // Grab the mutex on the ChannelReservation to ensure thead-safety + pendingReservation.Lock() + defer pendingReservation.Unlock() + // Now we can complete the funding transaction by adding their // signatures to their inputs. i := 0 - pendingReservation.TheirSigs = msg.theirSigs - for _, txin := range pendingReservation.FundingTx.TxIn { + pendingReservation.theirSigs = msg.theirSigs + for _, txin := range pendingReservation.fundingTx.TxIn { if txin.SignatureScript == nil { // TODO(roasbeef): use txscript.Engine to make sure each sig is // valid, txn complete. - txin.SignatureScript = pendingReservation.TheirSigs[i] + txin.SignatureScript = pendingReservation.theirSigs[i] i++ } } @@ -519,6 +538,8 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // CompletedFundingTx: btcutil.NewTx(pendingReservation.fundingTx), //} + pendingReservation.completedFundingTx = btcutil.NewTx(pendingReservation.fundingTx) + l.limboMtx.Lock() delete(l.fundingLimbo, pendingReservation.reservationID) l.limboMtx.Unlock()