blockchain: Use slices when fetching utxos

Maps have a higher overhead than slices.  As slices can be used instead
of maps, we avoid the overhead of making a map.
This commit is contained in:
Calvin Kim 2023-04-27 21:20:29 +09:00
parent 52ede324be
commit cfb39f790f
2 changed files with 31 additions and 27 deletions

View File

@ -503,7 +503,7 @@ func (view *UtxoViewpoint) commit() {
// Upon completion of this function, the view will contain an entry for each // Upon completion of this function, the view will contain an entry for each
// requested outpoint. Spent outputs, or those which otherwise don't exist, // requested outpoint. Spent outputs, or those which otherwise don't exist,
// will result in a nil entry in the view. // will result in a nil entry in the view.
func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints map[wire.OutPoint]struct{}) error { func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints []wire.OutPoint) error {
// Nothing to do if there are no requested outputs. // Nothing to do if there are no requested outputs.
if len(outpoints) == 0 { if len(outpoints) == 0 {
return nil return nil
@ -517,13 +517,13 @@ func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints map[wire.Out
// so other code can use the presence of an entry in the store as a way // so other code can use the presence of an entry in the store as a way
// to unnecessarily avoid attempting to reload it from the database. // to unnecessarily avoid attempting to reload it from the database.
return db.View(func(dbTx database.Tx) error { return db.View(func(dbTx database.Tx) error {
for outpoint := range outpoints { for i := range outpoints {
entry, err := dbFetchUtxoEntry(dbTx, outpoint) entry, err := dbFetchUtxoEntry(dbTx, outpoints[i])
if err != nil { if err != nil {
return err return err
} }
view.entries[outpoint] = entry view.entries[outpoints[i]] = entry
} }
return nil return nil
@ -533,25 +533,25 @@ func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints map[wire.Out
// fetchUtxos loads the unspent transaction outputs for the provided set of // fetchUtxos loads the unspent transaction outputs for the provided set of
// outputs into the view from the database as needed unless they already exist // outputs into the view from the database as needed unless they already exist
// in the view in which case they are ignored. // in the view in which case they are ignored.
func (view *UtxoViewpoint) fetchUtxos(db database.DB, outpoints map[wire.OutPoint]struct{}) error { func (view *UtxoViewpoint) fetchUtxos(db database.DB, outpoints []wire.OutPoint) error {
// Nothing to do if there are no requested outputs. // Nothing to do if there are no requested outputs.
if len(outpoints) == 0 { if len(outpoints) == 0 {
return nil return nil
} }
// Filter entries that are already in the view. // Filter entries that are already in the view.
neededSet := make(map[wire.OutPoint]struct{}) needed := make([]wire.OutPoint, 0, len(outpoints))
for outpoint := range outpoints { for i := range outpoints {
// Already loaded into the current view. // Already loaded into the current view.
if _, ok := view.entries[outpoint]; ok { if _, ok := view.entries[outpoints[i]]; ok {
continue continue
} }
neededSet[outpoint] = struct{}{} needed = append(needed, outpoints[i])
} }
// Request the input utxos from the database. // Request the input utxos from the database.
return view.fetchUtxosMain(db, neededSet) return view.fetchUtxosMain(db, needed)
} }
// fetchInputUtxos loads the unspent transaction outputs for the inputs // fetchInputUtxos loads the unspent transaction outputs for the inputs
@ -572,7 +572,7 @@ func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block)
// Loop through all of the transaction inputs (except for the coinbase // Loop through all of the transaction inputs (except for the coinbase
// which has no inputs) collecting them into sets of what is needed and // which has no inputs) collecting them into sets of what is needed and
// what is already known (in-flight). // what is already known (in-flight).
neededSet := make(map[wire.OutPoint]struct{}) needed := make([]wire.OutPoint, 0, len(transactions))
for i, tx := range transactions[1:] { for i, tx := range transactions[1:] {
for _, txIn := range tx.MsgTx().TxIn { for _, txIn := range tx.MsgTx().TxIn {
// It is acceptable for a transaction input to reference // It is acceptable for a transaction input to reference
@ -601,12 +601,12 @@ func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block)
continue continue
} }
neededSet[txIn.PreviousOutPoint] = struct{}{} needed = append(needed, txIn.PreviousOutPoint)
} }
} }
// Request the input utxos from the database. // Request the input utxos from the database.
return view.fetchUtxosMain(db, neededSet) return view.fetchUtxosMain(db, needed)
} }
// NewUtxoViewpoint returns a new empty unspent transaction output view. // NewUtxoViewpoint returns a new empty unspent transaction output view.
@ -626,15 +626,19 @@ func (b *BlockChain) FetchUtxoView(tx *btcutil.Tx) (*UtxoViewpoint, error) {
// Create a set of needed outputs based on those referenced by the // Create a set of needed outputs based on those referenced by the
// inputs of the passed transaction and the outputs of the transaction // inputs of the passed transaction and the outputs of the transaction
// itself. // itself.
neededSet := make(map[wire.OutPoint]struct{}) neededLen := len(tx.MsgTx().TxOut)
if !IsCoinBase(tx) {
neededLen += len(tx.MsgTx().TxIn)
}
needed := make([]wire.OutPoint, 0, neededLen)
prevOut := wire.OutPoint{Hash: *tx.Hash()} prevOut := wire.OutPoint{Hash: *tx.Hash()}
for txOutIdx := range tx.MsgTx().TxOut { for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx) prevOut.Index = uint32(txOutIdx)
neededSet[prevOut] = struct{}{} needed = append(needed, prevOut)
} }
if !IsCoinBase(tx) { if !IsCoinBase(tx) {
for _, txIn := range tx.MsgTx().TxIn { for _, txIn := range tx.MsgTx().TxIn {
neededSet[txIn.PreviousOutPoint] = struct{}{} needed = append(needed, txIn.PreviousOutPoint)
} }
} }
@ -642,7 +646,7 @@ func (b *BlockChain) FetchUtxoView(tx *btcutil.Tx) (*UtxoViewpoint, error) {
// chain. // chain.
view := NewUtxoViewpoint() view := NewUtxoViewpoint()
b.chainLock.RLock() b.chainLock.RLock()
err := view.fetchUtxosMain(b.db, neededSet) err := view.fetchUtxosMain(b.db, needed)
b.chainLock.RUnlock() b.chainLock.RUnlock()
return view, err return view, err
} }

View File

@ -302,8 +302,8 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
// target difficulty as claimed. // target difficulty as claimed.
// //
// The flags modify the behavior of this function as follows: // The flags modify the behavior of this function as follows:
// - BFNoPoWCheck: The check to ensure the block hash is less than the target // - BFNoPoWCheck: The check to ensure the block hash is less than the target
// difficulty is not performed. // difficulty is not performed.
func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error { func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error {
// The target difficulty must be larger than zero. // The target difficulty must be larger than zero.
target := CompactToBig(header.Bits) target := CompactToBig(header.Bits)
@ -637,8 +637,8 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int32) error {
// which depend on its position within the block chain. // which depend on its position within the block chain.
// //
// The flags modify the behavior of this function as follows: // The flags modify the behavior of this function as follows:
// - BFFastAdd: All checks except those involving comparing the header against // - BFFastAdd: All checks except those involving comparing the header against
// the checkpoints are not performed. // the checkpoints are not performed.
// //
// This function MUST be called with the chain state lock held (for writes). // This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error { func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error {
@ -716,8 +716,8 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
// on its position within the block chain. // on its position within the block chain.
// //
// The flags modify the behavior of this function as follows: // The flags modify the behavior of this function as follows:
// - BFFastAdd: The transaction are not checked to see if they are finalized // - BFFastAdd: The transaction are not checked to see if they are finalized
// and the somewhat expensive BIP0034 validation is not performed. // and the somewhat expensive BIP0034 validation is not performed.
// //
// The flags are also passed to checkBlockHeaderContext. See its documentation // The flags are also passed to checkBlockHeaderContext. See its documentation
// for how the flags modify its behavior. // for how the flags modify its behavior.
@ -832,22 +832,22 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block, view *UtxoViewpoint) error { func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block, view *UtxoViewpoint) error {
// Fetch utxos for all of the transaction ouputs in this block. // Fetch utxos for all of the transaction ouputs in this block.
// Typically, there will not be any utxos for any of the outputs. // Typically, there will not be any utxos for any of the outputs.
fetchSet := make(map[wire.OutPoint]struct{}) fetch := make([]wire.OutPoint, 0, len(block.Transactions()))
for _, tx := range block.Transactions() { for _, tx := range block.Transactions() {
prevOut := wire.OutPoint{Hash: *tx.Hash()} prevOut := wire.OutPoint{Hash: *tx.Hash()}
for txOutIdx := range tx.MsgTx().TxOut { for txOutIdx := range tx.MsgTx().TxOut {
prevOut.Index = uint32(txOutIdx) prevOut.Index = uint32(txOutIdx)
fetchSet[prevOut] = struct{}{} fetch = append(fetch, prevOut)
} }
} }
err := view.fetchUtxos(b.db, fetchSet) err := view.fetchUtxos(b.db, fetch)
if err != nil { if err != nil {
return err return err
} }
// Duplicate transactions are only allowed if the previous transaction // Duplicate transactions are only allowed if the previous transaction
// is fully spent. // is fully spent.
for outpoint := range fetchSet { for _, outpoint := range fetch {
utxo := view.LookupEntry(outpoint) utxo := view.LookupEntry(outpoint)
if utxo != nil && !utxo.IsSpent() { if utxo != nil && !utxo.IsSpent() {
str := fmt.Sprintf("tried to overwrite transaction %v "+ str := fmt.Sprintf("tried to overwrite transaction %v "+