// GetTx takes a txid and returns the transaction. If we have it. func (ts *TxStore) GetAllTxs() ([]*wire.MsgTx, error) { var rtxs []*wire.MsgTx err := ts.StateDB.View(func(btx *bolt.Tx) error { txns := btx.Bucket(BKTTxns) if txns == nil { return fmt.Errorf("no transactions in db") } return txns.ForEach(func(k, v []byte) error { tx := wire.NewMsgTx() buf := bytes.NewBuffer(v) err := tx.Deserialize(buf) if err != nil { return err } rtxs = append(rtxs, tx) return nil }) }) if err != nil { return nil, err } return rtxs, nil }
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy // based on the passed block height to the provided address. When the address // is nil, the coinbase transaction will instead be redeemable by anyone. // // See the comment for NewBlockTemplate for more information about why the nil // address handling is useful. func createCoinbaseTx(params *chaincfg.Params, coinbaseScript []byte, nextBlockHeight int32, addr btcutil.Address) (*btcutil.Tx, error) { // Create the script to pay to the provided payment address if one was // specified. Otherwise create a script that allows the coinbase to be // redeemable by anyone. var pkScript []byte if addr != nil { var err error pkScript, err = txscript.PayToAddrScript(addr) if err != nil { return nil, err } } else { var err error scriptBuilder := txscript.NewScriptBuilder() pkScript, err = scriptBuilder.AddOp(txscript.OP_TRUE).Script() if err != nil { return nil, err } } tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn(&wire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is // zero hash and max index. PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, wire.MaxPrevOutIndex), SignatureScript: coinbaseScript, Sequence: wire.MaxTxInSequenceNum, }) tx.AddTxOut(&wire.TxOut{ Value: blockchain.CalcBlockSubsidy(nextBlockHeight, params), PkScript: pkScript, }) return btcutil.NewTx(tx), nil }
// 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 }
// createSpendTx generates a basic spending transaction given the passed // signature, witness and public key scripts. func createSpendingTx(witness [][]byte, sigScript, pkScript []byte, outputValue int64) *wire.MsgTx { coinbaseTx := wire.NewMsgTx(wire.TxVersion) outPoint := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0)) txIn := wire.NewTxIn(outPoint, []byte{OP_0, OP_0}, nil) txOut := wire.NewTxOut(outputValue, pkScript) coinbaseTx.AddTxIn(txIn) coinbaseTx.AddTxOut(txOut) spendingTx := wire.NewMsgTx(wire.TxVersion) coinbaseTxSha := coinbaseTx.TxHash() outPoint = wire.NewOutPoint(&coinbaseTxSha, 0) txIn = wire.NewTxIn(outPoint, sigScript, witness) txOut = wire.NewTxOut(outputValue, nil) spendingTx.AddTxIn(txIn) spendingTx.AddTxOut(txOut) return spendingTx }
// toMsgTx generates a btcwire.MsgTx with this tx's inputs and outputs. func (tx *withdrawalTx) toMsgTx() *wire.MsgTx { msgtx := wire.NewMsgTx() for _, o := range tx.outputs { msgtx.AddTxOut(wire.NewTxOut(int64(o.amount), o.pkScript())) } if tx.hasChange() { msgtx.AddTxOut(tx.changeOutput) } for _, i := range tx.inputs { msgtx.AddTxIn(wire.NewTxIn(&i.OutPoint, []byte{})) } return msgtx }
// GetTx takes a txid and returns the transaction. If we have it. func (ts *TxStore) GetTx(txid *wire.ShaHash) (*wire.MsgTx, error) { rtx := wire.NewMsgTx() err := ts.StateDB.View(func(btx *bolt.Tx) error { txns := btx.Bucket(BKTTxns) if txns == nil { return fmt.Errorf("no transactions in db") } txbytes := txns.Get(txid.Bytes()) if txbytes == nil { return fmt.Errorf("tx %x not in db", txid.String()) } buf := bytes.NewBuffer(txbytes) return rtx.Deserialize(buf) }) if err != nil { return nil, err } return rtx, nil }
// extractBalanceDelta extracts the net balance delta from the PoV of the // wallet given a TransactionSummary. func extractBalanceDelta(txSummary base.TransactionSummary) (btcutil.Amount, error) { tx := wire.NewMsgTx() txReader := bytes.NewReader(txSummary.Transaction) if err := tx.Deserialize(txReader); err != nil { return -1, nil } // For each input we debit the wallet's outflow for this transaction, // and for each output we credit the wallet's inflow for this // transaction. var balanceDelta btcutil.Amount for _, input := range txSummary.MyInputs { balanceDelta -= input.PreviousAmount } for _, output := range txSummary.MyOutputs { balanceDelta += btcutil.Amount(tx.TxOut[output.Index].Value) } return balanceDelta, nil }
// spendCSVOutput spends an output previously created by the createCSVOutput // function. The sigScript is a trivial push of OP_TRUE followed by the // redeemScript to pass P2SH evaluation. func spendCSVOutput(redeemScript []byte, csvUTXO *wire.OutPoint, sequence uint32, targetOutput *wire.TxOut, txVersion int32) (*wire.MsgTx, error) { tx := wire.NewMsgTx(txVersion) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: *csvUTXO, Sequence: sequence, }) tx.AddTxOut(targetOutput) b := txscript.NewScriptBuilder(). AddOp(txscript.OP_TRUE). AddData(redeemScript) sigScript, err := b.Script() if err != nil { return nil, err } tx.TxIn[0].SignatureScript = sigScript return tx, nil }
func fetchChanCommitTxns(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer var err error if err = writeOutpoint(&bc, channel.ChanID); err != nil { return err } txnsKey := make([]byte, len(commitTxnsKey)+bc.Len()) copy(txnsKey[:3], commitTxnsKey) copy(txnsKey[3:], bc.Bytes()) txnBytes := bytes.NewReader(nodeChanBucket.Get(txnsKey)) channel.OurCommitTx = wire.NewMsgTx() if err = channel.OurCommitTx.Deserialize(txnBytes); err != nil { return err } channel.OurCommitSig, err = wire.ReadVarBytes(txnBytes, 0, 80, "") if err != nil { return err } scratch := make([]byte, 4) if _, err := txnBytes.Read(scratch); err != nil { return err } channel.LocalCsvDelay = byteOrder.Uint32(scratch) if _, err := txnBytes.Read(scratch); err != nil { return err } channel.RemoteCsvDelay = byteOrder.Uint32(scratch) return nil }
// createCoinbaseTx returns a coinbase transaction paying an appropriate // subsidy based on the passed block height to the provided address. func createCoinbaseTx(coinbaseScript []byte, nextBlockHeight int32, addr btcutil.Address, net *chaincfg.Params) (*btcutil.Tx, error) { // Create the script to pay to the provided payment address. pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, err } tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn(&wire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is // zero hash and max index. PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, wire.MaxPrevOutIndex), SignatureScript: coinbaseScript, Sequence: wire.MaxTxInSequenceNum, }) tx.AddTxOut(&wire.TxOut{ Value: blockchain.CalcBlockSubsidy(nextBlockHeight, net), PkScript: pkScript, }) return btcutil.NewTx(tx), nil }
// TestCommitmentSpendValidation test the spendability of both outputs within // the commitment transaction. // // The following spending cases are covered by this test: // * Alice's spend from the delayed output on her commitment transaction. // * Bob's spend from Alice's delayed output when she broadcasts a revoked // commitment transaction. // * Bob's spend from his unencumbered output within Alice's commitment // transaction. func TestCommitmentSpendValidation(t *testing.T) { // We generate a fake output, and the corresponding txin. This output // doesn't need to exist, as we'll only be validating spending from the // transaction that references this. fundingOut := &wire.OutPoint{ Hash: testHdSeed, Index: 50, } fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) // We also set up set some resources for the commitment transaction. // Each side currently has 1 BTC within the channel, with a total // channel capacity of 2BTC. aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) channelBalance := btcutil.Amount(1 * 10e8) csvTimeout := uint32(5) revocationPreimage := testHdSeed[:] revokePubKey := DeriveRevocationPubkey(bobKeyPub, revocationPreimage) aliceSelfOutputSigner := &mockSigner{aliceKeyPriv} // With all the test data set up, we create the commitment transaction. // We only focus on a single party's transactions, as the scripts are // identical with the roles reversed. // // This is Alice's commitment transaction, so she must wait a CSV delay // of 5 blocks before sweeping the output, while bob can spend // immediately with either the revocation key, or his regular key. commitmentTx, err := CreateCommitTx(fakeFundingTxIn, aliceKeyPub, bobKeyPub, revokePubKey, csvTimeout, channelBalance, channelBalance) if err != nil { t.Fatalf("unable to create commitment transaction: %v", nil) } delayOutput := commitmentTx.TxOut[0] regularOutput := commitmentTx.TxOut[1] // We're testing an uncooperative close, output sweep, so construct a // transaction which sweeps the funds to a random address. targetOutput, err := commitScriptUnencumbered(aliceKeyPub) if err != nil { t.Fatalf("unable to create target output: %v") } sweepTx := wire.NewMsgTx() sweepTx.Version = 2 sweepTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{commitmentTx.TxSha(), 0}, nil, nil)) sweepTx.AddTxOut(&wire.TxOut{ PkScript: targetOutput, Value: 0.5 * 10e8, }) // First, we'll test spending with Alice's key after the timeout. delayScript, err := commitScriptToSelf(csvTimeout, aliceKeyPub, revokePubKey) if err != nil { t.Fatalf("unable to generate alice delay script: %v") } sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, csvTimeout) signDesc := &SignDescriptor{ WitnessScript: delayScript, SigHashes: txscript.NewTxSigHashes(sweepTx), Output: &wire.TxOut{ Value: int64(channelBalance), }, HashType: txscript.SigHashAll, InputIndex: 0, } aliceWitnessSpend, err := CommitSpendTimeout(aliceSelfOutputSigner, signDesc, sweepTx) if err != nil { t.Fatalf("unable to generate delay commit spend witness :%v") } sweepTx.TxIn[0].Witness = aliceWitnessSpend vm, err := txscript.NewEngine(delayOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(channelBalance)) if err != nil { t.Fatalf("unable to create engine: %v", err) } if err := vm.Execute(); err != nil { t.Fatalf("spend from delay output is invalid: %v", err) } // Next, we'll test bob spending with the derived revocation key to // simulate the scenario when alice broadcasts this commitmen // transaction after it's been revoked. revokePrivKey := DeriveRevocationPrivKey(bobKeyPriv, revocationPreimage) bobWitnessSpend, err := commitSpendRevoke(delayScript, channelBalance, revokePrivKey, sweepTx) if err != nil { t.Fatalf("unable to generate revocation witness: %v", err) } sweepTx.TxIn[0].Witness = bobWitnessSpend vm, err = txscript.NewEngine(delayOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(channelBalance)) if err != nil { t.Fatalf("unable to create engine: %v", err) } if err := vm.Execute(); err != nil { t.Fatalf("revocation spend is invalid: %v", err) } // Finally, we test bob sweeping his output as normal in the case that // alice broadcasts this commitment transaction. bobScriptp2wkh, err := commitScriptUnencumbered(bobKeyPub) if err != nil { t.Fatalf("unable to create bob p2wkh script: %v", err) } bobRegularSpend, err := commitSpendNoDelay(bobScriptp2wkh, channelBalance, bobKeyPriv, sweepTx) if err != nil { t.Fatalf("unable to create bob regular spend: %v", err) } sweepTx.TxIn[0].Witness = bobRegularSpend vm, err = txscript.NewEngine(regularOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(channelBalance)) if err != nil { t.Fatalf("unable to create engine: %v", err) } if err := vm.Execute(); err != nil { t.Fatalf("bob p2wkh spend is invalid: %v", err) } }
// This example demonstrates manually creating and signing a redeem transaction. func ExampleSignTxOutput() { // Ordinarily the private key would come from whatever storage mechanism // is being used, but for this example just hard code it. privKeyBytes, err := hex.DecodeString("22a47fa09a223f2aa079edf85a7c2" + "d4f8720ee63e502ee2869afab7de234b80c") if err != nil { fmt.Println(err) return } privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes) pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed()) addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash, &chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } // For this example, create a fake transaction that represents what // would ordinarily be the real transaction that is being spent. It // contains a single output that pays to address in the amount of 1 BTC. originTx := wire.NewMsgTx(wire.TxVersion) prevOut := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0)) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}, nil) originTx.AddTxIn(txIn) pkScript, err := txscript.PayToAddrScript(addr) if err != nil { fmt.Println(err) return } txOut := wire.NewTxOut(100000000, pkScript) originTx.AddTxOut(txOut) originTxHash := originTx.TxHash() // Create the transaction to redeem the fake transaction. redeemTx := wire.NewMsgTx(wire.TxVersion) // Add the input(s) the redeeming transaction will spend. There is no // signature script at this point since it hasn't been created or signed // yet, hence nil is provided for it. prevOut = wire.NewOutPoint(&originTxHash, 0) txIn = wire.NewTxIn(prevOut, nil, nil) redeemTx.AddTxIn(txIn) // Ordinarily this would contain that actual destination of the funds, // but for this example don't bother. txOut = wire.NewTxOut(0, nil) redeemTx.AddTxOut(txOut) // Sign the redeeming transaction. lookupKey := func(a btcutil.Address) (*btcec.PrivateKey, bool, error) { // Ordinarily this function would involve looking up the private // key for the provided address, but since the only thing being // signed in this example uses the address associated with the // private key from above, simply return it with the compressed // flag set since the address is using the associated compressed // public key. // // NOTE: If you want to prove the code is actually signing the // transaction properly, uncomment the following line which // intentionally returns an invalid key to sign with, which in // turn will result in a failure during the script execution // when verifying the signature. // // privKey.D.SetInt64(12345) // return privKey, true, nil } // Notice that the script database parameter is nil here since it isn't // used. It must be specified when pay-to-script-hash transactions are // being signed. sigScript, err := txscript.SignTxOutput(&chaincfg.MainNetParams, redeemTx, 0, originTx.TxOut[0].PkScript, txscript.SigHashAll, txscript.KeyClosure(lookupKey), nil, nil) if err != nil { fmt.Println(err) return } redeemTx.TxIn[0].SignatureScript = sigScript // Prove that the transaction has been validly signed by executing the // script pair. flags := txscript.ScriptBip16 | txscript.ScriptVerifyDERSignatures | txscript.ScriptStrictMultiSig | txscript.ScriptDiscourageUpgradableNops vm, err := txscript.NewEngine(originTx.TxOut[0].PkScript, redeemTx, 0, flags, nil, nil, -1) if err != nil { fmt.Println(err) return } if err := vm.Execute(); err != nil { fmt.Println(err) return } fmt.Println("Transaction successfully signed") // Output: // Transaction successfully signed }
func TestInsertsCreditsDebitsRollbacks(t *testing.T) { t.Parallel() // Create a double spend of the received blockchain transaction. dupRecvTx, _ := btcutil.NewTxFromBytes(TstRecvSerializedTx) // Switch txout amount to 1 BTC. Transaction store doesn't // validate txs, so this is fine for testing a double spend // removal. TstDupRecvAmount := int64(1e8) newDupMsgTx := dupRecvTx.MsgTx() newDupMsgTx.TxOut[0].Value = TstDupRecvAmount TstDoubleSpendTx := btcutil.NewTx(newDupMsgTx) TstDoubleSpendSerializedTx := serializeTx(TstDoubleSpendTx) // Create a "signed" (with invalid sigs) tx that spends output 0 of // the double spend. spendingTx := wire.NewMsgTx() spendingTxIn := wire.NewTxIn(wire.NewOutPoint(TstDoubleSpendTx.Sha(), 0), []byte{0, 1, 2, 3, 4}) spendingTx.AddTxIn(spendingTxIn) spendingTxOut1 := wire.NewTxOut(1e7, []byte{5, 6, 7, 8, 9}) spendingTxOut2 := wire.NewTxOut(9e7, []byte{10, 11, 12, 13, 14}) spendingTx.AddTxOut(spendingTxOut1) spendingTx.AddTxOut(spendingTxOut2) TstSpendingTx := btcutil.NewTx(spendingTx) TstSpendingSerializedTx := serializeTx(TstSpendingTx) var _ = TstSpendingTx tests := []struct { name string f func(*Store) (*Store, error) bal, unc btcutil.Amount unspents map[wire.OutPoint]struct{} unmined map[wire.ShaHash]struct{} }{ { name: "new store", f: func(s *Store) (*Store, error) { return s, nil }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[wire.ShaHash]struct{}{}, }, { name: "txout insert", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, false) return s, err }, bal: 0, unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]struct{}{ *TstRecvTx.Sha(): {}, }, }, { name: "insert duplicate unconfirmed", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, false) return s, err }, bal: 0, unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]struct{}{ *TstRecvTx.Sha(): {}, }, }, { name: "confirmed txout insert", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]struct{}{}, }, { name: "insert duplicate confirmed", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]struct{}{}, }, { name: "rollback confirmed credit", f: func(s *Store) (*Store, error) { err := s.Rollback(TstRecvTxBlockDetails.Height) return s, err }, bal: 0, unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]struct{}{ *TstRecvTx.Sha(): {}, }, }, { name: "insert confirmed double spend", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstDoubleSpendSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstDoubleSpendTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]struct{}{}, }, { name: "insert unconfirmed debit", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) return s, err }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[wire.ShaHash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "insert unconfirmed debit again", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstDoubleSpendSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) return s, err }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[wire.ShaHash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "insert change (index 0)", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, true) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, }, unmined: map[wire.ShaHash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "insert output back to this own wallet (index 1)", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 1, true) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 1, }: {}, }, unmined: map[wire.ShaHash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "confirm signed tx", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstSignedTxBlockDetails) return s, err }, bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 1, }: {}, }, unmined: map[wire.ShaHash]struct{}{}, }, { name: "rollback after spending tx", f: func(s *Store) (*Store, error) { err := s.Rollback(TstSignedTxBlockDetails.Height + 1) return s, err }, bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 1, }: {}, }, unmined: map[wire.ShaHash]struct{}{}, }, { name: "rollback spending tx block", f: func(s *Store) (*Store, error) { err := s.Rollback(TstSignedTxBlockDetails.Height) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Sha(), Index: 1, }: {}, }, unmined: map[wire.ShaHash]struct{}{ *TstSpendingTx.Sha(): {}, }, }, { name: "rollback double spend tx block", f: func(s *Store) (*Store, error) { err := s.Rollback(TstRecvTxBlockDetails.Height) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ *wire.NewOutPoint(TstSpendingTx.Sha(), 0): {}, *wire.NewOutPoint(TstSpendingTx.Sha(), 1): {}, }, unmined: map[wire.ShaHash]struct{}{ *TstDoubleSpendTx.Sha(): {}, *TstSpendingTx.Sha(): {}, }, }, { name: "insert original recv txout", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ *wire.NewOutPoint(TstRecvTx.Sha(), 0): {}, }, unmined: map[wire.ShaHash]struct{}{}, }, } s, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } for _, test := range tests { tmpStore, err := test.f(s) if err != nil { t.Fatalf("%s: got error: %v", test.name, err) } s = tmpStore bal, err := s.Balance(1, TstRecvCurrentHeight) if err != nil { t.Fatalf("%s: Confirmed Balance failed: %v", test.name, err) } if bal != test.bal { t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal) } unc, err := s.Balance(0, TstRecvCurrentHeight) if err != nil { t.Fatalf("%s: Unconfirmed Balance failed: %v", test.name, err) } unc -= bal if unc != test.unc { t.Fatalf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc) } // Check that unspent outputs match expected. unspent, err := s.UnspentOutputs() if err != nil { t.Fatalf("%s: failed to fetch unspent outputs: %v", test.name, err) } for _, cred := range unspent { if _, ok := test.unspents[cred.OutPoint]; !ok { t.Errorf("%s: unexpected unspent output: %v", test.name, cred.OutPoint) } delete(test.unspents, cred.OutPoint) } if len(test.unspents) != 0 { t.Fatalf("%s: missing expected unspent output(s)", test.name) } // Check that unmined txs match expected. unmined, err := s.UnminedTxs() if err != nil { t.Fatalf("%s: cannot load unmined transactions: %v", test.name, err) } for _, tx := range unmined { txHash := tx.TxSha() if _, ok := test.unmined[txHash]; !ok { t.Fatalf("%s: unexpected unmined tx: %v", test.name, txHash) } delete(test.unmined, txHash) } if len(test.unmined) != 0 { t.Fatalf("%s: missing expected unmined tx(s)", test.name) } } }
// SendCoins does send coins, but it's very rudimentary // wit makes it into p2wpkh. Which is not yet spendable. func (s *SPVCon) SendCoins(adrs []btcutil.Address, sendAmts []int64) error { if len(adrs) != len(sendAmts) { return fmt.Errorf("%d addresses and %d amounts", len(adrs), len(sendAmts)) } var err error var score, totalSend, fee int64 dustCutoff := int64(20000) // below this amount, just give to miners satPerByte := int64(80) // satoshis per byte fee; have as arg later rawUtxos, err := s.TS.GetAllUtxos() if err != nil { return err } var allUtxos SortableUtxoSlice // start with utxos sorted by value. for _, utxo := range rawUtxos { score += utxo.Value allUtxos = append(allUtxos, *utxo) } // smallest and unconfirmed last (because it's reversed) sort.Sort(sort.Reverse(allUtxos)) // sort.Reverse(allUtxos) for _, amt := range sendAmts { totalSend += amt } // important rule in bitcoin, output total > input total is invalid. if totalSend > score { return fmt.Errorf("trying to send %d but %d available.", totalSend, score) } tx := wire.NewMsgTx() // make new tx // add non-change (arg) outputs for i, adr := range adrs { // make address script 76a914...88ac or 0014... outAdrScript, err := txscript.PayToAddrScript(adr) if err != nil { return err } // make user specified txout and add to tx txout := wire.NewTxOut(sendAmts[i], outAdrScript) tx.AddTxOut(txout) } // generate a utxo slice for your inputs var ins utxoSlice // add utxos until we've had enough nokori := totalSend // nokori is how much is needed on input side for _, utxo := range allUtxos { // skip unconfirmed. Or de-prioritize? // if utxo.AtHeight == 0 { // continue // } // yeah, lets add this utxo! ins = append(ins, utxo) // as we add utxos, fill in sigscripts // generate previous pkscripts (subscritpt?) for all utxos // then make txins with the utxo and prevpk, and insert them into the tx // these are all zeroed out during signing but it's an easy way to keep track var prevPKs []byte if utxo.IsWit { //tx.Flags = 0x01 wa, err := btcutil.NewAddressWitnessPubKeyHash( s.TS.Adrs[utxo.KeyIdx].PkhAdr.ScriptAddress(), s.TS.Param) prevPKs, err = txscript.PayToAddrScript(wa) if err != nil { return err } } else { // otherwise generate directly prevPKs, err = txscript.PayToAddrScript( s.TS.Adrs[utxo.KeyIdx].PkhAdr) if err != nil { return err } } tx.AddTxIn(wire.NewTxIn(&utxo.Op, prevPKs, nil)) nokori -= utxo.Value // if nokori is positive, don't bother checking fee yet. if nokori < 0 { fee = EstFee(tx, satPerByte) if nokori < -fee { // done adding utxos: nokori below negative est. fee break } } } // see if there's enough left to also add a change output changeOld, err := s.TS.NewAdr() // change is witnessy if err != nil { return err } changeAdr, err := btcutil.NewAddressWitnessPubKeyHash( changeOld.ScriptAddress(), s.TS.Param) if err != nil { return err } changeScript, err := txscript.PayToAddrScript(changeAdr) if err != nil { return err } changeOut := wire.NewTxOut(0, changeScript) tx.AddTxOut(changeOut) fee = EstFee(tx, satPerByte) changeOut.Value = -(nokori + fee) if changeOut.Value < dustCutoff { // remove last output (change) : not worth it tx.TxOut = tx.TxOut[:len(tx.TxOut)-1] } // sort utxos on the input side. use this instead of txsort // because we want to remember which keys are associated with which inputs sort.Sort(ins) // sort tx -- this only will change txouts since inputs are already sorted txsort.InPlaceSort(tx) // tx is ready for signing, sigStash := make([][]byte, len(ins)) witStash := make([][][]byte, len(ins)) // generate tx-wide hashCache for segwit stuff // middle index number doesn't matter for sighashAll. hCache := txscript.NewTxSigHashes(tx) for i, txin := range tx.TxIn { // pick key child, err := s.TS.rootPrivKey.Child( ins[i].KeyIdx + hdkeychain.HardenedKeyStart) if err != nil { return err } priv, err := child.ECPrivKey() if err != nil { return err } // This is where witness based sighash types need to happen // sign into stash if ins[i].IsWit { witStash[i], err = txscript.WitnessScript( tx, hCache, i, ins[i].Value, txin.SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } } else { sigStash[i], err = txscript.SignatureScript( tx, i, txin.SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } } } // swap sigs into sigScripts in txins for i, txin := range tx.TxIn { if sigStash[i] != nil { txin.SignatureScript = sigStash[i] } if witStash[i] != nil { txin.Witness = witStash[i] txin.SignatureScript = nil } } // fmt.Printf("tx: %s", TxToString(tx)) // buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) // send it out on the wire. hope it gets there. // we should deal with rejects. Don't yet. err = s.NewOutgoingTx(tx) if err != nil { return err } return nil }
// deserializeWithdrawal deserializes the given byte slice into a dbWithdrawalRow, // converts it into an withdrawalInfo and returns it. This function must run // with the address manager unlocked. func deserializeWithdrawal(p *Pool, serialized []byte) (*withdrawalInfo, error) { var row dbWithdrawalRow if err := gob.NewDecoder(bytes.NewReader(serialized)).Decode(&row); err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize withdrawal information", err) } wInfo := &withdrawalInfo{ lastSeriesID: row.LastSeriesID, dustThreshold: row.DustThreshold, } chainParams := p.Manager().ChainParams() wInfo.requests = make([]OutputRequest, len(row.Requests)) // A map of requests indexed by OutBailmentID; needed to populate // WithdrawalStatus.Outputs later on. requestsByOID := make(map[OutBailmentID]OutputRequest) for i, req := range row.Requests { addr, err := btcutil.DecodeAddress(req.Addr, chainParams) if err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize addr for requested output", err) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, newError(ErrWithdrawalStorage, "invalid addr for requested output", err) } request := OutputRequest{ Address: addr, Amount: req.Amount, PkScript: pkScript, Server: req.Server, Transaction: req.Transaction, } wInfo.requests[i] = request requestsByOID[request.outBailmentID()] = request } startAddr := row.StartAddress wAddr, err := p.WithdrawalAddress(startAddr.SeriesID, startAddr.Branch, startAddr.Index) if err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize startAddress", err) } wInfo.startAddress = *wAddr cAddr, err := p.ChangeAddress(row.ChangeStart.SeriesID, row.ChangeStart.Index) if err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize changeStart", err) } wInfo.changeStart = *cAddr // TODO: Copy over row.Status.nextInputAddr. Not done because StartWithdrawal // does not update that yet. nextChangeAddr := row.Status.NextChangeAddr cAddr, err = p.ChangeAddress(nextChangeAddr.SeriesID, nextChangeAddr.Index) if err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize nextChangeAddress for withdrawal", err) } wInfo.status = WithdrawalStatus{ nextChangeAddr: *cAddr, fees: row.Status.Fees, outputs: make(map[OutBailmentID]*WithdrawalOutput, len(row.Status.Outputs)), sigs: row.Status.Sigs, transactions: make(map[Ntxid]changeAwareTx, len(row.Status.Transactions)), } for oid, output := range row.Status.Outputs { outpoints := make([]OutBailmentOutpoint, len(output.Outpoints)) for i, outpoint := range output.Outpoints { outpoints[i] = OutBailmentOutpoint{ ntxid: outpoint.Ntxid, index: outpoint.Index, amount: outpoint.Amount, } } wInfo.status.outputs[oid] = &WithdrawalOutput{ request: requestsByOID[output.OutBailmentID], status: output.Status, outpoints: outpoints, } } for ntxid, tx := range row.Status.Transactions { msgtx := wire.NewMsgTx() if err := msgtx.Deserialize(bytes.NewBuffer(tx.SerializedMsgTx)); err != nil { return nil, newError(ErrWithdrawalStorage, "cannot deserialize transaction", err) } wInfo.status.transactions[ntxid] = changeAwareTx{ MsgTx: msgtx, changeIdx: tx.ChangeIdx, } } return wInfo, nil }
func (s *SPVCon) SendOne(u Utxo, adr btcutil.Address) error { // fixed fee fee := int64(5000) sendAmt := u.Value - fee tx := wire.NewMsgTx() // make new tx // add single output outAdrScript, err := txscript.PayToAddrScript(adr) if err != nil { return err } // make user specified txout and add to tx txout := wire.NewTxOut(sendAmt, outAdrScript) tx.AddTxOut(txout) var prevPKs []byte if u.IsWit { //tx.Flags = 0x01 wa, err := btcutil.NewAddressWitnessPubKeyHash( s.TS.Adrs[u.KeyIdx].PkhAdr.ScriptAddress(), s.TS.Param) prevPKs, err = txscript.PayToAddrScript(wa) if err != nil { return err } } else { // otherwise generate directly prevPKs, err = txscript.PayToAddrScript( s.TS.Adrs[u.KeyIdx].PkhAdr) if err != nil { return err } } tx.AddTxIn(wire.NewTxIn(&u.Op, prevPKs, nil)) var sig []byte var wit [][]byte hCache := txscript.NewTxSigHashes(tx) child, err := s.TS.rootPrivKey.Child(u.KeyIdx + hdkeychain.HardenedKeyStart) if err != nil { return err } priv, err := child.ECPrivKey() if err != nil { return err } // This is where witness based sighash types need to happen // sign into stash if u.IsWit { wit, err = txscript.WitnessScript( tx, hCache, 0, u.Value, tx.TxIn[0].SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } } else { sig, err = txscript.SignatureScript( tx, 0, tx.TxIn[0].SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } } // swap sigs into sigScripts in txins if sig != nil { tx.TxIn[0].SignatureScript = sig } if wit != nil { tx.TxIn[0].Witness = wit tx.TxIn[0].SignatureScript = nil } return s.NewOutgoingTx(tx) }
// Test the sigscript generation for valid and invalid inputs, all // hashTypes, and with and without compression. This test creates // sigscripts to spend fake coinbase inputs, as sigscripts cannot be // created for the MsgTxs in txTests, since they come from the blockchain // and we don't have the private keys. func TestSignatureScript(t *testing.T) { t.Parallel() privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyD) nexttest: for i := range sigScriptTests { tx := wire.NewMsgTx(wire.TxVersion) output := wire.NewTxOut(500, []byte{OP_RETURN}) tx.AddTxOut(output) for range sigScriptTests[i].inputs { txin := wire.NewTxIn(coinbaseOutPoint, nil, nil) tx.AddTxIn(txin) } var script []byte var err error for j := range tx.TxIn { var idx int if sigScriptTests[i].inputs[j].indexOutOfRange { t.Errorf("at test %v", sigScriptTests[i].name) idx = len(sigScriptTests[i].inputs) } else { idx = j } script, err = SignatureScript(tx, idx, sigScriptTests[i].inputs[j].txout.PkScript, sigScriptTests[i].hashType, privKey, sigScriptTests[i].compress) if (err == nil) != sigScriptTests[i].inputs[j].sigscriptGenerates { if err == nil { t.Errorf("passed test '%v' incorrectly", sigScriptTests[i].name) } else { t.Errorf("failed test '%v': %v", sigScriptTests[i].name, err) } continue nexttest } if !sigScriptTests[i].inputs[j].sigscriptGenerates { // done with this test continue nexttest } tx.TxIn[j].SignatureScript = script } // If testing using a correct sigscript but for an incorrect // index, use last input script for first input. Requires > 0 // inputs for test. if sigScriptTests[i].scriptAtWrongIndex { tx.TxIn[0].SignatureScript = script sigScriptTests[i].inputs[0].inputValidates = false } // Validate tx input scripts scriptFlags := ScriptBip16 | ScriptVerifyDERSignatures for j := range tx.TxIn { vm, err := NewEngine(sigScriptTests[i]. inputs[j].txout.PkScript, tx, j, scriptFlags, nil, nil, 0) if err != nil { t.Errorf("cannot create script vm for test %v: %v", sigScriptTests[i].name, err) continue nexttest } err = vm.Execute() if (err == nil) != sigScriptTests[i].inputs[j].inputValidates { if err == nil { t.Errorf("passed test '%v' validation incorrectly: %v", sigScriptTests[i].name, err) } else { t.Errorf("failed test '%v' validation: %v", sigScriptTests[i].name, err) } continue nexttest } } } }
// TestPeerListeners tests that the peer listeners are called as expected. func TestPeerListeners(t *testing.T) { verack := make(chan struct{}, 1) ok := make(chan wire.Message, 20) peerCfg := &peer.Config{ Listeners: peer.MessageListeners{ OnGetAddr: func(p *peer.Peer, msg *wire.MsgGetAddr) { ok <- msg }, OnAddr: func(p *peer.Peer, msg *wire.MsgAddr) { ok <- msg }, OnPing: func(p *peer.Peer, msg *wire.MsgPing) { ok <- msg }, OnPong: func(p *peer.Peer, msg *wire.MsgPong) { ok <- msg }, OnAlert: func(p *peer.Peer, msg *wire.MsgAlert) { ok <- msg }, OnMemPool: func(p *peer.Peer, msg *wire.MsgMemPool) { ok <- msg }, OnTx: func(p *peer.Peer, msg *wire.MsgTx) { ok <- msg }, OnBlock: func(p *peer.Peer, msg *wire.MsgBlock, buf []byte) { ok <- msg }, OnInv: func(p *peer.Peer, msg *wire.MsgInv) { ok <- msg }, OnHeaders: func(p *peer.Peer, msg *wire.MsgHeaders) { ok <- msg }, OnNotFound: func(p *peer.Peer, msg *wire.MsgNotFound) { ok <- msg }, OnGetData: func(p *peer.Peer, msg *wire.MsgGetData) { ok <- msg }, OnGetBlocks: func(p *peer.Peer, msg *wire.MsgGetBlocks) { ok <- msg }, OnGetHeaders: func(p *peer.Peer, msg *wire.MsgGetHeaders) { ok <- msg }, OnFeeFilter: func(p *peer.Peer, msg *wire.MsgFeeFilter) { ok <- msg }, OnFilterAdd: func(p *peer.Peer, msg *wire.MsgFilterAdd) { ok <- msg }, OnFilterClear: func(p *peer.Peer, msg *wire.MsgFilterClear) { ok <- msg }, OnFilterLoad: func(p *peer.Peer, msg *wire.MsgFilterLoad) { ok <- msg }, OnMerkleBlock: func(p *peer.Peer, msg *wire.MsgMerkleBlock) { ok <- msg }, OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) { ok <- msg }, OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { verack <- struct{}{} }, OnReject: func(p *peer.Peer, msg *wire.MsgReject) { ok <- msg }, OnSendHeaders: func(p *peer.Peer, msg *wire.MsgSendHeaders) { ok <- msg }, }, UserAgentName: "peer", UserAgentVersion: "1.0", ChainParams: &chaincfg.MainNetParams, Services: wire.SFNodeBloom, } inConn, outConn := pipe( &conn{raddr: "10.0.0.1:8333"}, &conn{raddr: "10.0.0.2:8333"}, ) inPeer := peer.NewInboundPeer(peerCfg) inPeer.AssociateConnection(inConn) peerCfg.Listeners = peer.MessageListeners{ OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { verack <- struct{}{} }, } outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") if err != nil { t.Errorf("NewOutboundPeer: unexpected err %v\n", err) return } outPeer.AssociateConnection(outConn) for i := 0; i < 2; i++ { select { case <-verack: case <-time.After(time.Second * 1): t.Errorf("TestPeerListeners: verack timeout\n") return } } tests := []struct { listener string msg wire.Message }{ { "OnGetAddr", wire.NewMsgGetAddr(), }, { "OnAddr", wire.NewMsgAddr(), }, { "OnPing", wire.NewMsgPing(42), }, { "OnPong", wire.NewMsgPong(42), }, { "OnAlert", wire.NewMsgAlert([]byte("payload"), []byte("signature")), }, { "OnMemPool", wire.NewMsgMemPool(), }, { "OnTx", wire.NewMsgTx(wire.TxVersion), }, { "OnBlock", wire.NewMsgBlock(wire.NewBlockHeader(1, &chainhash.Hash{}, &chainhash.Hash{}, 1, 1)), }, { "OnInv", wire.NewMsgInv(), }, { "OnHeaders", wire.NewMsgHeaders(), }, { "OnNotFound", wire.NewMsgNotFound(), }, { "OnGetData", wire.NewMsgGetData(), }, { "OnGetBlocks", wire.NewMsgGetBlocks(&chainhash.Hash{}), }, { "OnGetHeaders", wire.NewMsgGetHeaders(), }, { "OnFeeFilter", wire.NewMsgFeeFilter(15000), }, { "OnFilterAdd", wire.NewMsgFilterAdd([]byte{0x01}), }, { "OnFilterClear", wire.NewMsgFilterClear(), }, { "OnFilterLoad", wire.NewMsgFilterLoad([]byte{0x01}, 10, 0, wire.BloomUpdateNone), }, { "OnMerkleBlock", wire.NewMsgMerkleBlock(wire.NewBlockHeader(1, &chainhash.Hash{}, &chainhash.Hash{}, 1, 1)), }, // only one version message is allowed // only one verack message is allowed { "OnReject", wire.NewMsgReject("block", wire.RejectDuplicate, "dupe block"), }, { "OnSendHeaders", wire.NewMsgSendHeaders(), }, } t.Logf("Running %d tests", len(tests)) for _, test := range tests { // Queue the test message outPeer.QueueMessage(test.msg, nil) select { case <-ok: case <-time.After(time.Second * 1): t.Errorf("TestPeerListeners: %s timeout", test.listener) return } } inPeer.Disconnect() outPeer.Disconnect() }
// echo -n | openssl sha256 // This stuff gets reversed!!! shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") shaHash1, _ = wire.NewShaHash(shaHash1Bytes) outpoint1 = wire.NewOutPoint(shaHash1, 0) // echo | openssl sha256 // This stuff gets reversed!!! shaHash2Bytes, _ = hex.DecodeString("01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b") shaHash2, _ = wire.NewShaHash(shaHash2Bytes) outpoint2 = wire.NewOutPoint(shaHash2, 1) // create inputs from outpoint1 and outpoint2 inputs = []*wire.TxIn{wire.NewTxIn(outpoint1, nil, nil), wire.NewTxIn(outpoint2, nil, nil)} // Commitment Signature tx = wire.NewMsgTx() emptybytes = new([]byte) sigStr, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, privKey) commitSig, _ = btcec.ParseSignature(sigStr, btcec.S256()) // Funding TX Sig 1 sig1privKeyBytes, _ = hex.DecodeString("927f5827d75dd2addeb532c0fa5ac9277565f981dd6d0d037b422be5f60bdbef") sig1privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sig1privKeyBytes) sigStr1, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, sig1privKey) commitSig1, _ = btcec.ParseSignature(sigStr1, btcec.S256()) // Funding TX Sig 2 sig2privKeyBytes, _ = hex.DecodeString("8a4ad188f6f4000495b765cfb6ffa591133a73019c45428ddd28f53bab551847") sig2privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sig2privKeyBytes) sigStr2, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, sig2privKey) commitSig2, _ = btcec.ParseSignature(sigStr2, btcec.S256()) // Slice of Funding TX Sigs
// handleContributionMsg processes the second workflow step for the lifetime of // a channel reservation. Upon completion, the reservation will carry a // completed funding transaction (minus the counterparty's input signatures), // both versions of the commitment transaction, and our signature for their // version of the commitment transaction. func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { l.limboMtx.Lock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] l.limboMtx.Unlock() if !ok { req.err <- fmt.Errorf("attempted to update non-existant funding state") 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() fundingTx := pendingReservation.fundingTx // Some temporary variables to cut down on the resolution verbosity. pendingReservation.theirContribution = req.contribution theirContribution := req.contribution ourContribution := pendingReservation.ourContribution // Add all multi-party inputs and outputs to the transaction. for _, ourInput := range ourContribution.Inputs { fundingTx.AddTxIn(ourInput) } for _, theirInput := range theirContribution.Inputs { fundingTx.AddTxIn(theirInput) } for _, ourChangeOutput := range ourContribution.ChangeOutputs { fundingTx.AddTxOut(ourChangeOutput) } for _, theirChangeOutput := range theirContribution.ChangeOutputs { fundingTx.AddTxOut(theirChangeOutput) } ourKey := pendingReservation.partialState.OurMultiSigKey theirKey := theirContribution.MultiSigKey // Finally, add the 2-of-2 multi-sig output which will set up the lightning // channel. channelCapacity := int64(pendingReservation.partialState.Capacity) witnessScript, multiSigOut, err := GenFundingPkScript(ourKey.SerializeCompressed(), theirKey.SerializeCompressed(), channelCapacity) if err != nil { req.err <- err return } pendingReservation.partialState.FundingWitnessScript = witnessScript // Sort the transaction. Since both side agree to a canonical // ordering, by sorting we no longer need to send the entire // transaction. Only signatures will be exchanged. fundingTx.AddTxOut(multiSigOut) txsort.InPlaceSort(pendingReservation.fundingTx) // Next, sign all inputs that are ours, collecting the signatures in // order of the inputs. pendingReservation.ourFundingInputScripts = make([]*InputScript, 0, len(ourContribution.Inputs)) signDesc := SignDescriptor{ HashType: txscript.SigHashAll, SigHashes: txscript.NewTxSigHashes(fundingTx), } for i, txIn := range fundingTx.TxIn { info, err := l.FetchInputInfo(&txIn.PreviousOutPoint) if err == ErrNotMine { continue } else if err != nil { req.err <- err return } signDesc.Output = info signDesc.InputIndex = i inputScript, err := l.Signer.ComputeInputScript(fundingTx, &signDesc) if err != nil { req.err <- err return } txIn.SignatureScript = inputScript.ScriptSig txIn.Witness = inputScript.Witness pendingReservation.ourFundingInputScripts = append( pendingReservation.ourFundingInputScripts, inputScript, ) } // Locate the index of the multi-sig outpoint in order to record it // since the outputs are canonically sorted. If this is a single funder // workflow, then we'll also need to send this to the remote node. fundingTxID := fundingTx.TxSha() _, multiSigIndex := FindScriptOutputIndex(fundingTx, multiSigOut.PkScript) fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex) pendingReservation.partialState.FundingOutpoint = fundingOutpoint // Initialize an empty sha-chain for them, tracking the current pending // revocation hash (we don't yet know the pre-image so we can't add it // to the chain). e := &elkrem.ElkremReceiver{} pendingReservation.partialState.RemoteElkrem = e pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey masterElkremRoot, err := l.deriveMasterElkremRoot() if err != nil { req.err <- err return } // Now that we have their commitment key, we can create the revocation // key for the first version of our commitment transaction. To do so, // we'll first create our elkrem root, then grab the first pre-iamge // from it. elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey) elkremSender := elkrem.NewElkremSender(elkremRoot) pendingReservation.partialState.LocalElkrem = elkremSender firstPreimage, err := elkremSender.AtIndex(0) if err != nil { req.err <- err return } theirCommitKey := theirContribution.CommitKey ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:]) // Create the txIn to our commitment transaction; required to construct // the commitment transactions. fundingTxIn := wire.NewTxIn(wire.NewOutPoint(&fundingTxID, multiSigIndex), nil, nil) // With the funding tx complete, create both commitment transactions. // TODO(roasbeef): much cleanup + de-duplication pendingReservation.fundingLockTime = theirContribution.CsvDelay ourBalance := ourContribution.FundingAmount theirBalance := theirContribution.FundingAmount ourCommitKey := ourContribution.CommitKey ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, ourRevokeKey, ourContribution.CsvDelay, ourBalance, theirBalance) if err != nil { req.err <- err return } theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey, theirContribution.RevocationKey, theirContribution.CsvDelay, theirBalance, ourBalance) if err != nil { req.err <- err return } // Sort both transactions according to the agreed upon cannonical // ordering. This lets us skip sending the entire transaction over, // instead we'll just send signatures. txsort.InPlaceSort(ourCommitTx) txsort.InPlaceSort(theirCommitTx) deliveryScript, err := txscript.PayToAddrScript(theirContribution.DeliveryAddress) if err != nil { req.err <- err return } // Record newly available information witin the open channel state. pendingReservation.partialState.RemoteCsvDelay = theirContribution.CsvDelay pendingReservation.partialState.TheirDeliveryScript = deliveryScript pendingReservation.partialState.ChanID = fundingOutpoint pendingReservation.partialState.TheirCommitKey = theirCommitKey pendingReservation.partialState.TheirMultiSigKey = theirContribution.MultiSigKey pendingReservation.partialState.OurCommitTx = ourCommitTx pendingReservation.ourContribution.RevocationKey = ourRevokeKey // Generate a signature for their version of the initial commitment // transaction. signDesc = SignDescriptor{ WitnessScript: witnessScript, PubKey: ourKey, Output: multiSigOut, HashType: txscript.SigHashAll, SigHashes: txscript.NewTxSigHashes(theirCommitTx), InputIndex: 0, } sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc) if err != nil { req.err <- err return } pendingReservation.ourCommitmentSig = sigTheirCommit req.err <- nil }
func testSpendNotification(miner *rpctest.Harness, notifier chainntnfs.ChainNotifier, t *testing.T) { // We'd like to test the spend notifiations for all // ChainNotifier concrete implemenations. // // To do so, we first create a new output to our test target // address. txid, err := getTestTxId(miner) if err != nil { t.Fatalf("unable to create test addr: %v", err) } // Mine a single block which should include that txid above. if _, err := miner.Node.Generate(1); err != nil { t.Fatalf("unable to generate single block: %v", err) } // Now that we have the txid, fetch the transaction itself. wrappedTx, err := miner.Node.GetRawTransaction(txid) if err != nil { t.Fatalf("unable to get new tx: %v", err) } tx := wrappedTx.MsgTx() // Locate the output index sent to us. We need this so we can // construct a spending txn below. outIndex := -1 var pkScript []byte for i, txOut := range tx.TxOut { if bytes.Contains(txOut.PkScript, testAddr.ScriptAddress()) { pkScript = txOut.PkScript outIndex = i break } } if outIndex == -1 { t.Fatalf("unable to locate new output") } // Now that we've found the output index, register for a spentness // notification for the newly created output. outpoint := wire.NewOutPoint(txid, uint32(outIndex)) spentIntent, err := notifier.RegisterSpendNtfn(outpoint) if err != nil { t.Fatalf("unable to register for spend ntfn: %v", err) } // Next, create a new transaction spending that output. spendingTx := wire.NewMsgTx() spendingTx.AddTxIn(&wire.TxIn{ PreviousOutPoint: *outpoint, }) spendingTx.AddTxOut(&wire.TxOut{ Value: 1e8, PkScript: pkScript, }) sigScript, err := txscript.SignatureScript(spendingTx, 0, pkScript, txscript.SigHashAll, privKey, true) if err != nil { t.Fatalf("unable to sign tx: %v", err) } spendingTx.TxIn[0].SignatureScript = sigScript // Broadcast our spending transaction. spenderSha, err := miner.Node.SendRawTransaction(spendingTx, true) if err != nil { t.Fatalf("unable to brodacst tx: %v", err) } // Now we mine a single block, which should include our spend. The // notification should also be sent off. if _, err := miner.Node.Generate(1); err != nil { t.Fatalf("unable to generate single block: %v", err) } spentNtfn := make(chan *chainntnfs.SpendDetail) go func() { spentNtfn <- <-spentIntent.Spend }() select { case ntfn := <-spentNtfn: // We've received the spend nftn. So now verify all the fields // have been set properly. if ntfn.SpentOutPoint != outpoint { t.Fatalf("ntfn includes wrong output, reports %v instead of %v", ntfn.SpentOutPoint, outpoint) } if !bytes.Equal(ntfn.SpenderTxHash.Bytes(), spenderSha.Bytes()) { t.Fatalf("ntfn includes wrong spender tx sha, reports %v intead of %v", ntfn.SpenderTxHash.Bytes(), spenderSha.Bytes()) } if ntfn.SpenderInputIndex != 0 { t.Fatalf("ntfn includes wrong spending input index, reports %v, should be %v", ntfn.SpenderInputIndex, 0) } case <-time.After(2 * time.Second): t.Fatalf("spend ntfn never received") } }
// TestHTLCReceiverSpendValidation tests all possible valid+invalid redemption // paths in the script used within the reciever's commitment transaction for an // incoming HTLC. // // The following cases are exercised by this test: // * reciever spends // * HTLC redemption w/ invalid preimage size // * HTLC redemption w/ invalid sequence // * HTLC redemption w/ valid preimage size // * sender spends // * revoke w/ sig // * refund w/ invalid lock time // * refund w/ valid lock time func TestHTLCReceiverSpendValidation(t *testing.T) { // We generate a fake output, and the coresponding txin. This output // doesn't need to exist, as we'll only be validating spending from the // transaction that references this. fundingOut := &wire.OutPoint{ Hash: testHdSeed, Index: 50, } fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) // Generate a payment and revocation pre-image to be used below. revokePreimage := testHdSeed[:] revokeHash := fastsha256.Sum256(revokePreimage) paymentPreimage := revokeHash paymentPreimage[0] ^= 1 paymentHash := fastsha256.Sum256(paymentPreimage[:]) // We'll also need some tests keys for alice and bob, and meta-data of // the HTLC output. aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) paymentAmt := btcutil.Amount(1 * 10e8) cltvTimeout := uint32(8) csvTimeout := uint32(5) // Generate the raw HTLC redemption scripts, and its p2wsh counterpart. htlcScript, err := receiverHTLCScript(cltvTimeout, csvTimeout, aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:]) if err != nil { t.Fatalf("unable to create htlc sender script: %v", err) } htlcWitnessScript, err := witnessScriptHash(htlcScript) if err != nil { t.Fatalf("unable to create p2wsh htlc script: %v", err) } // This will be Bob's commitment transaction. In this scenario Alice // is sending an HTLC to a node she has a a path to (could be Bob, // could be multiple hops down, it doesn't really matter). recieverCommitTx := wire.NewMsgTx() recieverCommitTx.AddTxIn(fakeFundingTxIn) recieverCommitTx.AddTxOut(&wire.TxOut{ Value: int64(paymentAmt), PkScript: htlcWitnessScript, }) prevOut := &wire.OutPoint{ Hash: recieverCommitTx.TxSha(), Index: 0, } sweepTx := wire.NewMsgTx() sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil)) sweepTx.AddTxOut( &wire.TxOut{ PkScript: []byte("doesn't matter"), Value: 1 * 10e8, }, ) testCases := []struct { witness func() wire.TxWitness valid bool }{ { // HTLC redemption w/ invalid preimage size makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendRedeem(htlcScript, paymentAmt, bobKeyPriv, sweepTx, bytes.Repeat([]byte{1}, 45), csvTimeout, ) }), false, }, { // HTLC redemption w/ invalid sequence makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendRedeem(htlcScript, paymentAmt, bobKeyPriv, sweepTx, paymentPreimage[:], csvTimeout-2, ) }), false, }, { // HTLC redemption w/ valid preimage size makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendRedeem(htlcScript, paymentAmt, bobKeyPriv, sweepTx, paymentPreimage[:], csvTimeout, ) }), true, }, { // revoke w/ sig makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendRevoke(htlcScript, paymentAmt, aliceKeyPriv, sweepTx, revokePreimage[:], ) }), true, }, { // refund w/ invalid lock time makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendTimeout(htlcScript, paymentAmt, aliceKeyPriv, sweepTx, cltvTimeout-2) }), false, }, { // refund w/ valid lock time makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendTimeout(htlcScript, paymentAmt, aliceKeyPriv, sweepTx, cltvTimeout) }), true, }, } for i, testCase := range testCases { sweepTx.TxIn[0].Witness = testCase.witness() vm, err := txscript.NewEngine(htlcWitnessScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(paymentAmt)) if err != nil { t.Fatalf("unable to create engine: %v", err) } // This buffer will trace execution of the Script, only dumping // out to stdout in the case that a test fails. var debugBuf bytes.Buffer done := false for !done { dis, err := vm.DisasmPC() if err != nil { t.Fatalf("stepping (%v)\n", err) } debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) done, err = vm.Step() if err != nil && testCase.valid { fmt.Println(debugBuf.String()) t.Fatalf("spend test case #%v failed, spend should be valid: %v", i, err) } else if err == nil && !testCase.valid && done { fmt.Println(debugBuf.String()) t.Fatalf("spend test case #%v succeed, spend should be invalid: %v", i, err) } debugBuf.WriteString(fmt.Sprintf("Stack: ", vm.GetStack())) debugBuf.WriteString(fmt.Sprintf("AltStack: ", vm.GetAltStack())) } } }
// TestCheckSerializedHeight tests the checkSerializedHeight function with // various serialized heights and also does negative tests to ensure errors // and handled properly. func TestCheckSerializedHeight(t *testing.T) { // Create an empty coinbase template to be used in the tests below. coinbaseOutpoint := wire.NewOutPoint(&chainhash.Hash{}, math.MaxUint32) coinbaseTx := wire.NewMsgTx(1) coinbaseTx.AddTxIn(wire.NewTxIn(coinbaseOutpoint, nil, nil)) // Expected rule errors. missingHeightError := blockchain.RuleError{ ErrorCode: blockchain.ErrMissingCoinbaseHeight, } badHeightError := blockchain.RuleError{ ErrorCode: blockchain.ErrBadCoinbaseHeight, } tests := []struct { sigScript []byte // Serialized data wantHeight int32 // Expected height err error // Expected error type }{ // No serialized height length. {[]byte{}, 0, missingHeightError}, // Serialized height length with no height bytes. {[]byte{0x02}, 0, missingHeightError}, // Serialized height length with too few height bytes. {[]byte{0x02, 0x4a}, 0, missingHeightError}, // Serialized height that needs 2 bytes to encode. {[]byte{0x02, 0x4a, 0x52}, 21066, nil}, // Serialized height that needs 2 bytes to encode, but backwards // endianness. {[]byte{0x02, 0x4a, 0x52}, 19026, badHeightError}, // Serialized height that needs 3 bytes to encode. {[]byte{0x03, 0x40, 0x0d, 0x03}, 200000, nil}, // Serialized height that needs 3 bytes to encode, but backwards // endianness. {[]byte{0x03, 0x40, 0x0d, 0x03}, 1074594560, badHeightError}, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { msgTx := coinbaseTx.Copy() msgTx.TxIn[0].SignatureScript = test.sigScript tx := btcutil.NewTx(msgTx) err := blockchain.TstCheckSerializedHeight(tx, test.wantHeight) if reflect.TypeOf(err) != reflect.TypeOf(test.err) { t.Errorf("checkSerializedHeight #%d wrong error type "+ "got: %v <%T>, want: %T", i, err, err, test.err) continue } if rerr, ok := err.(blockchain.RuleError); ok { trerr := test.err.(blockchain.RuleError) if rerr.ErrorCode != trerr.ErrorCode { t.Errorf("checkSerializedHeight #%d wrong "+ "error code got: %v, want: %v", i, rerr.ErrorCode, trerr.ErrorCode) continue } } } }
func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running single funder workflow responder test") // For this scenario, bob will initiate the channel, while we simply act as // the responder. capacity := btcutil.Amount(4 * 1e8) // Create the bob-test wallet which will be initiator of a single // funder channel shortly. bobNode, err := newBobNode(miner, capacity) if err != nil { t.Fatalf("unable to create bob node: %v", err) } // Bob sends over a single funding request, so we allocate our // contribution and the necessary resources. fundingAmt := btcutil.Amount(0) chanReservation, err := wallet.InitChannelReservation(capacity, fundingAmt, bobNode.id, bobAddr, numReqConfs, 4) if err != nil { t.Fatalf("unable to init channel reservation: %v", err) } // Verify all contribution fields have been set properly. Since we are // the recipient of a single-funder channel, we shouldn't have selected // any coins or generated any change outputs. ourContribution := chanReservation.OurContribution() if len(ourContribution.Inputs) != 0 { t.Fatalf("outputs for funding tx not properly selected, have %v "+ "outputs should have 0", len(ourContribution.Inputs)) } if len(ourContribution.ChangeOutputs) != 0 { t.Fatalf("coin selection failed, should have no change outputs, "+ "instead have: %v", ourContribution.ChangeOutputs[0].Value) } if ourContribution.MultiSigKey == nil { t.Fatalf("alice's key for multi-sig not found") } if ourContribution.CommitKey == nil { t.Fatalf("alice's key for commit not found") } if ourContribution.DeliveryAddress == nil { t.Fatalf("alice's final delivery address not found") } if ourContribution.CsvDelay == 0 { t.Fatalf("csv delay not set") } // Next we process Bob's single funder contribution which doesn't // include any inputs or change addresses, as only Bob will construct // the funding transaction. bobContribution := bobNode.Contribution(ourContribution.CommitKey) if err := chanReservation.ProcessSingleContribution(bobContribution); err != nil { t.Fatalf("unable to process bob's contribution: %v", err) } if chanReservation.FinalFundingTx() != nil { t.Fatalf("funding transaction populated!") } if len(bobContribution.Inputs) != 1 { t.Fatalf("bob shouldn't have one inputs, instead has %v", len(bobContribution.Inputs)) } if ourContribution.RevocationKey == nil { t.Fatalf("alice's revocation key not found") } if len(bobContribution.ChangeOutputs) != 1 { t.Fatalf("bob shouldn't have one change output, instead "+ "has %v", len(bobContribution.ChangeOutputs)) } if bobContribution.MultiSigKey == nil { t.Fatalf("bob's key for multi-sig not found") } if bobContribution.CommitKey == nil { t.Fatalf("bob's key for commit tx not found") } if bobContribution.DeliveryAddress == nil { t.Fatalf("bob's final delivery address not found") } if bobContribution.RevocationKey == nil { t.Fatalf("bob's revocaiton key not found") } fundingRedeemScript, multiOut, err := lnwallet.GenFundingPkScript( ourContribution.MultiSigKey.SerializeCompressed(), bobContribution.MultiSigKey.SerializeCompressed(), // TODO(roasbeef): account for hard-coded fee, remove bob node int64(capacity)+5000) if err != nil { t.Fatalf("unable to generate multi-sig output: %v", err) } // At this point, we send Bob our contribution, allowing him to // construct the funding transaction, and sign our version of the // commitment transaction. fundingTx := wire.NewMsgTx() fundingTx.AddTxIn(bobNode.availableOutputs[0]) fundingTx.AddTxOut(bobNode.changeOutputs[0]) fundingTx.AddTxOut(multiOut) txsort.InPlaceSort(fundingTx) if _, err := bobNode.signFundingTx(fundingTx); err != nil { t.Fatalf("unable to generate bob's funding sigs: %v", err) } // Locate the output index of the 2-of-2 in order to send back to the // wallet so it can finalize the transaction by signing bob's commitment // transaction. fundingTxID := fundingTx.TxSha() _, multiSigIndex := lnwallet.FindScriptOutputIndex(fundingTx, multiOut.PkScript) fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex) fundingTxIn := wire.NewTxIn(fundingOutpoint, nil, nil) aliceCommitTx, err := lnwallet.CreateCommitTx(fundingTxIn, ourContribution.CommitKey, bobContribution.CommitKey, ourContribution.RevocationKey, ourContribution.CsvDelay, 0, capacity) if err != nil { t.Fatalf("unable to create alice's commit tx: %v", err) } txsort.InPlaceSort(aliceCommitTx) bobCommitSig, err := bobNode.signCommitTx(aliceCommitTx, // TODO(roasbeef): account for hard-coded fee, remove bob node fundingRedeemScript, int64(capacity)+5000) if err != nil { t.Fatalf("unable to sign alice's commit tx: %v", err) } // With this stage complete, Alice can now complete the reservation. bobRevokeKey := bobContribution.RevocationKey if err := chanReservation.CompleteReservationSingle(bobRevokeKey, fundingOutpoint, bobCommitSig); err != nil { t.Fatalf("unable to complete reservation: %v", err) } // Alice should have saved the funding output. if chanReservation.FundingOutpoint() != fundingOutpoint { t.Fatalf("funding outputs don't match: %#v vs %#v", chanReservation.FundingOutpoint(), fundingOutpoint) } // Some period of time later, Bob presents us with an SPV proof // attesting to an open channel. At this point Alice recognizes the // channel, saves the state to disk, and creates the channel itself. if _, err := chanReservation.FinalizeReservation(); err != nil { t.Fatalf("unable to finalize reservation: %v", err) } // TODO(roasbeef): bob verify alice's sig }
// TestBIP0113Activation tests for proper adherance of the BIP 113 rule // constraint which requires all transaction finality tests to use the MTP of // the last 11 blocks, rather than the timestamp of the block which includes // them. // // Overview: // - Pre soft-fork: // - Transactions with non-final lock-times from the PoV of MTP should be // rejected from the mempool. // - Transactions within non-final MTP based lock-times should be accepted // in valid blocks. // // - Post soft-fork: // - Transactions with non-final lock-times from the PoV of MTP should be // rejected from the mempool and when found within otherwise valid blocks. // - Transactions with final lock-times from the PoV of MTP should be // accepted to the mempool and mined in future block. func TestBIP0113Activation(t *testing.T) { t.Parallel() btcdCfg := []string{"--rejectnonstd"} r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg) if err != nil { t.Fatal("unable to create primary harness: ", err) } if err := r.SetUp(true, 1); err != nil { t.Fatalf("unable to setup test chain: %v", err) } defer r.TearDown() // Create a fresh output for usage within the test below. const outputValue = btcutil.SatoshiPerBitcoin outputKey, testOutput, testPkScript, err := makeTestOutput(r, t, outputValue) if err != nil { t.Fatalf("unable to create test output: %v", err) } // Fetch a fresh address from the harness, we'll use this address to // send funds back into the Harness. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to generate address: %v", err) } addrScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to generate addr script: %v", err) } // Now create a transaction with a lock time which is "final" according // to the latest block, but not according to the current median time // past. tx := wire.NewMsgTx(1) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: *testOutput, }) tx.AddTxOut(&wire.TxOut{ PkScript: addrScript, Value: outputValue - 1000, }) // We set the lock-time of the transaction to just one minute after the // current MTP of the chain. chainInfo, err := r.Node.GetBlockChainInfo() if err != nil { t.Fatalf("unable to query for chain info: %v", err) } tx.LockTime = uint32(chainInfo.MedianTime) + 1 sigScript, err := txscript.SignatureScript(tx, 0, testPkScript, txscript.SigHashAll, outputKey, true) if err != nil { t.Fatalf("unable to generate sig: %v", err) } tx.TxIn[0].SignatureScript = sigScript // This transaction should be rejected from the mempool as using MTP // for transactions finality is now a policy rule. Additionally, the // exact error should be the rejection of a non-final transaction. _, err = r.Node.SendRawTransaction(tx, true) if err == nil { t.Fatalf("transaction accepted, but should be non-final") } else if !strings.Contains(err.Error(), "not finalized") { t.Fatalf("transaction should be rejected due to being "+ "non-final, instead: %v", err) } // However, since the block validation consensus rules haven't yet // activated, a block including the transaction should be accepted. txns := []*btcutil.Tx{btcutil.NewTx(tx)} block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err != nil { t.Fatalf("unable to submit block: %v", err) } txid := tx.TxHash() assertTxInBlock(r, t, block.Hash(), &txid) // At this point, the block height should be 103: we mined 101 blocks // to create a single mature output, then an additional block to create // a new output, and then mined a single block above to include our // transation. assertChainHeight(r, t, 103) // Next, mine enough blocks to ensure that the soft-fork becomes // activated. Assert that the block version of the second-to-last block // in the final range is active. // Next, mine ensure blocks to ensure that the soft-fork becomes // active. We're at height 103 and we need 200 blocks to be mined after // the genesis target period, so we mine 196 blocks. This'll put us at // height 299. The getblockchaininfo call checks the state for the // block AFTER the current height. numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 4 if _, err := r.Node.Generate(numBlocks); err != nil { t.Fatalf("unable to generate blocks: %v", err) } assertChainHeight(r, t, 299) assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdActive) // The timeLockDeltas slice represents a series of deviations from the // current MTP which will be used to test border conditions w.r.t // transaction finality. -1 indicates 1 second prior to the MTP, 0 // indicates the current MTP, and 1 indicates 1 second after the // current MTP. // // This time, all transactions which are final according to the MTP // *should* be accepted to both the mempool and within a valid block. // While transactions with lock-times *after* the current MTP should be // rejected. timeLockDeltas := []int64{-1, 0, 1} for _, timeLockDelta := range timeLockDeltas { chainInfo, err = r.Node.GetBlockChainInfo() if err != nil { t.Fatalf("unable to query for chain info: %v", err) } medianTimePast := chainInfo.MedianTime // Create another test output to be spent shortly below. outputKey, testOutput, testPkScript, err = makeTestOutput(r, t, outputValue) if err != nil { t.Fatalf("unable to create test output: %v", err) } // Create a new transaction with a lock-time past the current known // MTP. tx = wire.NewMsgTx(1) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: *testOutput, }) tx.AddTxOut(&wire.TxOut{ PkScript: addrScript, Value: outputValue - 1000, }) tx.LockTime = uint32(medianTimePast + timeLockDelta) sigScript, err = txscript.SignatureScript(tx, 0, testPkScript, txscript.SigHashAll, outputKey, true) if err != nil { t.Fatalf("unable to generate sig: %v", err) } tx.TxIn[0].SignatureScript = sigScript // If the time-lock delta is greater than -1, then the // transaction should be rejected from the mempool and when // included within a block. A time-lock delta of -1 should be // accepted as it has a lock-time of one // second _before_ the current MTP. _, err = r.Node.SendRawTransaction(tx, true) if err == nil && timeLockDelta >= 0 { t.Fatal("transaction was accepted into the mempool " + "but should be rejected!") } else if err != nil && !strings.Contains(err.Error(), "not finalized") { t.Fatalf("transaction should be rejected from mempool "+ "due to being non-final, instead: %v", err) } txns = []*btcutil.Tx{btcutil.NewTx(tx)} _, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err == nil && timeLockDelta >= 0 { t.Fatal("block should be rejected due to non-final " + "txn, but was accepted") } else if err != nil && !strings.Contains(err.Error(), "unfinalized") { t.Fatalf("block should be rejected due to non-final "+ "tx, instead: %v", err) } } }
// createSweepTx creates a final sweeping transaction with all witnesses // inplace for all inputs. The created transaction has a single output sending // all the funds back to the source wallet. func (u *utxoNursery) createSweepTx(matureOutputs []*immatureOutput) (*wire.MsgTx, error) { sweepAddr, err := u.wallet.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { return nil, err } pkScript, err := txscript.PayToAddrScript(sweepAddr) if err != nil { return nil, err } var totalSum btcutil.Amount for _, o := range matureOutputs { totalSum += o.amt } sweepTx := wire.NewMsgTx() sweepTx.Version = 2 sweepTx.AddTxOut(&wire.TxOut{ PkScript: pkScript, Value: int64(totalSum - 1000), }) for _, utxo := range matureOutputs { sweepTx.AddTxIn(&wire.TxIn{ PreviousOutPoint: utxo.outPoint, // TODO(roasbeef): assumes pure block delays Sequence: utxo.blocksToMaturity, }) } // TODO(roasbeef): insert fee calculation // * remove hardcoded fee above // With all the inputs in place, use each output's unique witness // function to generate the final witness required for spending. hashCache := txscript.NewTxSigHashes(sweepTx) for i, txIn := range sweepTx.TxIn { witness, err := matureOutputs[i].witnessFunc(sweepTx, hashCache, i) if err != nil { return nil, err } txIn.Witness = witness } return sweepTx, nil }