// CreateTxChain creates a chain of zero-fee transactions (each subsequent // transaction spends the entire amount from the previous one) with the first // one spending the provided outpoint. Each transaction spends the entire // amount of the previous one and as such does not include any fees. func (p *poolHarness) CreateTxChain(firstOutput spendableOutput, numTxns uint32) ([]*btcutil.Tx, error) { txChain := make([]*btcutil.Tx, 0, numTxns) prevOutPoint := firstOutput.outPoint spendableAmount := firstOutput.amount for i := uint32(0); i < numTxns; i++ { // Create the transaction using the previous transaction output // and paying the full amount to the payment address associated // with the harness. tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: prevOutPoint, SignatureScript: nil, Sequence: wire.MaxTxInSequenceNum, }) tx.AddTxOut(&wire.TxOut{ PkScript: p.payScript, Value: int64(spendableAmount), }) // Sign the new transaction. sigScript, err := txscript.SignatureScript(tx, 0, p.payScript, txscript.SigHashAll, p.signKey, true) if err != nil { return nil, err } tx.TxIn[0].SignatureScript = sigScript txChain = append(txChain, btcutil.NewTx(tx)) // Next transaction uses outputs from this one. prevOutPoint = wire.OutPoint{Hash: tx.TxHash(), Index: 0} } return txChain, nil }
// makeUtxoView creates a mock unspent transaction output view by using the // transaction index in order to look up all inputs referenced by the // transactions in the block. This is sometimes needed when catching indexes up // because many of the txouts could actually already be spent however the // associated scripts are still required to index them. func makeUtxoView(dbTx database.Tx, block *btcutil.Block) (*blockchain.UtxoViewpoint, error) { view := blockchain.NewUtxoViewpoint() for txIdx, tx := range block.Transactions() { // Coinbases do not reference any inputs. Since the block is // required to have already gone through full validation, it has // already been proven on the first transaction in the block is // a coinbase. if txIdx == 0 { continue } // Use the transaction index to load all of the referenced // inputs and add their outputs to the view. for _, txIn := range tx.MsgTx().TxIn { originOut := &txIn.PreviousOutPoint originTx, err := dbFetchTx(dbTx, &originOut.Hash) if err != nil { return nil, err } view.AddTxOuts(btcutil.NewTx(originTx), 0) } } return view, 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 }
// TestTx tests the API for Tx. func TestTx(t *testing.T) { testTx := Block100000.Transactions[0] tx := btcutil.NewTx(testTx) // Ensure we get the same data back out. if msgTx := tx.MsgTx(); !reflect.DeepEqual(msgTx, testTx) { t.Errorf("MsgTx: mismatched MsgTx - got %v, want %v", spew.Sdump(msgTx), spew.Sdump(testTx)) } // Ensure transaction index set and get work properly. wantIndex := 0 tx.SetIndex(0) if gotIndex := tx.Index(); gotIndex != wantIndex { t.Errorf("Index: mismatched index - got %v, want %v", gotIndex, wantIndex) } // Hash for block 100,000 transaction 0. wantShaStr := "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87" wantSha, err := wire.NewShaHashFromStr(wantShaStr) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) } // Request the sha multiple times to test generation and caching. for i := 0; i < 2; i++ { sha := tx.Sha() if !sha.IsEqual(wantSha) { t.Errorf("Sha #%d mismatched sha - got %v, want %v", i, sha, wantSha) } } }
// Receive waits for the response promised by the future and returns a // transaction given its hash. func (r FutureGetRawTransactionResult) Receive() (*btcutil.Tx, error) { res, err := receiveFuture(r) if err != nil { return nil, err } // Unmarshal result as a string. var txHex string err = json.Unmarshal(res, &txHex) if err != nil { return nil, err } // Decode the serialized transaction hex to raw bytes. serializedTx, err := hex.DecodeString(txHex) if err != nil { return nil, err } // Deserialize the transaction and return it. var msgTx wire.MsgTx if err := msgTx.Deserialize(bytes.NewReader(serializedTx)); err != nil { return nil, err } return btcutil.NewTx(&msgTx), nil }
// newUtxoViewpoint returns a new utxo view populated with outputs of the // provided source transactions as if there were available at the respective // block height specified in the heights slice. The length of the source txns // and source tx heights must match or it will panic. func newUtxoViewpoint(sourceTxns []*wire.MsgTx, sourceTxHeights []int32) *blockchain.UtxoViewpoint { if len(sourceTxns) != len(sourceTxHeights) { panic("each transaction must have its block height specified") } view := blockchain.NewUtxoViewpoint() for i, tx := range sourceTxns { view.AddTxOuts(btcutil.NewTx(tx), sourceTxHeights[i]) } return view }
// fetchTxStoreMain fetches transaction data about the provided set of // transactions from the point of view of the end of the main chain. It takes // a flag which specifies whether or not fully spent transaction should be // included in the results. func fetchTxStoreMain(db database.Db, txSet map[wire.ShaHash]struct{}, includeSpent bool) TxStore { // Just return an empty store now if there are no requested hashes. txStore := make(TxStore) if len(txSet) == 0 { return txStore } // The transaction store map needs to have an entry for every requested // transaction. By default, all the transactions are marked as missing. // Each entry will be filled in with the appropriate data below. txList := make([]*wire.ShaHash, 0, len(txSet)) for hash := range txSet { hashCopy := hash txStore[hash] = &TxData{Hash: &hashCopy, Err: database.ErrTxShaMissing} txList = append(txList, &hashCopy) } // Ask the database (main chain) for the list of transactions. This // will return the information from the point of view of the end of the // main chain. Choose whether or not to include fully spent // transactions depending on the passed flag. var txReplyList []*database.TxListReply if includeSpent { txReplyList = db.FetchTxByShaList(txList) } else { txReplyList = db.FetchUnSpentTxByShaList(txList) } for _, txReply := range txReplyList { // Lookup the existing results entry to modify. Skip // this reply if there is no corresponding entry in // the transaction store map which really should not happen, but // be safe. txD, ok := txStore[*txReply.Sha] if !ok { continue } // Fill in the transaction details. A copy is used here since // there is no guarantee the returned data isn't cached and // this code modifies the data. A bug caused by modifying the // cached data would likely be difficult to track down and could // cause subtle errors, so avoid the potential altogether. txD.Err = txReply.Err if txReply.Err == nil { txD.Tx = btcutil.NewTx(txReply.Tx) txD.BlockHeight = txReply.Height txD.Spent = make([]bool, len(txReply.TxSpent)) copy(txD.Spent, txReply.TxSpent) } } return txStore }
// CreateSignedTx creates a new signed transaction that consumes the provided // inputs and generates the provided number of outputs by evenly splitting the // total input amount. All outputs will be to the payment script associated // with the harness and all inputs are assumed to do the same. func (p *poolHarness) CreateSignedTx(inputs []spendableOutput, numOutputs uint32) (*btcutil.Tx, error) { // Calculate the total input amount and split it amongst the requested // number of outputs. var totalInput btcutil.Amount for _, input := range inputs { totalInput += input.amount } amountPerOutput := int64(totalInput) / int64(numOutputs) remainder := int64(totalInput) - amountPerOutput*int64(numOutputs) tx := wire.NewMsgTx(wire.TxVersion) for _, input := range inputs { tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: input.outPoint, SignatureScript: nil, Sequence: wire.MaxTxInSequenceNum, }) } for i := uint32(0); i < numOutputs; i++ { // Ensure the final output accounts for any remainder that might // be left from splitting the input amount. amount := amountPerOutput if i == numOutputs-1 { amount = amountPerOutput + remainder } tx.AddTxOut(&wire.TxOut{ PkScript: p.payScript, Value: amount, }) } // Sign the new transaction. for i := range tx.TxIn { sigScript, err := txscript.SignatureScript(tx, i, p.payScript, txscript.SigHashAll, p.signKey, true) if err != nil { return nil, err } tx.TxIn[i].SignatureScript = sigScript } return btcutil.NewTx(tx), nil }
// parseChainTxNtfnParams parses out the transaction and optional details about // the block it's mined in from the parameters of recvtx and redeemingtx // notifications. func parseChainTxNtfnParams(params []json.RawMessage) (*btcutil.Tx, *btcjson.BlockDetails, error) { if len(params) == 0 || len(params) > 2 { return nil, nil, wrongNumParams(len(params)) } // Unmarshal first parameter as a string. var txHex string err := json.Unmarshal(params[0], &txHex) if err != nil { return nil, nil, err } // If present, unmarshal second optional parameter as the block details // JSON object. var block *btcjson.BlockDetails if len(params) > 1 { err = json.Unmarshal(params[1], &block) if err != nil { return nil, nil, err } } // Hex decode and deserialize the transaction. serializedTx, err := hex.DecodeString(txHex) if err != nil { return nil, nil, err } var msgTx wire.MsgTx err = msgTx.Deserialize(bytes.NewReader(serializedTx)) if err != nil { return nil, nil, err } // TODO: Change recvtx and redeemingtx callback signatures to use // nicer types for details about the block (block sha as a // wire.ShaHash, block time as a time.Time, etc.). return btcutil.NewTx(&msgTx), block, 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() 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 }
// CreateCoinbaseTx returns a coinbase transaction with the requested number of // outputs paying an appropriate subsidy based on the passed block height to the // address associated with the harness. It automatically uses a standard // signature script that starts with the block height that is required by // version 2 blocks. func (p *poolHarness) CreateCoinbaseTx(blockHeight int32, numOutputs uint32) (*btcutil.Tx, error) { // Create standard coinbase script. extraNonce := int64(0) coinbaseScript, err := txscript.NewScriptBuilder(). AddInt64(int64(blockHeight)).AddInt64(extraNonce).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, }) totalInput := blockchain.CalcBlockSubsidy(blockHeight, p.chainParams) amountPerOutput := totalInput / int64(numOutputs) remainder := totalInput - amountPerOutput*int64(numOutputs) for i := uint32(0); i < numOutputs; i++ { // Ensure the final output accounts for any remainder that might // be left from splitting the input amount. amount := amountPerOutput if i == numOutputs-1 { amount = amountPerOutput + remainder } tx.AddTxOut(&wire.TxOut{ PkScript: p.payScript, Value: amount, }) } return btcutil.NewTx(tx), nil }
// TestCalcSequenceLock tests the LockTimeToSequence function, and the // CalcSequenceLock method of a Chain instance. The tests exercise several // combinations of inputs to the CalcSequenceLock function in order to ensure // the returned SequenceLocks are correct for each test instance. func TestCalcSequenceLock(t *testing.T) { fileName := "blk_0_to_4.dat.bz2" blocks, err := loadBlocks(fileName) if err != nil { t.Errorf("Error loading file: %v\n", err) return } // Create a new database and chain instance to run tests against. chain, teardownFunc, err := chainSetup("haveblock", &chaincfg.MainNetParams) if err != nil { t.Errorf("Failed to setup chain instance: %v", err) return } defer teardownFunc() // Since we're not dealing with the real block chain, disable // checkpoints and set the coinbase maturity to 1. chain.DisableCheckpoints(true) chain.TstSetCoinbaseMaturity(1) // Load all the blocks into our test chain. for i := 1; i < len(blocks); i++ { _, isOrphan, err := chain.ProcessBlock(blocks[i], blockchain.BFNone) if err != nil { t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) return } if isOrphan { t.Errorf("ProcessBlock incorrectly returned block %v "+ "is an orphan\n", i) return } } // Create with all the utxos within the create created above. utxoView := blockchain.NewUtxoViewpoint() for blockHeight, block := range blocks { for _, tx := range block.Transactions() { utxoView.AddTxOuts(tx, int32(blockHeight)) } } utxoView.SetBestHash(blocks[len(blocks)-1].Hash()) // The median past time from the point of view of the second to last // block in the chain. medianTime := blocks[2].MsgBlock().Header.Timestamp.Unix() // The median past time of the *next* block will be the timestamp of // the 2nd block due to the way MTP is calculated in order to be // compatible with Bitcoin Core. nextMedianTime := blocks[2].MsgBlock().Header.Timestamp.Unix() // We'll refer to this utxo within each input in the transactions // created below. This block that includes this UTXO has a height of 4. targetTx := blocks[4].Transactions()[0] utxo := wire.OutPoint{ Hash: *targetTx.Hash(), Index: 0, } // Add an additional transaction which will serve as our unconfirmed // output. var fakeScript []byte unConfTx := &wire.MsgTx{ TxOut: []*wire.TxOut{{ PkScript: fakeScript, Value: 5, }}, } unConfUtxo := wire.OutPoint{ Hash: unConfTx.TxHash(), Index: 0, } // Adding a utxo with a height of 0x7fffffff indicates that the output // is currently unmined. utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff) tests := []struct { tx *btcutil.Tx view *blockchain.UtxoViewpoint want *blockchain.SequenceLock mempool bool }{ // A transaction of version one should disable sequence locks // as the new sequence number semantics only apply to // transactions version 2 or higher. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: -1, }, }, // A transaction with a single input, that a max int sequence // number. This sequence number has the high bit set, so // sequence locks should be disabled. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: wire.MaxTxInSequenceNum, }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: -1, }, }, // A transaction with a single input whose lock time is // expressed in seconds. However, the specified lock time is // below the required floor for time based lock times since // they have time granularity of 512 seconds. As a result, the // seconds lock-time should be just before the median time of // the targeted block. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime - 1, BlockHeight: -1, }, }, // A transaction with a single input whose lock time is // expressed in seconds. The number of seconds should be 1023 // seconds after the median past time of the last block in the // chain. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 1024), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + 1023, BlockHeight: -1, }, }, // A transaction with multiple inputs. The first input has a // sequence lock in blocks with a value of 4. The last input // has a sequence number with a value of 5, but has the disable // bit set. So the first lock should be selected as it's the // target lock as its the furthest in the future lock that // isn't disabled. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3) | wire.SequenceLockTimeDisabled, }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: 6, }, }, // Transaction has a single input spending the genesis block // transaction. The input's sequence number is encodes a // relative lock-time in blocks (3 blocks). The sequence lock // should have a value of -1 for seconds, but a block height of // 6 meaning it can be included at height 7. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 6, }, }, // A transaction with two inputs with lock times expressed in // seconds. The selected sequence lock value for seconds should // be the time further in the future. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 5120), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: -1, }, }, // A transaction with two inputs with lock times expressed in // seconds. The selected sequence lock value for blocks should // be the height further in the future, so a height of 10 // indicating in can be included at height 7. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 1), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 7), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 10, }, }, // A transaction with multiple inputs. Two inputs are time // based, and the other two are input maturity based. The lock // lying further into the future for both inputs should be // chosen. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 6656), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 9), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: 12, }, }, // A transaction with a single unconfirmed input. As the input // is confirmed, the height of the input should be interpreted // as the height of the *next* block. So the relative block // lock should be based from a height of 5 rather than a height // of 4. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: unConfUtxo, Sequence: blockchain.LockTimeToSequence(false, 2), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 6, }, }, // A transaction with a single unconfirmed input. The input has // a time based lock, so the lock time should be based off the // MTP of the *next* block. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: unConfUtxo, Sequence: blockchain.LockTimeToSequence(true, 1024), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: nextMedianTime + 1023, BlockHeight: -1, }, }, } t.Logf("Running %v SequenceLock tests", len(tests)) for i, test := range tests { seqLock, err := chain.CalcSequenceLock(test.tx, test.view, test.mempool) if err != nil { t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err) } if seqLock.Seconds != test.want.Seconds { t.Fatalf("test #%d got %v seconds want %v seconds", i, seqLock.Seconds, test.want.Seconds) } if seqLock.BlockHeight != test.want.BlockHeight { t.Fatalf("test #%d got height of %v want height of %v ", i, seqLock.BlockHeight, test.want.BlockHeight) } } }
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.Hash(), 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[chainhash.Hash]struct{} }{ { name: "new store", f: func(s *Store) (*Store, error) { return s, nil }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[chainhash.Hash]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.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Hash(): {}, }, }, { 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.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Hash(): {}, }, }, { 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.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]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.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]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.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Hash(): {}, }, }, { 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.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]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[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { 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[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { 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.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { 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.Hash(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 1, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { 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.Hash(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 1, }: {}, }, unmined: map[chainhash.Hash]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.Hash(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 1, }: {}, }, unmined: map[chainhash.Hash]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.Hash(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 1, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { 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.Hash(), 0): {}, *wire.NewOutPoint(TstSpendingTx.Hash(), 1): {}, }, unmined: map[chainhash.Hash]struct{}{ *TstDoubleSpendTx.Hash(): {}, *TstSpendingTx.Hash(): {}, }, }, { 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.Hash(), 0): {}, }, unmined: map[chainhash.Hash]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.TxHash() 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 (w *LibbitcoinWallet) Spend(amount int64, addr btc.Address, feeLevel bitcoin.FeeLevel) error { // Check for dust script, _ := txscript.PayToAddrScript(addr) if txrules.IsDustAmount(btc.Amount(amount), len(script), txrules.DefaultRelayFeePerKb) { return errors.New("Amount is below dust threshold") } var additionalPrevScripts map[wire.OutPoint][]byte var additionalKeysByAddress map[string]*btc.WIF // Create input source coinMap := w.gatherCoins() coins := make([]coinset.Coin, 0, len(coinMap)) for k := range coinMap { coins = append(coins, k) } inputSource := func(target btc.Amount) (total btc.Amount, inputs []*wire.TxIn, scripts [][]byte, err error) { // TODO: maybe change the coin selection algorithm? We're using min coins right now because // TODO: we don't know the number of confirmations on each coin without querying the libbitcoin server. coinSelector := coinset.MinNumberCoinSelector{MaxInputs: 10000, MinChangeAmount: btc.Amount(10000)} coins, err := coinSelector.CoinSelect(target, coins) if err != nil { return total, inputs, scripts, errors.New("insuffient funds") } additionalPrevScripts = make(map[wire.OutPoint][]byte) additionalKeysByAddress = make(map[string]*btc.WIF) for _, c := range coins.Coins() { total += c.Value() outpoint := wire.NewOutPoint(c.Hash(), c.Index()) in := wire.NewTxIn(outpoint, []byte{}) in.Sequence = 0 // Opt-in RBF so we can bump fees inputs = append(inputs, in) additionalPrevScripts[*outpoint] = c.PkScript() key := coinMap[c] addr, _ := btc.NewAddressPubKey(key.PublicKey().Key, w.params) pk, _ := btcec.PrivKeyFromBytes(btcec.S256(), key.Key) wif, _ := btc.NewWIF(pk, w.params, true) additionalKeysByAddress[addr.AddressPubKeyHash().EncodeAddress()] = wif } return total, inputs, scripts, nil } // Get the fee per kilobyte feePerKB := int64(w.getFeePerByte(feeLevel)) * 1000 // outputs out := wire.NewTxOut(amount, script) // Create change source changeSource := func() ([]byte, error) { addr := w.GetCurrentAddress(bitcoin.CHANGE) script, err := txscript.PayToAddrScript(addr) if err != nil { return []byte{}, err } return script, nil } authoredTx, err := txauthor.NewUnsignedTransaction([]*wire.TxOut{out}, btc.Amount(feePerKB), inputSource, changeSource) if err != nil { return err } // BIP 69 sorting txsort.InPlaceSort(authoredTx.Tx) // Sign tx getKey := txscript.KeyClosure(func(addr btc.Address) ( *btcec.PrivateKey, bool, error) { addrStr := addr.EncodeAddress() wif := additionalKeysByAddress[addrStr] return wif.PrivKey, wif.CompressPubKey, nil }) getScript := txscript.ScriptClosure(func( addr btc.Address) ([]byte, error) { return []byte{}, nil }) for i, txIn := range authoredTx.Tx.TxIn { prevOutScript := additionalPrevScripts[txIn.PreviousOutPoint] script, err := txscript.SignTxOutput(w.params, authoredTx.Tx, i, prevOutScript, txscript.SigHashAll, getKey, getScript, txIn.SignatureScript) if err != nil { return errors.New("Failed to sign transaction") } txIn.SignatureScript = script } // Broadcast tx to bitcoin network serializedTx := new(bytes.Buffer) authoredTx.Tx.Serialize(serializedTx) w.Client.Broadcast(serializedTx.Bytes(), func(i interface{}, err error) { if err == nil { log.Infof("Broadcast tx %s to bitcoin network\n", authoredTx.Tx.TxSha().String()) } else { log.Errorf("Failed to broadcast tx, reason: %s\n", err) } }) // Update the db w.ProcessTransaction(btc.NewTx(authoredTx.Tx), 0) return nil }
// 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)) // 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 } } } }
// loadTxStore returns a transaction store loaded from a file. func loadTxStore(filename string) (blockchain.TxStore, error) { // The txstore file format is: // <num tx data entries> <tx length> <serialized tx> <blk height> // <num spent bits> <spent bits> // // All num and length fields are little-endian uint32s. The spent bits // field is padded to a byte boundary. filename = filepath.Join("testdata/", filename) fi, err := os.Open(filename) if err != nil { return nil, err } // Choose read based on whether the file is compressed or not. var r io.Reader if strings.HasSuffix(filename, ".bz2") { r = bzip2.NewReader(fi) } else { r = fi } defer fi.Close() // Num of transaction store objects. var numItems uint32 if err := binary.Read(r, binary.LittleEndian, &numItems); err != nil { return nil, err } txStore := make(blockchain.TxStore) var uintBuf uint32 for height := uint32(0); height < numItems; height++ { txD := blockchain.TxData{} // Serialized transaction length. err = binary.Read(r, binary.LittleEndian, &uintBuf) if err != nil { return nil, err } serializedTxLen := uintBuf if serializedTxLen > wire.MaxBlockPayload { return nil, fmt.Errorf("Read serialized transaction "+ "length of %d is larger max allowed %d", serializedTxLen, wire.MaxBlockPayload) } // Transaction. var msgTx wire.MsgTx err = msgTx.Deserialize(r) if err != nil { return nil, err } txD.Tx = btcutil.NewTx(&msgTx) // Transaction hash. txHash, err := msgTx.TxSha() if err != nil { return nil, err } txD.Hash = &txHash // Block height the transaction came from. err = binary.Read(r, binary.LittleEndian, &uintBuf) if err != nil { return nil, err } txD.BlockHeight = int64(uintBuf) // Num spent bits. err = binary.Read(r, binary.LittleEndian, &uintBuf) if err != nil { return nil, err } numSpentBits := uintBuf numSpentBytes := numSpentBits / 8 if numSpentBits%8 != 0 { numSpentBytes++ } // Packed spent bytes. spentBytes := make([]byte, numSpentBytes) _, err = io.ReadFull(r, spentBytes) if err != nil { return nil, err } // Populate spent data based on spent bits. txD.Spent = make([]bool, numSpentBits) for byteNum, spentByte := range spentBytes { for bit := 0; bit < 8; bit++ { if uint32((byteNum*8)+bit) < numSpentBits { if spentByte&(1<<uint(bit)) != 0 { txD.Spent[(byteNum*8)+bit] = true } } } } txStore[*txD.Hash] = &txD } return txStore, nil }
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 != wire.BlockVersion { t.Fatalf("block version is not default: expected %v, got %v", wire.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) } }
// Ingest puts a tx into the DB atomically. This can result in a // gain, a loss, or no result. Gain or loss in satoshis is returned. func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { var hits uint32 var err error var spentOPs [][]byte var nUtxoBytes [][]byte // tx has been OK'd by SPV; check tx sanity utilTx := btcutil.NewTx(tx) // convert for validation // checks basic stuff like there are inputs and ouputs err = blockchain.CheckTransactionSanity(utilTx) if err != nil { return hits, err } // note that you can't check signatures; this is SPV. // 0 conf SPV means pretty much nothing. Anyone can say anything. // before entering into db, serialize all inputs of the ingested tx for _, txin := range tx.TxIn { nOP, err := outPointToBytes(&txin.PreviousOutPoint) if err != nil { return hits, err } spentOPs = append(spentOPs, nOP) } // also generate PKscripts for all addresses (maybe keep storing these?) for _, adr := range ts.Adrs { // iterate through all our addresses aPKscript, err := txscript.PayToAddrScript(adr.PkhAdr) if err != nil { return hits, err } // iterate through all outputs of this tx for i, out := range tx.TxOut { if bytes.Equal(out.PkScript, aPKscript) { // new utxo for us var newu Utxo newu.AtHeight = height newu.KeyIdx = adr.KeyIdx newu.Value = out.Value var newop wire.OutPoint newop.Hash = tx.TxSha() newop.Index = uint32(i) newu.Op = newop b, err := newu.ToBytes() if err != nil { return hits, err } nUtxoBytes = append(nUtxoBytes, b) hits++ break // only one match } } } err = ts.StateDB.Update(func(btx *bolt.Tx) error { // get all 4 buckets duf := btx.Bucket(BKTUtxos) // sta := btx.Bucket(BKTState) old := btx.Bucket(BKTStxos) txns := btx.Bucket(BKTTxns) if duf == nil || old == nil || txns == nil { return fmt.Errorf("error: db not initialized") } // first see if we lose utxos // iterate through duffel bag and look for matches // this makes us lose money, which is regrettable, but we need to know. for _, nOP := range spentOPs { duf.ForEach(func(k, v []byte) error { if bytes.Equal(k, nOP) { // matched, we lost utxo // do all this just to figure out value we lost x := make([]byte, len(k)+len(v)) copy(x, k) copy(x[len(k):], v) lostTxo, err := UtxoFromBytes(x) if err != nil { return err } hits++ // then delete the utxo from duf, save to old err = duf.Delete(k) if err != nil { return err } // after deletion, save stxo to old bucket var st Stxo // generate spent txo st.Utxo = lostTxo // assign outpoint st.SpendHeight = height // spent at height st.SpendTxid = tx.TxSha() // spent by txid stxb, err := st.ToBytes() // serialize if err != nil { return err } err = old.Put(k, stxb) // write k:v outpoint:stxo bytes if err != nil { return err } // store this relevant tx sha := tx.TxSha() var buf bytes.Buffer tx.Serialize(&buf) err = txns.Put(sha.Bytes(), buf.Bytes()) if err != nil { return err } return nil // matched utxo k, won't match another } return nil // no match }) } // done losing utxos, next gain utxos // next add all new utxos to db, this is quick as the work is above for _, ub := range nUtxoBytes { err = duf.Put(ub[:36], ub[36:]) if err != nil { return err } } return nil }) return hits, err }
// TestCheckTransactionStandard tests the checkTransactionStandard API. func TestCheckTransactionStandard(t *testing.T) { // Create some dummy, but otherwise standard, data for transactions. prevOutHash, err := wire.NewShaHashFromStr("01") if err != nil { t.Fatalf("NewShaHashFromStr: unexpected error: %v", err) } dummyPrevOut := wire.OutPoint{Hash: *prevOutHash, Index: 1} dummySigScript := bytes.Repeat([]byte{0x00}, 65) dummyTxIn := wire.TxIn{ PreviousOutPoint: dummyPrevOut, SignatureScript: dummySigScript, Sequence: wire.MaxTxInSequenceNum, } addrHash := [20]byte{0x01} addr, err := btcutil.NewAddressPubKeyHash(addrHash[:], &chaincfg.TestNet3Params) if err != nil { t.Fatalf("NewAddressPubKeyHash: unexpected error: %v", err) } dummyPkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("PayToAddrScript: unexpected error: %v", err) } dummyTxOut := wire.TxOut{ Value: 100000000, // 1 BTC PkScript: dummyPkScript, } tests := []struct { name string tx wire.MsgTx height int32 isStandard bool code wire.RejectCode }{ { name: "Typical pay-to-pubkey-hash transaction", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: true, }, { name: "Transaction version too high", tx: wire.MsgTx{ Version: wire.TxVersion + 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Transaction is not finalized", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: dummySigScript, Sequence: 0, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 300001, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Transaction size is too large", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: bytes.Repeat([]byte{0x00}, maxStandardTxSize+1), }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Signature script size is too large", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: bytes.Repeat([]byte{0x00}, maxStandardSigScriptSize+1), Sequence: wire.MaxTxInSequenceNum, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Signature script that does more than push data", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: []byte{ txscript.OP_CHECKSIGVERIFY}, Sequence: wire.MaxTxInSequenceNum, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Valid but non standard public key script", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 100000000, PkScript: []byte{txscript.OP_TRUE}, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "More than one nulldata output", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Dust output", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: dummyPkScript, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectDust, }, { name: "One nulldata output with 0 amount (standard)", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: []byte{txscript.OP_RETURN}, }}, LockTime: 0, }, height: 300000, isStandard: true, }, } timeSource := blockchain.NewMedianTime() for _, test := range tests { // Ensure standardness is as expected. err := checkTransactionStandard(btcutil.NewTx(&test.tx), test.height, timeSource, defaultMinRelayTxFee) if err == nil && test.isStandard { // Test passes since function returned standard for a // transaction which is intended to be standard. continue } if err == nil && !test.isStandard { t.Errorf("checkTransactionStandard (%s): standard when "+ "it should not be", test.name) continue } if err != nil && test.isStandard { t.Errorf("checkTransactionStandard (%s): nonstandard "+ "when it should not be: %v", test.name, err) continue } // Ensure error type is a TxRuleError inside of a RuleError. rerr, ok := err.(RuleError) if !ok { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error type - got %T", test.name, err) continue } txrerr, ok := rerr.Err.(TxRuleError) if !ok { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error type - got %T", test.name, rerr.Err) continue } // Ensure the reject code is the expected one. if txrerr.RejectCode != test.code { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error code - got %v, want %v", test.name, txrerr.RejectCode, test.code) continue } } }