func testFundingTransactionLockedOutputs(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running funding txn locked outputs test") // Create a single channel asking for 16 BTC total. fundingAmount := btcutil.Amount(8 * 1e8) _, err := wallet.InitChannelReservation(fundingAmount, fundingAmount, testPub, bobAddr, numReqConfs, 4) if err != nil { t.Fatalf("unable to initialize funding reservation 1: %v", err) } // Now attempt to reserve funds for another channel, this time // requesting 900 BTC. We only have around 64BTC worth of outpoints // that aren't locked, so this should fail. amt := btcutil.Amount(900 * 1e8) failedReservation, err := wallet.InitChannelReservation(amt, amt, testPub, bobAddr, numReqConfs, 4) if err == nil { t.Fatalf("not error returned, should fail on coin selection") } if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { t.Fatalf("error not coinselect error: %v", err) } if failedReservation != nil { t.Fatalf("reservation should be nil") } }
func TestOutputSplittingOversizeTx(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() requestAmount := btcutil.Amount(5) bigInput := int64(3) smallInput := int64(2) request := TstNewOutputRequest( t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", requestAmount, pool.Manager().ChainParams()) seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{smallInput, bigInput}) changeStart := TstNewChangeAddress(t, pool, seriesID, 0) w := newWithdrawal(0, []OutputRequest{request}, eligible, *changeStart) w.txOptions = func(tx *withdrawalTx) { tx.calculateFee = TstConstantFee(0) tx.calculateSize = func() int { // Trigger an output split right after the second input is added. if len(tx.inputs) == 2 { return txMaxSize + 1 } return txMaxSize - 1 } } if err := w.fulfillRequests(); err != nil { t.Fatal(err) } if len(w.transactions) != 2 { t.Fatalf("Wrong number of finalized transactions; got %d, want 2", len(w.transactions)) } tx1 := w.transactions[0] if len(tx1.outputs) != 1 { t.Fatalf("Wrong number of outputs on tx1; got %d, want 1", len(tx1.outputs)) } if tx1.outputs[0].amount != btcutil.Amount(bigInput) { t.Fatalf("Wrong amount for output in tx1; got %d, want %d", tx1.outputs[0].amount, bigInput) } tx2 := w.transactions[1] if len(tx2.outputs) != 1 { t.Fatalf("Wrong number of outputs on tx2; got %d, want 1", len(tx2.outputs)) } if tx2.outputs[0].amount != btcutil.Amount(smallInput) { t.Fatalf("Wrong amount for output in tx2; got %d, want %d", tx2.outputs[0].amount, smallInput) } if len(w.status.outputs) != 1 { t.Fatalf("Wrong number of output statuses; got %d, want 1", len(w.status.outputs)) } status := w.status.outputs[request.outBailmentID()].status if status != statusSplit { t.Fatalf("Wrong output status; got '%s', want '%s'", status, statusSplit) } }
func createTestChannelState(cdb *DB) (*OpenChannel, error) { addr, err := btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), netParams) if err != nil { return nil, err } script, err := txscript.MultiSigScript([]*btcutil.AddressPubKey{addr, addr}, 2) if err != nil { return nil, err } // Simulate 1000 channel updates via progression of the elkrem // revocation trees. sender := elkrem.NewElkremSender(key) receiver := &elkrem.ElkremReceiver{} for i := 0; i < 1000; i++ { preImage, err := sender.AtIndex(uint64(i)) if err != nil { return nil, err } if receiver.AddNext(preImage); err != nil { return nil, err } } return &OpenChannel{ IdentityPub: pubKey, ChanID: id, MinFeePerKb: btcutil.Amount(5000), OurCommitKey: privKey.PubKey(), TheirCommitKey: pubKey, Capacity: btcutil.Amount(10000), OurBalance: btcutil.Amount(3000), TheirBalance: btcutil.Amount(9000), OurCommitTx: testTx, OurCommitSig: bytes.Repeat([]byte{1}, 71), LocalElkrem: sender, RemoteElkrem: receiver, FundingOutpoint: testOutpoint, OurMultiSigKey: privKey.PubKey(), TheirMultiSigKey: privKey.PubKey(), FundingWitnessScript: script, TheirCurrentRevocation: privKey.PubKey(), TheirCurrentRevocationHash: key, OurDeliveryScript: script, TheirDeliveryScript: script, LocalCsvDelay: 5, RemoteCsvDelay: 9, NumUpdates: 0, TotalSatoshisSent: 8, TotalSatoshisReceived: 2, TotalNetFees: 9, CreationTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), Db: cdb, }, nil }
// OpenChannel attempts to open a singly funded channel specified in the // request to a remote peer. func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest, updateStream lnrpc.Lightning_OpenChannelServer) error { rpcsLog.Tracef("[openchannel] request to peerid(%v) "+ "allocation(us=%v, them=%v) numconfs=%v", in.TargetPeerId, in.LocalFundingAmount, in.RemoteFundingAmount, in.NumConfs) localFundingAmt := btcutil.Amount(in.LocalFundingAmount) remoteFundingAmt := btcutil.Amount(in.RemoteFundingAmount) nodepubKey, err := btcec.ParsePubKey(in.NodePubkey, btcec.S256()) if err != nil { return err } updateChan, errChan := r.server.OpenChannel(in.TargetPeerId, nodepubKey, localFundingAmt, remoteFundingAmt, in.NumConfs) var outpoint wire.OutPoint out: for { select { case err := <-errChan: rpcsLog.Errorf("unable to open channel to "+ "identityPub(%x) nor peerID(%v): %v", nodepubKey, in.TargetPeerId, err) return err case fundingUpdate := <-updateChan: rpcsLog.Tracef("[openchannel] sending update: %v", fundingUpdate) if err := updateStream.Send(fundingUpdate); err != nil { return err } // If a final channel open update is being sent, then // we can break out of our recv loop as we no longer // need to process any further updates. switch update := fundingUpdate.Update.(type) { case *lnrpc.OpenStatusUpdate_ChanOpen: chanPoint := update.ChanOpen.ChannelPoint h, _ := wire.NewShaHash(chanPoint.FundingTxid) outpoint = wire.OutPoint{ Hash: *h, Index: chanPoint.OutputIndex, } break out } case <-r.quit: return nil } } rpcsLog.Tracef("[openchannel] success peerid(%v), ChannelPoint(%v)", in.TargetPeerId, outpoint) return nil }
func TestWithdrawalTxInputTotal(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() tx := createWithdrawalTx(t, pool, []int64{5}, []int64{}) if tx.inputTotal() != btcutil.Amount(5) { t.Fatalf("Wrong total output; got %v, want %v", tx.outputTotal(), btcutil.Amount(5)) } }
func TestStoreTransactionsWithChangeOutput(t *testing.T) { tearDown, pool, store := TstCreatePoolAndTxStore(t) defer tearDown() wtx := createWithdrawalTxWithStoreCredits(t, store, pool, []int64{5e6}, []int64{1e6, 1e6}) wtx.changeOutput = wire.NewTxOut(int64(3e6), []byte{}) msgtx := wtx.toMsgTx() tx := &changeAwareTx{MsgTx: msgtx, changeIdx: int32(len(msgtx.TxOut) - 1)} if err := storeTransactions(store, []*changeAwareTx{tx}); err != nil { t.Fatal(err) } sha := msgtx.TxSha() txDetails, err := store.TxDetails(&sha) if err != nil { t.Fatal(err) } if txDetails == nil { t.Fatal("The new tx doesn't seem to have been stored") } storedTx := txDetails.TxRecord.MsgTx outputTotal := int64(0) for i, txOut := range storedTx.TxOut { if int32(i) != tx.changeIdx { outputTotal += txOut.Value } } if outputTotal != int64(2e6) { t.Fatalf("Unexpected output amount; got %v, want %v", outputTotal, int64(2e6)) } inputTotal := btcutil.Amount(0) for _, debit := range txDetails.Debits { inputTotal += debit.Amount } if inputTotal != btcutil.Amount(5e6) { t.Fatalf("Unexpected input amount; got %v, want %v", inputTotal, btcutil.Amount(5e6)) } credits, err := store.UnspentOutputs() if err != nil { t.Fatal(err) } if len(credits) != 1 { t.Fatalf("Unexpected number of credits in txstore; got %d, want 1", len(credits)) } changeOutpoint := wire.OutPoint{Hash: sha, Index: uint32(tx.changeIdx)} if credits[0].OutPoint != changeOutpoint { t.Fatalf("Credit's outpoint (%v) doesn't match the one from change output (%v)", credits[0].OutPoint, changeOutpoint) } }
func TestWithdrawalTxOutputTotal(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() tx := createWithdrawalTx(t, pool, []int64{}, []int64{4}) tx.changeOutput = wire.NewTxOut(int64(1), []byte{}) if tx.outputTotal() != btcutil.Amount(4) { t.Fatalf("Wrong total output; got %v, want %v", tx.outputTotal(), btcutil.Amount(4)) } }
// TestRollbackLastOutputWhenNewOutputAdded checks that we roll back the last // output if a tx becomes too big right after we add a new output to it. func TestRollbackLastOutputWhenNewOutputAdded(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() net := pool.Manager().ChainParams() series, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{5, 5}) requests := []OutputRequest{ // This is ordered by bailment ID TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", 1, net), TstNewOutputRequest(t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", 2, net), } changeStart := TstNewChangeAddress(t, pool, series, 0) w := newWithdrawal(0, requests, eligible, *changeStart) w.txOptions = func(tx *withdrawalTx) { tx.calculateFee = TstConstantFee(0) tx.calculateSize = func() int { // Trigger an output split right after the second output is added. if len(tx.outputs) > 1 { return txMaxSize + 1 } return txMaxSize - 1 } } if err := w.fulfillRequests(); err != nil { t.Fatal("Unexpected error:", err) } // At this point we should have two finalized transactions. if len(w.transactions) != 2 { t.Fatalf("Wrong number of finalized transactions; got %d, want 2", len(w.transactions)) } // First tx should have one output with 1 and one change output with 4 // satoshis. firstTx := w.transactions[0] req1 := requests[0] checkTxOutputs(t, firstTx, []*withdrawalTxOut{&withdrawalTxOut{request: req1, amount: req1.Amount}}) checkTxChangeAmount(t, firstTx, btcutil.Amount(4)) // Second tx should have one output with 2 and one changeoutput with 3 satoshis. secondTx := w.transactions[1] req2 := requests[1] checkTxOutputs(t, secondTx, []*withdrawalTxOut{&withdrawalTxOut{request: req2, amount: req2.Amount}}) checkTxChangeAmount(t, secondTx, btcutil.Amount(3)) }
// testChannelBalance creates a new channel between Alice and Bob, then // checks channel balance to be equal amount specified while creation of channel. func testChannelBalance(net *networkHarness, t *harnessTest) { timeout := time.Duration(time.Second * 5) // Open a channel with 0.5 BTC between Alice and Bob, ensuring the // channel has been opened properly. amount := btcutil.Amount(btcutil.SatoshiPerBitcoin / 2) ctx, _ := context.WithTimeout(context.Background(), timeout) // Creates a helper closure to be used below which asserts the proper // response to a channel balance RPC. checkChannelBalance := func(node lnrpc.LightningClient, amount btcutil.Amount) { response, err := node.ChannelBalance(ctx, &lnrpc.ChannelBalanceRequest{}) if err != nil { t.Fatalf("unable to get channel balance: %v", err) } balance := btcutil.Amount(response.Balance) if balance != amount { t.Fatalf("channel balance wrong: %v != %v", balance, amount) } } chanPoint := openChannelAndAssert(t, net, ctx, net.Alice, net.Bob, amount) // As this is a single funder channel, Alice's balance should be // exactly 0.5 BTC since now state transitions have taken place yet. checkChannelBalance(net.Alice, amount) // Since we only explicitly wait for Alice's channel open notification, // Bob might not yet have updated his internal state in response to // Alice's channel open proof. So we sleep here for a second to let Bob // catch up. // TODO(roasbeef): Bob should also watch for the channel on-chain after // the changes to restrict the number of pending channels are in. time.Sleep(time.Second) // Ensure Bob currently has no available balance within the channel. checkChannelBalance(net.Bob, 0) // Finally close the channel between Alice and Bob, asserting that the // channel has been properly closed on-chain. ctx, _ = context.WithTimeout(context.Background(), timeout) closeChannelAndAssert(t, net, ctx, net.Alice, chanPoint) }
// Check that some requested outputs are not fulfilled when we don't have credits for all // of them. func TestFulfillRequestsNotEnoughCreditsForAllRequests(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() net := pool.Manager().ChainParams() // Create eligible inputs and the list of outputs we need to fulfil. seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{2e6, 4e6}) out1 := TstNewOutputRequest( t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", btcutil.Amount(3e6), net) out2 := TstNewOutputRequest( t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", btcutil.Amount(2e6), net) out3 := TstNewOutputRequest( t, 3, "3Qt1EaKRD9g9FeL2DGkLLswhK1AKmmXFSe", btcutil.Amount(5e6), net) outputs := []OutputRequest{out1, out2, out3} changeStart := TstNewChangeAddress(t, pool, seriesID, 0) w := newWithdrawal(0, outputs, eligible, *changeStart) if err := w.fulfillRequests(); err != nil { t.Fatal(err) } tx := w.transactions[0] // The created tx should spend both eligible credits, so we expect it to have // an input amount of 2e6+4e6 satoshis. inputAmount := eligible[0].Amount + eligible[1].Amount // We expect it to include outputs for requests 1 and 2, plus a change output, but // output request #3 should not be there because we don't have enough credits. change := inputAmount - (out1.Amount + out2.Amount + tx.calculateFee()) expectedOutputs := []OutputRequest{out1, out2} sort.Sort(byOutBailmentID(expectedOutputs)) expectedOutputs = append( expectedOutputs, TstNewOutputRequest(t, 4, changeStart.addr.String(), change, net)) msgtx := tx.toMsgTx() checkMsgTxOutputs(t, msgtx, expectedOutputs) // withdrawal.status should state that outputs 1 and 2 were successfully fulfilled, // and that output 3 was not. expectedStatuses := map[OutBailmentID]outputStatus{ out1.outBailmentID(): statusSuccess, out2.outBailmentID(): statusSuccess, out3.outBailmentID(): statusPartial} for _, wOutput := range w.status.outputs { if wOutput.status != expectedStatuses[wOutput.request.outBailmentID()] { t.Fatalf("Unexpected status for %v; got '%s', want '%s'", wOutput.request, wOutput.status, expectedStatuses[wOutput.request.outBailmentID()]) } } }
// newServer creates a new instance of the server which is to listen using the // passed listener address. func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, bio lnwallet.BlockChainIO, wallet *lnwallet.LightningWallet, chanDB *channeldb.DB) (*server, error) { privKey, err := wallet.GetIdentitykey() if err != nil { return nil, err } listeners := make([]net.Listener, len(listenAddrs)) for i, addr := range listenAddrs { listeners[i], err = brontide.NewListener(privKey, addr) if err != nil { return nil, err } } serializedPubKey := privKey.PubKey().SerializeCompressed() s := &server{ bio: bio, chainNotifier: notifier, chanDB: chanDB, fundingMgr: newFundingManager(wallet), invoices: newInvoiceRegistry(chanDB), lnwallet: wallet, identityPriv: privKey, // TODO(roasbeef): derive proper onion key based on rotation // schedule sphinx: sphinx.NewRouter(privKey, activeNetParams.Params), lightningID: fastsha256.Sum256(serializedPubKey), listeners: listeners, peers: make(map[int32]*peer), newPeers: make(chan *peer, 100), donePeers: make(chan *peer, 100), queries: make(chan interface{}), quit: make(chan struct{}), } // If the debug HTLC flag is on, then we invoice a "master debug" // invoice which all outgoing payments will be sent and all incoming // HTLC's with the debug R-Hash immediately settled. if cfg.DebugHTLC { kiloCoin := btcutil.Amount(btcutil.SatoshiPerBitcoin * 1000) s.invoices.AddDebugInvoice(kiloCoin, *debugPre) srvrLog.Debugf("Debug HTLC invoice inserted, preimage=%x, hash=%x", debugPre[:], debugHash[:]) } s.utxoNursery = newUtxoNursery(notifier, wallet) // Create a new routing manager with ourself as the sole node within // the graph. selfVertex := hex.EncodeToString(serializedPubKey) s.routingMgr = routing.NewRoutingManager(graph.NewID(selfVertex), nil) s.htlcSwitch = newHtlcSwitch(serializedPubKey, s.routingMgr) s.rpcServer = newRpcServer(s) return s, nil }
// Check that withdrawal.status correctly states that no outputs were fulfilled when we // don't have enough eligible credits for any of them. func TestFulfillRequestsNoSatisfiableOutputs(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{1e6}) request := TstNewOutputRequest( t, 1, "3Qt1EaKRD9g9FeL2DGkLLswhK1AKmmXFSe", btcutil.Amount(3e6), pool.Manager().ChainParams()) changeStart := TstNewChangeAddress(t, pool, seriesID, 0) w := newWithdrawal(0, []OutputRequest{request}, eligible, *changeStart) if err := w.fulfillRequests(); err != nil { t.Fatal(err) } if len(w.transactions) != 0 { t.Fatalf("Unexpected number of transactions; got %d, want 0", len(w.transactions)) } if len(w.status.outputs) != 1 { t.Fatalf("Unexpected number of outputs in WithdrawalStatus; got %d, want 1", len(w.status.outputs)) } status := w.status.outputs[request.outBailmentID()].status if status != statusPartial { t.Fatalf("Unexpected status for requested outputs; got '%s', want '%s'", status, statusPartial) } }
func compareMsgTxAndWithdrawalTxOutputs(t *testing.T, msgtx *wire.MsgTx, tx *withdrawalTx) { nOutputs := len(tx.outputs) if tx.changeOutput != nil { nOutputs++ } if len(msgtx.TxOut) != nOutputs { t.Fatalf("Unexpected number of TxOuts; got %d, want %d", len(msgtx.TxOut), nOutputs) } for i, output := range tx.outputs { outputRequest := output.request txOut := msgtx.TxOut[i] if !bytes.Equal(txOut.PkScript, outputRequest.PkScript) { t.Fatalf( "Unexpected pkScript for outputRequest %d; got %x, want %x", i, txOut.PkScript, outputRequest.PkScript) } gotAmount := btcutil.Amount(txOut.Value) if gotAmount != outputRequest.Amount { t.Fatalf( "Unexpected amount for outputRequest %d; got %v, want %v", i, gotAmount, outputRequest.Amount) } } // Finally check the change output if it exists if tx.changeOutput != nil { msgTxChange := msgtx.TxOut[len(msgtx.TxOut)-1] if msgTxChange != tx.changeOutput { t.Fatalf("wrong TxOut in msgtx; got %v, want %v", msgTxChange, tx.changeOutput) } } }
func fetchRawUnminedCreditAmount(v []byte) (btcutil.Amount, error) { if len(v) < 9 { str := "short unmined credit value" return 0, storeError(ErrData, str, nil) } return btcutil.Amount(byteOrder.Uint64(v)), nil }
func TestCommitSignatureEncodeDecode(t *testing.T) { commitSignature := &CommitSignature{ ChannelPoint: outpoint1, Fee: btcutil.Amount(10000), LogIndex: 5, CommitSig: commitSig, } // Next encode the CS message into an empty bytes buffer. var b bytes.Buffer if err := commitSignature.Encode(&b, 0); err != nil { t.Fatalf("unable to encode CommitSignature: %v", err) } // Deserialize the encoded EG message into a new empty struct. commitSignature2 := &CommitSignature{} if err := commitSignature2.Decode(&b, 0); err != nil { t.Fatalf("unable to decode CommitSignature: %v", err) } // Assert equality of the two instances. if !reflect.DeepEqual(commitSignature, commitSignature2) { t.Fatalf("encode/decode error messages don't match %#v vs %#v", commitSignature, commitSignature2) } }
func TestHarness(t *testing.T) { // We should have (numMatureOutputs * 50 BTC) of mature unspendable // outputs. expectedBalance := btcutil.Amount(numMatureOutputs * 50 * btcutil.SatoshiPerBitcoin) harnessBalance := mainHarness.ConfirmedBalance() if harnessBalance != expectedBalance { t.Fatalf("expected wallet balance of %v instead have %v", expectedBalance, harnessBalance) } // Current tip should be at a height of numMatureOutputs plus the // required number of blocks for coinbase maturity. nodeInfo, err := mainHarness.Node.GetInfo() if err != nil { t.Fatalf("unable to execute getinfo on node: %v", err) } expectedChainHeight := numMatureOutputs + uint32(mainHarness.ActiveNet.CoinbaseMaturity) if uint32(nodeInfo.Blocks) != expectedChainHeight { t.Errorf("Chain height is %v, should be %v", nodeInfo.Blocks, expectedChainHeight) } for _, testCase := range harnessTestCases { testCase(mainHarness, t) } testTearDownAll(t) }
func createAndFulfillWithdrawalRequests(t *testing.T, pool *Pool, roundID uint32) withdrawalInfo { params := pool.Manager().ChainParams() seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{2e6, 4e6}) requests := []OutputRequest{ TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", 3e6, params), TstNewOutputRequest(t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", 2e6, params), } changeStart := TstNewChangeAddress(t, pool, seriesID, 0) dustThreshold := btcutil.Amount(1e4) startAddr := TstNewWithdrawalAddress(t, pool, seriesID, 1, 0) lastSeriesID := seriesID w := newWithdrawal(roundID, requests, eligible, *changeStart) if err := w.fulfillRequests(); err != nil { t.Fatal(err) } return withdrawalInfo{ requests: requests, startAddress: *startAddr, changeStart: *changeStart, lastSeriesID: lastSeriesID, dustThreshold: dustThreshold, status: *w.status, } }
// TstCreateCreditsOnStore inserts a new credit in the given store for // every item in the amounts slice. func TstCreateCreditsOnStore(t *testing.T, s *wtxmgr.Store, pkScript []byte, amounts []int64) []wtxmgr.Credit { msgTx := createMsgTx(pkScript, amounts) meta := &wtxmgr.BlockMeta{ Block: wtxmgr.Block{Height: TstInputsBlock}, } rec, err := wtxmgr.NewTxRecordFromMsgTx(msgTx, time.Now()) if err != nil { t.Fatal(err) } if err := s.InsertTx(rec, meta); err != nil { t.Fatal("Failed to create inputs: ", err) } credits := make([]wtxmgr.Credit, len(msgTx.TxOut)) for i := range msgTx.TxOut { if err := s.AddCredit(rec, meta, uint32(i), false); err != nil { t.Fatal("Failed to create inputs: ", err) } credits[i] = wtxmgr.Credit{ OutPoint: wire.OutPoint{ Hash: rec.Hash, Index: uint32(i), }, BlockMeta: *meta, Amount: btcutil.Amount(msgTx.TxOut[i].Value), PkScript: msgTx.TxOut[i].PkScript, } } return credits }
// TstCreateSeriesCredits creates a new credit for every item in the amounts // slice, locked to the given series' address with branch==1 and index==0. func TstCreateSeriesCredits(t *testing.T, pool *Pool, seriesID uint32, amounts []int64) []credit { addr := TstNewWithdrawalAddress(t, pool, seriesID, Branch(1), Index(0)) pkScript, err := txscript.PayToAddrScript(addr.addr) if err != nil { t.Fatal(err) } msgTx := createMsgTx(pkScript, amounts) txSha := msgTx.TxSha() credits := make([]credit, len(amounts)) for i := range msgTx.TxOut { c := wtxmgr.Credit{ OutPoint: wire.OutPoint{ Hash: txSha, Index: uint32(i), }, BlockMeta: wtxmgr.BlockMeta{ Block: wtxmgr.Block{Height: TstInputsBlock}, }, Amount: btcutil.Amount(msgTx.TxOut[i].Value), PkScript: msgTx.TxOut[i].PkScript, } credits[i] = newCredit(c, *addr) } return credits }
func makeInputSource(unspents []*wire.TxOut) InputSource { // Return outputs in order. currentTotal := btcutil.Amount(0) currentInputs := make([]*wire.TxIn, 0, len(unspents)) f := func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, [][]byte, error) { for currentTotal < target && len(unspents) != 0 { u := unspents[0] unspents = unspents[1:] nextInput := wire.NewTxIn(&wire.OutPoint{}, nil) currentTotal += btcutil.Amount(u.Value) currentInputs = append(currentInputs, nextInput) } return currentTotal, currentInputs, make([][]byte, len(currentInputs)), nil } return InputSource(f) }
func TestCloseRequestEncodeDecode(t *testing.T) { cr := &CloseRequest{ ChannelPoint: outpoint1, RequesterCloseSig: commitSig, Fee: btcutil.Amount(10000), } // Next encode the CR message into an empty bytes buffer. var b bytes.Buffer if err := cr.Encode(&b, 0); err != nil { t.Fatalf("unable to encode CloseRequest: %v", err) } // Deserialize the encoded CR message into a new empty struct. cr2 := &CloseRequest{} if err := cr2.Decode(&b, 0); err != nil { t.Fatalf("unable to decode CloseRequest: %v", err) } // Assert equality of the two instances. if !reflect.DeepEqual(cr, cr2) { t.Fatalf("encode/decode error messages don't match %#v vs %#v", cr, cr2) } }
func deserializeHTLC(r io.Reader) (*HTLC, error) { h := &HTLC{} var scratch [8]byte if _, err := r.Read(scratch[:1]); err != nil { return nil, err } if scratch[0] == 1 { h.Incoming = true } else { h.Incoming = false } if _, err := r.Read(scratch[:]); err != nil { return nil, err } h.Amt = btcutil.Amount(byteOrder.Uint64(scratch[:])) if _, err := r.Read(h.RHash[:]); err != nil { return nil, err } if _, err := r.Read(scratch[:4]); err != nil { return nil, err } h.RefundTimeout = byteOrder.Uint32(scratch[:4]) if _, err := r.Read(scratch[:4]); err != nil { return nil, err } h.RevocationDelay = byteOrder.Uint32(scratch[:]) return h, nil }
func makeInputSource(eligible []wtxmgr.Credit) txauthor.InputSource { // Pick largest outputs first. This is only done for compatibility with // previous tx creation code, not because it's a good idea. sort.Sort(sort.Reverse(byAmount(eligible))) // Current inputs and their total value. These are closed over by the // returned input source and reused across multiple calls. currentTotal := btcutil.Amount(0) currentInputs := make([]*wire.TxIn, 0, len(eligible)) currentScripts := make([][]byte, 0, len(eligible)) currentInputValues := make([]btcutil.Amount, 0, len(eligible)) return func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, []btcutil.Amount, [][]byte, error) { for currentTotal < target && len(eligible) != 0 { nextCredit := &eligible[0] eligible = eligible[1:] nextInput := wire.NewTxIn(&nextCredit.OutPoint, nil, nil) currentTotal += nextCredit.Amount currentInputs = append(currentInputs, nextInput) currentScripts = append(currentScripts, nextCredit.PkScript) currentInputValues = append(currentInputValues, nextCredit.Amount) } return currentTotal, currentInputs, currentInputValues, currentScripts, nil } }
// evalOutputs evaluates each of the passed outputs, creating a new matching // utxo within the wallet if we're able to spend the output. func (m *memWallet) evalOutputs(outputs []*wire.TxOut, txHash *chainhash.Hash, isCoinbase bool, undo *undoEntry) { for i, output := range outputs { pkScript := output.PkScript // Scan all the addresses we currently control to see if the // output is paying to us. for keyIndex, addr := range m.addrs { pkHash := addr.ScriptAddress() if !bytes.Contains(pkScript, pkHash) { continue } // If this is a coinbase output, then we mark the // maturity height at the proper block height in the // future. var maturityHeight int32 if isCoinbase { maturityHeight = m.currentHeight + int32(m.net.CoinbaseMaturity) } op := wire.OutPoint{Hash: *txHash, Index: uint32(i)} m.utxos[op] = &utxo{ value: btcutil.Amount(output.Value), keyIndex: keyIndex, maturityHeight: maturityHeight, pkScript: pkScript, } undo.utxosCreated = append(undo.utxosCreated, op) } } }
// TestOutputSplittingNotEnoughInputs checks that an output will get split if we // don't have enough inputs to fulfil it. func TestOutputSplittingNotEnoughInputs(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() net := pool.Manager().ChainParams() output1Amount := btcutil.Amount(2) output2Amount := btcutil.Amount(3) requests := []OutputRequest{ // These output requests will have the same server ID, so we know // they'll be fulfilled in the order they're defined here, which is // important for this test. TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", output1Amount, net), TstNewOutputRequest(t, 2, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", output2Amount, net), } seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{7}) w := newWithdrawal(0, requests, eligible, *TstNewChangeAddress(t, pool, seriesID, 0)) w.txOptions = func(tx *withdrawalTx) { // Trigger an output split because of lack of inputs by forcing a high fee. // If we just started with not enough inputs for the requested outputs, // fulfillRequests() would drop outputs until we had enough. tx.calculateFee = TstConstantFee(3) } if err := w.fulfillRequests(); err != nil { t.Fatal(err) } if len(w.transactions) != 1 { t.Fatalf("Wrong number of finalized transactions; got %d, want 1", len(w.transactions)) } tx := w.transactions[0] if len(tx.outputs) != 2 { t.Fatalf("Wrong number of outputs; got %d, want 2", len(tx.outputs)) } // The first output should've been left untouched. if tx.outputs[0].amount != output1Amount { t.Fatalf("Wrong amount for first tx output; got %v, want %v", tx.outputs[0].amount, output1Amount) } // The last output should have had its amount updated to whatever we had // left after satisfying all previous outputs. newAmount := tx.inputTotal() - output1Amount - tx.calculateFee() checkLastOutputWasSplit(t, w, tx, output2Amount, newAmount) }
func TestFindingSpentCredits(t *testing.T) { t.Parallel() s, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } // Insert transaction and credit which will be spent. recvRec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { t.Fatal(err) } err = s.InsertTx(recvRec, TstRecvTxBlockDetails) if err != nil { t.Fatal(err) } err = s.AddCredit(recvRec, TstRecvTxBlockDetails, 0, false) if err != nil { t.Fatal(err) } // Insert confirmed transaction which spends the above credit. spendingRec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { t.Fatal(err) } err = s.InsertTx(spendingRec, TstSignedTxBlockDetails) if err != nil { t.Fatal(err) } err = s.AddCredit(spendingRec, TstSignedTxBlockDetails, 0, false) if err != nil { t.Fatal(err) } bal, err := s.Balance(1, TstSignedTxBlockDetails.Height) if err != nil { t.Fatal(err) } expectedBal := btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value) if bal != expectedBal { t.Fatalf("bad balance: %v != %v", bal, expectedBal) } unspents, err := s.UnspentOutputs() if err != nil { t.Fatal(err) } op := wire.NewOutPoint(TstSpendingTx.Sha(), 0) if unspents[0].OutPoint != *op { t.Fatal("unspent outpoint doesn't match expected") } if len(unspents) > 1 { t.Fatal("has more than one unspent credit") } }
// fetchRawCreditAmountChange returns the amount of the credit and whether the // credit is marked as change. func fetchRawCreditAmountChange(v []byte) (btcutil.Amount, bool, error) { if len(v) < 9 { str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)", bucketCredits, 9, len(v)) return 0, false, storeError(ErrData, str, nil) } return btcutil.Amount(byteOrder.Uint64(v)), v[8]&(1<<1) != 0, nil }
// addCredit is an AddCredit helper that runs in an update transaction. The // bool return specifies whether the unspent output is newly added (true) or a // duplicate (false). func (s *Store) addCredit(ns walletdb.Bucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) (bool, error) { if block == nil { k := canonicalOutPoint(&rec.Hash, index) if existsRawUnminedCredit(ns, k) != nil { return false, nil } v := valueUnminedCredit(btcutil.Amount(rec.MsgTx.TxOut[index].Value), change) return true, putRawUnminedCredit(ns, k, v) } k, v := existsCredit(ns, &rec.Hash, index, &block.Block) if v != nil { return false, nil } txOutAmt := btcutil.Amount(rec.MsgTx.TxOut[index].Value) log.Debugf("Marking transaction %v output %d (%v) spendable", rec.Hash, index, txOutAmt) cred := credit{ outPoint: wire.OutPoint{ Hash: rec.Hash, Index: index, }, block: block.Block, amount: txOutAmt, change: change, spentBy: indexedIncidence{index: ^uint32(0)}, } v = valueUnspentCredit(&cred) err := putRawCredit(ns, k, v) if err != nil { return false, err } minedBalance, err := fetchMinedBalance(ns) if err != nil { return false, err } err = putMinedBalance(ns, minedBalance+txOutAmt) if err != nil { return false, err } return true, putUnspent(ns, &cred.outPoint, &block.Block) }
// CreateTransaction returns a fully signed transaction paying to the specified // outputs while observing the desired fee rate. The passed fee rate should be // expressed in satoshis-per-byte. // // This function is safe for concurrent access. func (m *memWallet) CreateTransaction(outputs []*wire.TxOut, feeRate btcutil.Amount) (*wire.MsgTx, error) { m.Lock() defer m.Unlock() tx := wire.NewMsgTx(wire.TxVersion) // Tally up the total amount to be sent in order to perform coin // selection shortly below. var outputAmt btcutil.Amount for _, output := range outputs { outputAmt += btcutil.Amount(output.Value) tx.AddTxOut(output) } // Attempt to fund the transaction with spendable utxos. if err := m.fundTx(tx, outputAmt, feeRate); err != nil { return nil, err } // Populate all the selected inputs with valid sigScript for spending. // Along the way record all outputs being spent in order to avoid a // potential double spend. spentOutputs := make([]*utxo, 0, len(tx.TxIn)) for i, txIn := range tx.TxIn { outPoint := txIn.PreviousOutPoint utxo := m.utxos[outPoint] extendedKey, err := m.hdRoot.Child(utxo.keyIndex) if err != nil { return nil, err } privKey, err := extendedKey.ECPrivKey() if err != nil { return nil, err } sigScript, err := txscript.SignatureScript(tx, i, utxo.pkScript, txscript.SigHashAll, privKey, true) if err != nil { return nil, err } txIn.SignatureScript = sigScript spentOutputs = append(spentOutputs, utxo) } // As these outputs are now being spent by this newly created // transaction, mark the outputs are "locked". This action ensures // these outputs won't be double spent by any subsequent transactions. // These locked outputs can be freed via a call to UnlockOutputs. for _, utxo := range spentOutputs { utxo.isLocked = true } return tx, nil }
// maybeDropRequests will check the total amount we have in eligible inputs and drop // requested outputs (in descending amount order) if we don't have enough to // fulfill them all. For every dropped output request we update its entry in // w.status.outputs with the status string set to statusPartial. func (w *withdrawal) maybeDropRequests() { inputAmount := btcutil.Amount(0) for _, input := range w.eligibleInputs { inputAmount += input.Amount } outputAmount := btcutil.Amount(0) for _, request := range w.pendingRequests { outputAmount += request.Amount } sort.Sort(sort.Reverse(byAmount(w.pendingRequests))) for inputAmount < outputAmount { request := w.popRequest() log.Infof("Not fulfilling request to send %v to %v; not enough credits.", request.Amount, request.Address) outputAmount -= request.Amount w.status.outputs[request.outBailmentID()].status = statusPartial } }