func p2pkhOutputs(amounts ...btcutil.Amount) []*wire.TxOut { v := make([]*wire.TxOut, 0, len(amounts)) for _, a := range amounts { outScript := make([]byte, txsizes.P2PKHOutputSize) v = append(v, wire.NewTxOut(int64(a), outScript)) } return v }
// NewUnsignedTransaction creates an unsigned transaction paying to one or more // non-change outputs. An appropriate transaction fee is included based on the // transaction size. // // Transaction inputs are chosen from repeated calls to fetchInputs with // increasing targets amounts. // // If any remaining output value can be returned to the wallet via a change // output without violating mempool dust rules, a P2PKH change output is // appended to the transaction outputs. Since the change output may not be // necessary, fetchChange is called zero or one times to generate this script. // This function must return a P2PKH script or smaller, otherwise fee estimation // will be incorrect. // // If successful, the transaction, total input value spent, and all previous // output scripts are returned. If the input source was unable to provide // enough input value to pay for every output any any necessary fees, an // InputSourceError is returned. // // BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs. // TODO(roasbeef): fix fee estimation for witness outputs func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) { targetAmount := h.SumOutputValues(outputs) estimatedSize := txsizes.EstimateSerializeSize(1, outputs, true) targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize) for { inputAmount, inputs, inputValues, scripts, err := fetchInputs(targetAmount + targetFee) if err != nil { return nil, err } if inputAmount < targetAmount+targetFee { return nil, insufficientFundsError{} } maxSignedSize := txsizes.EstimateSerializeSize(len(inputs), outputs, true) maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize) remainingAmount := inputAmount - targetAmount if remainingAmount < maxRequiredFee { targetFee = maxRequiredFee continue } unsignedTransaction := &wire.MsgTx{ Version: wire.TxVersion, TxIn: inputs, TxOut: outputs, LockTime: 0, } changeIndex := -1 changeAmount := inputAmount - targetAmount - maxRequiredFee if changeAmount != 0 && !txrules.IsDustAmount(changeAmount, txsizes.P2PKHPkScriptSize, relayFeePerKb) { changeScript, err := fetchChange() if err != nil { return nil, err } if len(changeScript) > txsizes.P2PKHPkScriptSize { return nil, errors.New("fee estimation requires change " + "scripts no larger than P2PKH output scripts") } change := wire.NewTxOut(int64(changeAmount), changeScript) l := len(outputs) unsignedTransaction.TxOut = append(outputs[:l:l], change) changeIndex = l } return &AuthoredTx{ Tx: unsignedTransaction, PrevScripts: scripts, PrevInputValues: inputValues, TotalInput: inputAmount, ChangeIndex: changeIndex, }, nil } }
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)) } }
// addChange adds a change output if there are any satoshis left after paying // all the outputs and network fees. It returns true if a change output was // added. // // This method must be called only once, and no extra inputs/outputs should be // added after it's called. Also, callsites must make sure adding a change // output won't cause the tx to exceed the size limit. func (tx *withdrawalTx) addChange(pkScript []byte) bool { tx.fee = tx.calculateFee() change := tx.inputTotal() - tx.outputTotal() - tx.fee log.Debugf("addChange: input total %v, output total %v, fee %v", tx.inputTotal(), tx.outputTotal(), tx.fee) if change > 0 { tx.changeOutput = wire.NewTxOut(int64(change), pkScript) log.Debugf("Added change output with amount %v", change) } return tx.hasChange() }
func TestWithdrawalTxToMsgTxWithInputButNoOutputsWithChange(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() tx := createWithdrawalTx(t, pool, []int64{1}, []int64{}) tx.changeOutput = wire.NewTxOut(int64(1), []byte{}) msgtx := tx.toMsgTx() compareMsgTxAndWithdrawalTxOutputs(t, msgtx, tx) compareMsgTxAndWithdrawalTxInputs(t, msgtx, tx) }
// 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 }
// addrPairsToOutputs converts a map describing a set of outputs to be created, // the outputs themselves. The passed map pairs up an address, to a desired // output value amount. Each address is converted to its corresponding pkScript // to be used within the constructed output(s). func addrPairsToOutputs(addrPairs map[string]int64) ([]*wire.TxOut, error) { outputs := make([]*wire.TxOut, 0, len(addrPairs)) for addr, amt := range addrPairs { addr, err := btcutil.DecodeAddress(addr, activeNetParams.Params) if err != nil { return nil, err } pkscript, err := txscript.PayToAddrScript(addr) if err != nil { return nil, err } outputs = append(outputs, wire.NewTxOut(amt, pkscript)) } return outputs, nil }
func createMsgTx(pkScript []byte, amts []int64) *wire.MsgTx { msgtx := &wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{ { PreviousOutPoint: wire.OutPoint{ Hash: wire.ShaHash{}, Index: 0xffffffff, }, SignatureScript: []byte{txscript.OP_NOP}, Sequence: 0xffffffff, }, }, LockTime: 0, } for _, amt := range amts { msgtx.AddTxOut(wire.NewTxOut(amt, pkScript)) } return msgtx }
// GenFundingPkScript creates a redeem script, and its matching p2wsh // output for the funding transaction. func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) { // As a sanity check, ensure that the passed amount is above zero. if amt <= 0 { return nil, nil, fmt.Errorf("can't create FundTx script with " + "zero, or negative coins") } // First, create the 2-of-2 multi-sig script itself. witnessScript, err := genMultiSigScript(aPub, bPub) if err != nil { return nil, nil, err } // With the 2-of-2 script in had, generate a p2wsh script which pays // to the funding script. pkScript, err := witnessScriptHash(witnessScript) if err != nil { return nil, nil, err } return witnessScript, wire.NewTxOut(amt, pkScript), nil }
func testMemWalletLockedOutputs(r *Harness, t *testing.T) { // Obtain the initial balance of the wallet at this point. startingBalance := r.ConfirmedBalance() // First, create a signed transaction spending some outputs. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to generate new address: %v", err) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create script: %v", err) } outputAmt := btcutil.Amount(50 * btcutil.SatoshiPerBitcoin) output := wire.NewTxOut(int64(outputAmt), pkScript) tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { t.Fatalf("unable to create transaction: %v", err) } // The current wallet balance should now be at least 50 BTC less // (accounting for fees) than the period balance currentBalance := r.ConfirmedBalance() if !(currentBalance <= startingBalance-outputAmt) { t.Fatalf("spent outputs not locked: previous balance %v, "+ "current balance %v", startingBalance, currentBalance) } // Now unlocked all the spent inputs within the unbroadcast signed // transaction. The current balance should now be exactly that of the // starting balance. r.UnlockOutputs(tx.TxIn) currentBalance = r.ConfirmedBalance() if currentBalance != startingBalance { t.Fatalf("current and starting balance should now match: "+ "expected %v, got %v", startingBalance, currentBalance) } }
func testSendOutputs(r *Harness, t *testing.T) { genSpend := func(amt btcutil.Amount) *chainhash.Hash { // Grab a fresh address from the wallet. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to get new address: %v", err) } // Next, send amt BTC to this address, spending from one of our mature // coinbase outputs. addrScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to generate pkscript to addr: %v", err) } output := wire.NewTxOut(int64(amt), addrScript) txid, err := r.SendOutputs([]*wire.TxOut{output}, 10) if err != nil { t.Fatalf("coinbase spend failed: %v", err) } return txid } assertTxMined := func(txid *chainhash.Hash, blockHash *chainhash.Hash) { block, err := r.Node.GetBlock(blockHash) if err != nil { t.Fatalf("unable to get block: %v", err) } numBlockTxns := len(block.Transactions) if numBlockTxns < 2 { t.Fatalf("crafted transaction wasn't mined, block should have "+ "at least %v transactions instead has %v", 2, numBlockTxns) } minedTx := block.Transactions[1] txHash := minedTx.TxHash() if txHash != *txid { t.Fatalf("txid's don't match, %v vs %v", txHash, txid) } } // First, generate a small spend which will require only a single // input. txid := genSpend(btcutil.Amount(5 * btcutil.SatoshiPerBitcoin)) // Generate a single block, the transaction the wallet created should // be found in this block. blockHashes, err := r.Node.Generate(1) if err != nil { t.Fatalf("unable to generate single block: %v", err) } assertTxMined(txid, blockHashes[0]) // Next, generate a spend much greater than the block reward. This // transaction should also have been mined properly. txid = genSpend(btcutil.Amount(500 * btcutil.SatoshiPerBitcoin)) blockHashes, err = r.Node.Generate(1) if err != nil { t.Fatalf("unable to generate single block: %v", err) } assertTxMined(txid, blockHashes[0]) }
// 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 }
0xeb, 0xa4, 0x02, 0xcb, 0x68, 0xe0, 0x69, 0x56, 0xbf, 0x32, 0x53, 0x90, 0x0e, 0x0a, 0x88, 0xac} uncompressedAddrStr = "1L6fd93zGmtzkK6CsZFVVoCwzZV3MUtJ4F" compressedAddrStr = "14apLppt9zTq6cNw8SDfiJhk9PhkZrQtYZ" ) // Pretend output amounts. const coinbaseVal = 2500000000 const fee = 5000000 var sigScriptTests = []tstSigScript{ { name: "one input uncompressed", inputs: []tstInput{ { txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), sigscriptGenerates: true, inputValidates: true, indexOutOfRange: false, }, }, hashType: SigHashAll, compress: false, scriptAtWrongIndex: false, }, { name: "two inputs uncompressed", inputs: []tstInput{ { txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), sigscriptGenerates: true,
// 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 } } } }
func testJoinMempools(r *Harness, t *testing.T) { // Assert main test harness has no transactions in its mempool. pooledHashes, err := r.Node.GetRawMempool() if err != nil { t.Fatalf("unable to get mempool for main test harness: %v", err) } if len(pooledHashes) != 0 { t.Fatal("main test harness mempool not empty") } // Create a local test harness with only the genesis block. The nodes // will be synced below so the same transaction can be sent to both // nodes without it being an orphan. harness, err := New(&chaincfg.SimNetParams, nil, nil) if err != nil { t.Fatal(err) } if err := harness.SetUp(false, 0); err != nil { t.Fatalf("unable to complete rpctest setup: %v", err) } defer harness.TearDown() nodeSlice := []*Harness{r, harness} // Both mempools should be considered synced as they are empty. // Therefore, this should return instantly. if err := JoinNodes(nodeSlice, Mempools); err != nil { t.Fatalf("unable to join node on mempools: %v", err) } // Generate a coinbase spend to a new address within the main harness' // mempool. addr, err := r.NewAddress() addrScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to generate pkscript to addr: %v", err) } output := wire.NewTxOut(5e8, addrScript) testTx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { t.Fatalf("coinbase spend failed: %v", err) } if _, err := r.Node.SendRawTransaction(testTx, true); err != nil { t.Fatalf("send transaction failed: %v", err) } // Wait until the transaction shows up to ensure the two mempools are // not the same. harnessSynced := make(chan struct{}) go func() { for { poolHashes, err := r.Node.GetRawMempool() if err != nil { t.Fatalf("failed to retrieve harness mempool: %v", err) } if len(poolHashes) > 0 { break } time.Sleep(time.Millisecond * 100) } harnessSynced <- struct{}{} }() select { case <-harnessSynced: case <-time.After(time.Minute): t.Fatalf("harness node never received transaction") } // This select case should fall through to the default as the goroutine // should be blocked on the JoinNodes call. poolsSynced := make(chan struct{}) go func() { if err := JoinNodes(nodeSlice, Mempools); err != nil { t.Fatalf("unable to join node on mempools: %v", err) } poolsSynced <- struct{}{} }() select { case <-poolsSynced: t.Fatalf("mempools detected as synced yet harness has a new tx") default: } // Establish an outbound connection from the local harness to the main // harness and wait for the chains to be synced. if err := ConnectNode(harness, r); err != nil { t.Fatalf("unable to connect harnesses: %v", err) } if err := JoinNodes(nodeSlice, Blocks); err != nil { t.Fatalf("unable to join node on blocks: %v", err) } // Send the transaction to the local harness which will result in synced // mempools. if _, err := harness.Node.SendRawTransaction(testTx, true); err != nil { t.Fatalf("send transaction failed: %v", err) } // Select once again with a special timeout case after 1 minute. The // goroutine above should now be blocked on sending into the unbuffered // channel. The send should immediately succeed. In order to avoid the // test hanging indefinitely, a 1 minute timeout is in place. select { case <-poolsSynced: // fall through case <-time.After(time.Minute): t.Fatalf("mempools never detected as synced") } }
// 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 }
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) }
// newBobNode generates a test "ln node" to interact with Alice (us). For the // funding transaction, bob has a single output totaling 7BTC. For our basic // test, he'll fund the channel with 5BTC, leaving 2BTC to the change output. // TODO(roasbeef): proper handling of change etc. func newBobNode(miner *rpctest.Harness, amt btcutil.Amount) (*bobNode, error) { // First, parse Bob's priv key in order to obtain a key he'll use for the // multi-sig funding transaction. privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) // Next, generate an output redeemable by bob. pkHash := btcutil.Hash160(pubKey.SerializeCompressed()) bobAddr, err := btcutil.NewAddressWitnessPubKeyHash( pkHash, miner.ActiveNet) if err != nil { return nil, err } bobAddrScript, err := txscript.PayToAddrScript(bobAddr) if err != nil { return nil, err } // Give bobNode one 7 BTC output for use in creating channels. output := &wire.TxOut{7e8, bobAddrScript} mainTxid, err := miner.CoinbaseSpend([]*wire.TxOut{output}) if err != nil { return nil, err } // Mine a block in order to include the above output in a block. During // the reservation workflow, we currently test to ensure that the funding // output we're given actually exists. if _, err := miner.Node.Generate(1); err != nil { return nil, err } // Grab the transaction in order to locate the output index to Bob. tx, err := miner.Node.GetRawTransaction(mainTxid) if err != nil { return nil, err } found, index := lnwallet.FindScriptOutputIndex(tx.MsgTx(), bobAddrScript) if !found { return nil, fmt.Errorf("output to bob never created") } prevOut := wire.NewOutPoint(mainTxid, index) bobTxIn := wire.NewTxIn(prevOut, nil, nil) // Using bobs priv key above, create a change output he can spend. bobChangeOutput := wire.NewTxOut(2*1e8, bobAddrScript) // Bob's initial revocation hash is just his private key with the first // byte changed... var revocation [32]byte copy(revocation[:], bobsPrivKey) revocation[0] = 0xff // His ID is just as creative... var id [wire.HashSize]byte id[0] = 0xff return &bobNode{ id: pubKey, privKey: privKey, channelKey: pubKey, deliveryAddress: bobAddr, revocation: revocation, fundingAmt: amt, delay: 5, availableOutputs: []*wire.TxIn{bobTxIn}, changeOutputs: []*wire.TxOut{bobChangeOutput}, }, nil }
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) } } }
func testListTransactionDetails(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running list transaction details test") // Create 5 new outputs spendable by the wallet. const numTxns = 5 const outputAmt = btcutil.SatoshiPerBitcoin txids := make(map[wire.ShaHash]struct{}) for i := 0; i < numTxns; i++ { addr, err := wallet.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { t.Fatalf("unable to create new address: %v", err) } script, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create output script: %v", err) } output := &wire.TxOut{outputAmt, script} txid, err := miner.CoinbaseSpend([]*wire.TxOut{output}) if err != nil { t.Fatalf("unable to send coinbase: %v", err) } txids[*txid] = struct{}{} } // Generate 10 blocks to mine all the transactions created above. const numBlocksMined = 10 blocks, err := miner.Node.Generate(numBlocksMined) if err != nil { t.Fatalf("unable to mine blocks: %v", err) } // Next, fetch all the current transaction details. // TODO(roasbeef): use ntfn client here instead? time.Sleep(time.Second * 2) txDetails, err := wallet.ListTransactionDetails() if err != nil { t.Fatalf("unable to fetch tx details: %v", err) } // Each of the transactions created above should be found with the // proper details populated. for _, txDetail := range txDetails { if _, ok := txids[txDetail.Hash]; !ok { continue } if txDetail.NumConfirmations != numBlocksMined { t.Fatalf("num confs incorrect, got %v expected %v", txDetail.NumConfirmations, numBlocksMined) } if txDetail.Value != outputAmt { t.Fatalf("tx value incorrect, got %v expected %v", txDetail.Value, outputAmt) } if !bytes.Equal(txDetail.BlockHash[:], blocks[0][:]) { t.Fatalf("block hash mismatch, got %v expected %v", txDetail.BlockHash, blocks[0]) } delete(txids, txDetail.Hash) } if len(txids) != 0 { t.Fatalf("all transactions not found in details!") } // Next create a transaction paying to an output which isn't under the // wallet's control. b := txscript.NewScriptBuilder() b.AddOp(txscript.OP_0) outputScript, err := b.Script() if err != nil { t.Fatalf("unable to make output script: %v", err) } burnOutput := wire.NewTxOut(outputAmt, outputScript) burnTXID, err := wallet.SendOutputs([]*wire.TxOut{burnOutput}) if err != nil { t.Fatalf("unable to create burn tx: %v", err) } burnBlock, err := miner.Node.Generate(1) if err != nil { t.Fatalf("unable to mine block: %v", err) } // Fetch the transaction details again, the new transaction should be // shown as debiting from the wallet's balance. time.Sleep(time.Second * 2) txDetails, err = wallet.ListTransactionDetails() if err != nil { t.Fatalf("unable to fetch tx details: %v", err) } var burnTxFound bool for _, txDetail := range txDetails { if !bytes.Equal(txDetail.Hash[:], burnTXID[:]) { continue } burnTxFound = true if txDetail.NumConfirmations != 1 { t.Fatalf("num confs incorrect, got %v expected %v", txDetail.NumConfirmations, 1) } if txDetail.Value >= -outputAmt { t.Fatalf("tx value incorrect, got %v expected %v", txDetail.Value, -outputAmt) } if !bytes.Equal(txDetail.BlockHash[:], burnBlock[0][:]) { t.Fatalf("block hash mismatch, got %v expected %v", txDetail.BlockHash, burnBlock[0]) } } if !burnTxFound { t.Fatalf("tx burning btc not found") } }
func testGenerateAndSubmitBlock(r *Harness, t *testing.T) { // Generate a few test spend transactions. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to generate new address: %v", err) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create script: %v", err) } output := wire.NewTxOut(btcutil.SatoshiPerBitcoin, pkScript) const numTxns = 5 txns := make([]*btcutil.Tx, 0, numTxns) for i := 0; i < numTxns; i++ { tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { t.Fatalf("unable to create tx: %v", err) } txns = append(txns, btcutil.NewTx(tx)) } // Now generate a block with the default block version, and a zero'd // out time. block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err != nil { t.Fatalf("unable to generate block: %v", err) } // Ensure that all created transactions were included, and that the // block version was properly set to the default. numBlocksTxns := len(block.Transactions()) if numBlocksTxns != numTxns+1 { t.Fatalf("block did not include all transactions: "+ "expected %v, got %v", numTxns+1, numBlocksTxns) } blockVersion := block.MsgBlock().Header.Version if blockVersion != BlockVersion { t.Fatalf("block version is not default: expected %v, got %v", BlockVersion, blockVersion) } // Next generate a block with a "non-standard" block version along with // time stamp a minute after the previous block's timestamp. timestamp := block.MsgBlock().Header.Timestamp.Add(time.Minute) targetBlockVersion := int32(1337) block, err = r.GenerateAndSubmitBlock(nil, targetBlockVersion, timestamp) if err != nil { t.Fatalf("unable to generate block: %v", err) } // Finally ensure that the desired block version and timestamp were set // properly. header := block.MsgBlock().Header blockVersion = header.Version if blockVersion != targetBlockVersion { t.Fatalf("block version mismatch: expected %v, got %v", targetBlockVersion, blockVersion) } if !timestamp.Equal(header.Timestamp) { t.Fatalf("header time stamp mismatch: expected %v, got %v", timestamp, header.Timestamp) } }