diff --git a/blockchain/chain.go b/blockchain/chain.go index b5a8ea54..c88bea6d 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -557,7 +557,9 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List // it would be inefficient to repeat it. // // This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos []spentTxOut) error { +func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, + view *UtxoViewpoint, stxos []SpentTxOut) error { + // Make sure it's extending the end of the best chain. prevHash := &block.MsgBlock().Header.PrevBlock if !prevHash.IsEqual(&b.bestChain.Tip().hash) { @@ -816,7 +818,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // then they are needed again when doing the actual database updates. // Rather than doing two loads, cache the loaded data into these slices. detachBlocks := make([]*btcutil.Block, 0, detachNodes.Len()) - detachSpentTxOuts := make([][]spentTxOut, 0, detachNodes.Len()) + detachSpentTxOuts := make([][]SpentTxOut, 0, detachNodes.Len()) attachBlocks := make([]*btcutil.Block, 0, attachNodes.Len()) // Disconnect all of the blocks back to the point of the fork. This @@ -846,7 +848,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // Load all of the spent txos for the block from the spend // journal. - var stxos []spentTxOut + var stxos []SpentTxOut err = b.db.View(func(dbTx database.Tx) error { stxos, err = dbFetchSpendJournalEntry(dbTx, block) return err @@ -990,7 +992,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // as spent and add all transactions being created by this block // to it. Also, provide an stxo slice so the spent txout // details are generated. - stxos := make([]spentTxOut, 0, countSpentOutputs(block)) + stxos := make([]SpentTxOut, 0, countSpentOutputs(block)) err = view.connectTransactions(block, &stxos) if err != nil { return err @@ -1044,7 +1046,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla // actually connecting the block. view := NewUtxoViewpoint() view.SetBestHash(parentHash) - stxos := make([]spentTxOut, 0, countSpentOutputs(block)) + stxos := make([]SpentTxOut, 0, countSpentOutputs(block)) if !fastAdd { err := b.checkConnectBlock(node, block, view, &stxos) if err == nil { diff --git a/blockchain/chainio.go b/blockchain/chainio.go index 84cd9b77..8bd56276 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -232,18 +232,25 @@ func dbFetchOrCreateVersion(dbTx database.Tx, key []byte, defaultVersion uint32) // - 0xb2...ec: pubkey hash // ----------------------------------------------------------------------------- -// spentTxOut contains a spent transaction output and potentially additional +// SpentTxOut contains a spent transaction output and potentially additional // contextual information such as whether or not it was contained in a coinbase // transaction, the version of the transaction it was contained in, and which // block height the containing transaction was included in. As described in // the comments above, the additional contextual information will only be valid // when this spent txout is spending the last unspent output of the containing // transaction. -type spentTxOut struct { - amount int64 // The amount of the output. - pkScript []byte // The public key script for the output. - height int32 // Height of the the block containing the creating tx. - isCoinBase bool // Whether creating tx is a coinbase. +type SpentTxOut struct { + // Amount is the amount of the output. + Amount int64 + + // PkScipt is the the public key script for the output. + PkScript []byte + + // Height is the height of the the block containing the creating tx. + Height int32 + + // Denotes if the creating tx is a coinbase. + IsCoinBase bool } // FetchSpendJournal attempts to retrieve the spend journal, or the set of @@ -272,12 +279,12 @@ func (b *BlockChain) FetchSpendJournal(targetBlock *btcutil.Block) ([]SpentTxOut // spentTxOutHeaderCode returns the calculated header code to be used when // serializing the provided stxo entry. -func spentTxOutHeaderCode(stxo *spentTxOut) uint64 { +func spentTxOutHeaderCode(stxo *SpentTxOut) uint64 { // As described in the serialization format comments, the header code // encodes the height shifted over one bit and the coinbase flag in the // lowest bit. - headerCode := uint64(stxo.height) << 1 - if stxo.isCoinBase { + headerCode := uint64(stxo.Height) << 1 + if stxo.IsCoinBase { headerCode |= 0x01 } @@ -286,38 +293,38 @@ func spentTxOutHeaderCode(stxo *spentTxOut) uint64 { // spentTxOutSerializeSize returns the number of bytes it would take to // serialize the passed stxo according to the format described above. -func spentTxOutSerializeSize(stxo *spentTxOut) int { +func spentTxOutSerializeSize(stxo *SpentTxOut) int { size := serializeSizeVLQ(spentTxOutHeaderCode(stxo)) - if stxo.height > 0 { + if stxo.Height > 0 { // The legacy v1 spend journal format conditionally tracked the // containing transaction version when the height was non-zero, // so this is required for backwards compat. size += serializeSizeVLQ(0) } - return size + compressedTxOutSize(uint64(stxo.amount), stxo.pkScript) + return size + compressedTxOutSize(uint64(stxo.Amount), stxo.PkScript) } // putSpentTxOut serializes the passed stxo according to the format described // above directly into the passed target byte slice. The target byte slice must // be at least large enough to handle the number of bytes returned by the -// spentTxOutSerializeSize function or it will panic. -func putSpentTxOut(target []byte, stxo *spentTxOut) int { +// SpentTxOutSerializeSize function or it will panic. +func putSpentTxOut(target []byte, stxo *SpentTxOut) int { headerCode := spentTxOutHeaderCode(stxo) offset := putVLQ(target, headerCode) - if stxo.height > 0 { + if stxo.Height > 0 { // The legacy v1 spend journal format conditionally tracked the // containing transaction version when the height was non-zero, // so this is required for backwards compat. offset += putVLQ(target[offset:], 0) } - return offset + putCompressedTxOut(target[offset:], uint64(stxo.amount), - stxo.pkScript) + return offset + putCompressedTxOut(target[offset:], uint64(stxo.Amount), + stxo.PkScript) } // decodeSpentTxOut decodes the passed serialized stxo entry, possibly followed // by other data, into the passed stxo struct. It returns the number of bytes // read. -func decodeSpentTxOut(serialized []byte, stxo *spentTxOut) (int, error) { +func decodeSpentTxOut(serialized []byte, stxo *SpentTxOut) (int, error) { // Ensure there are bytes to decode. if len(serialized) == 0 { return 0, errDeserialize("no serialized bytes") @@ -334,9 +341,9 @@ func decodeSpentTxOut(serialized []byte, stxo *spentTxOut) (int, error) { // // Bit 0 indicates containing transaction is a coinbase. // Bits 1-x encode height of containing transaction. - stxo.isCoinBase = code&0x01 != 0 - stxo.height = int32(code >> 1) - if stxo.height > 0 { + stxo.IsCoinBase = code&0x01 != 0 + stxo.Height = int32(code >> 1) + if stxo.Height > 0 { // The legacy v1 spend journal format conditionally tracked the // containing transaction version when the height was non-zero, // so this is required for backwards compat. @@ -356,8 +363,8 @@ func decodeSpentTxOut(serialized []byte, stxo *spentTxOut) (int, error) { return offset, errDeserialize(fmt.Sprintf("unable to decode "+ "txout: %v", err)) } - stxo.amount = int64(amount) - stxo.pkScript = pkScript + stxo.Amount = int64(amount) + stxo.PkScript = pkScript return offset, nil } @@ -367,7 +374,7 @@ func decodeSpentTxOut(serialized []byte, stxo *spentTxOut) (int, error) { // Since the serialization format is not self describing, as noted in the // format comments, this function also requires the transactions that spend the // txouts. -func deserializeSpendJournalEntry(serialized []byte, txns []*wire.MsgTx) ([]spentTxOut, error) { +func deserializeSpendJournalEntry(serialized []byte, txns []*wire.MsgTx) ([]SpentTxOut, error) { // Calculate the total number of stxos. var numStxos int for _, tx := range txns { @@ -392,7 +399,7 @@ func deserializeSpendJournalEntry(serialized []byte, txns []*wire.MsgTx) ([]spen // reverse order to match the serialization order. stxoIdx := numStxos - 1 offset := 0 - stxos := make([]spentTxOut, numStxos) + stxos := make([]SpentTxOut, numStxos) for txIdx := len(txns) - 1; txIdx > -1; txIdx-- { tx := txns[txIdx] @@ -418,7 +425,7 @@ func deserializeSpendJournalEntry(serialized []byte, txns []*wire.MsgTx) ([]spen // serializeSpendJournalEntry serializes all of the passed spent txouts into a // single byte slice according to the format described in detail above. -func serializeSpendJournalEntry(stxos []spentTxOut) []byte { +func serializeSpendJournalEntry(stxos []SpentTxOut) []byte { if len(stxos) == 0 { return nil } @@ -446,7 +453,7 @@ func serializeSpendJournalEntry(stxos []spentTxOut) []byte { // NOTE: Legacy entries will not have the coinbase flag or height set unless it // was the final output spend in the containing transaction. It is up to the // caller to handle this properly by looking the information up in the utxo set. -func dbFetchSpendJournalEntry(dbTx database.Tx, block *btcutil.Block) ([]spentTxOut, error) { +func dbFetchSpendJournalEntry(dbTx database.Tx, block *btcutil.Block) ([]SpentTxOut, error) { // Exclude the coinbase transaction since it can't spend anything. spendBucket := dbTx.Metadata().Bucket(spendJournalBucketName) serialized := spendBucket.Get(block.Hash()[:]) @@ -474,7 +481,7 @@ func dbFetchSpendJournalEntry(dbTx database.Tx, block *btcutil.Block) ([]spentTx // spend journal entry for the given block hash using the provided slice of // spent txouts. The spent txouts slice must contain an entry for every txout // the transactions in the block spend in the order they are spent. -func dbPutSpendJournalEntry(dbTx database.Tx, blockHash *chainhash.Hash, stxos []spentTxOut) error { +func dbPutSpendJournalEntry(dbTx database.Tx, blockHash *chainhash.Hash, stxos []SpentTxOut) error { spendBucket := dbTx.Metadata().Bucket(spendJournalBucketName) serialized := serializeSpendJournalEntry(stxos) return spendBucket.Put(blockHash[:], serialized) diff --git a/blockchain/chainio_test.go b/blockchain/chainio_test.go index f62a39d2..630af14e 100644 --- a/blockchain/chainio_test.go +++ b/blockchain/chainio_test.go @@ -44,37 +44,37 @@ func TestStxoSerialization(t *testing.T) { tests := []struct { name string - stxo spentTxOut + stxo SpentTxOut serialized []byte }{ // From block 170 in main blockchain. { name: "Spends last output of coinbase", - stxo: spentTxOut{ - amount: 5000000000, - pkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"), - isCoinBase: true, - height: 9, + stxo: SpentTxOut{ + Amount: 5000000000, + PkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"), + IsCoinBase: true, + Height: 9, }, serialized: hexToBytes("1300320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"), }, // Adapted from block 100025 in main blockchain. { name: "Spends last output of non coinbase", - stxo: spentTxOut{ - amount: 13761000000, - pkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"), - isCoinBase: false, - height: 100024, + stxo: SpentTxOut{ + Amount: 13761000000, + PkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"), + IsCoinBase: false, + Height: 100024, }, serialized: hexToBytes("8b99700086c64700b2fb57eadf61e106a100a7445a8c3f67898841ec"), }, // Adapted from block 100025 in main blockchain. { name: "Does not spend last output, legacy format", - stxo: spentTxOut{ - amount: 34405000000, - pkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"), + stxo: SpentTxOut{ + Amount: 34405000000, + PkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"), }, serialized: hexToBytes("0091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"), }, @@ -85,7 +85,7 @@ func TestStxoSerialization(t *testing.T) { // actually serializing it is calculated properly. gotSize := spentTxOutSerializeSize(&test.stxo) if gotSize != len(test.serialized) { - t.Errorf("spentTxOutSerializeSize (%s): did not get "+ + t.Errorf("SpentTxOutSerializeSize (%s): did not get "+ "expected size - got %d, want %d", test.name, gotSize, len(test.serialized)) continue @@ -110,7 +110,7 @@ func TestStxoSerialization(t *testing.T) { // Ensure the serialized bytes are decoded back to the expected // stxo. - var gotStxo spentTxOut + var gotStxo SpentTxOut gotBytesRead, err := decodeSpentTxOut(test.serialized, &gotStxo) if err != nil { t.Errorf("decodeSpentTxOut (%s): unexpected error: %v", @@ -138,42 +138,42 @@ func TestStxoDecodeErrors(t *testing.T) { tests := []struct { name string - stxo spentTxOut + stxo SpentTxOut serialized []byte bytesRead int // Expected number of bytes read. errType error }{ { name: "nothing serialized", - stxo: spentTxOut{}, + stxo: SpentTxOut{}, serialized: hexToBytes(""), errType: errDeserialize(""), bytesRead: 0, }, { name: "no data after header code w/o reserved", - stxo: spentTxOut{}, + stxo: SpentTxOut{}, serialized: hexToBytes("00"), errType: errDeserialize(""), bytesRead: 1, }, { name: "no data after header code with reserved", - stxo: spentTxOut{}, + stxo: SpentTxOut{}, serialized: hexToBytes("13"), errType: errDeserialize(""), bytesRead: 1, }, { name: "no data after reserved", - stxo: spentTxOut{}, + stxo: SpentTxOut{}, serialized: hexToBytes("1300"), errType: errDeserialize(""), bytesRead: 2, }, { name: "incomplete compressed txout", - stxo: spentTxOut{}, + stxo: SpentTxOut{}, serialized: hexToBytes("1332"), errType: errDeserialize(""), bytesRead: 2, @@ -208,7 +208,7 @@ func TestSpendJournalSerialization(t *testing.T) { tests := []struct { name string - entry []spentTxOut + entry []SpentTxOut blockTxns []*wire.MsgTx serialized []byte }{ @@ -222,11 +222,11 @@ func TestSpendJournalSerialization(t *testing.T) { // From block 170 in main blockchain. { name: "One tx with one input spends last output of coinbase", - entry: []spentTxOut{{ - amount: 5000000000, - pkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"), - isCoinBase: true, - height: 9, + entry: []SpentTxOut{{ + Amount: 5000000000, + PkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"), + IsCoinBase: true, + Height: 9, }}, blockTxns: []*wire.MsgTx{{ // Coinbase omitted. Version: 1, @@ -252,16 +252,16 @@ func TestSpendJournalSerialization(t *testing.T) { // Adapted from block 100025 in main blockchain. { name: "Two txns when one spends last output, one doesn't", - entry: []spentTxOut{{ - amount: 34405000000, - pkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"), - isCoinBase: false, - height: 100024, + entry: []SpentTxOut{{ + Amount: 34405000000, + PkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"), + IsCoinBase: false, + Height: 100024, }, { - amount: 13761000000, - pkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"), - isCoinBase: false, - height: 100024, + Amount: 13761000000, + PkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"), + IsCoinBase: false, + Height: 100024, }}, blockTxns: []*wire.MsgTx{{ // Coinbase omitted. Version: 1, diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index 5af84234..08005d27 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -216,7 +216,7 @@ func (view *UtxoViewpoint) AddTxOuts(tx *btcutil.Tx, blockHeight int32) { // spent. In addition, when the 'stxos' argument is not nil, it will be updated // to append an entry for each spent txout. An error will be returned if the // view does not contain the required utxos. -func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, stxos *[]spentTxOut) error { +func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, stxos *[]SpentTxOut) error { // Coinbase transactions don't have any inputs to spend. if IsCoinBase(tx) { // Add the transaction's outputs as available utxos. @@ -239,11 +239,11 @@ func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, // Only create the stxo details if requested. if stxos != nil { // Populate the stxo details using the utxo entry. - var stxo = spentTxOut{ - amount: entry.Amount(), - pkScript: entry.PkScript(), - height: entry.BlockHeight(), - isCoinBase: entry.IsCoinBase(), + var stxo = SpentTxOut{ + Amount: entry.Amount(), + PkScript: entry.PkScript(), + Height: entry.BlockHeight(), + IsCoinBase: entry.IsCoinBase(), } *stxos = append(*stxos, stxo) } @@ -264,7 +264,7 @@ func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, // spend as spent, and setting the best hash for the view to the passed block. // In addition, when the 'stxos' argument is not nil, it will be updated to // append an entry for each spent txout. -func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]spentTxOut) error { +func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]SpentTxOut) error { for _, tx := range block.Transactions() { err := view.connectTransaction(tx, block.Height(), stxos) if err != nil { @@ -308,7 +308,7 @@ func (view *UtxoViewpoint) fetchEntryByHash(db database.DB, hash *chainhash.Hash // created by the passed block, restoring all utxos the transactions spent by // using the provided spent txo information, and setting the best hash for the // view to the block before the passed block. -func (view *UtxoViewpoint) disconnectTransactions(db database.DB, block *btcutil.Block, stxos []spentTxOut) error { +func (view *UtxoViewpoint) disconnectTransactions(db database.DB, block *btcutil.Block, stxos []SpentTxOut) error { // Sanity check the correct number of stxos are provided. if len(stxos) != countSpentOutputs(block) { return AssertError("disconnectTransactions called with bad " + @@ -405,7 +405,7 @@ func (view *UtxoViewpoint) disconnectTransactions(db database.DB, block *btcutil // connected. In the case of a fresh database that has // only ever run with the new v2 format, this code path // will never run. - if stxo.height == 0 { + if stxo.Height == 0 { utxo, err := view.fetchEntryByHash(db, txHash) if err != nil { return err @@ -416,17 +416,17 @@ func (view *UtxoViewpoint) disconnectTransactions(db database.DB, block *btcutil *originOut)) } - stxo.height = utxo.BlockHeight() - stxo.isCoinBase = utxo.IsCoinBase() + stxo.Height = utxo.BlockHeight() + stxo.IsCoinBase = utxo.IsCoinBase() } // Restore the utxo using the stxo data from the spend // journal and mark it as modified. - entry.amount = stxo.amount - entry.pkScript = stxo.pkScript - entry.blockHeight = stxo.height + entry.amount = stxo.Amount + entry.pkScript = stxo.PkScript + entry.blockHeight = stxo.Height entry.packedFlags = tfModified - if stxo.isCoinBase { + if stxo.IsCoinBase { entry.packedFlags |= tfCoinBase } } diff --git a/blockchain/validate.go b/blockchain/validate.go index 48e8b294..b971337f 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -986,7 +986,7 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpo // with that node. // // This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos *[]spentTxOut) error { +func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos *[]SpentTxOut) error { // If the side chain blocks end up in the database, a call to // CheckBlockSanity should be done here in case a previous version // allowed a block that is no longer valid. However, since the