Optimize rescan.
This change adds rescan fast paths for all current implementations of the btcutil.Address interface. Rather than encoding a payment address string from a txout script, the minimum number of bytes (which depends on the address type) are copied into a stack array and used as a lookup key to a map exclusive for that address type. This performs better than the previous implementation due to avoiding the base58 encoding and generating less garbage (so each GC cycle completes faster). If the address type switch falls into the default (unhandled) case, the encoded payment address is used as a fallback. This keeps the intended rescan behavior even when new implementations of btcutil.Address are added without a rescan fast path. Benchmarks indicate that when a fast path is followed, for 20 byte RIPEMD-160 hashes (such as P2PKH and P2SH), skipping the payment address encoding (and thus bypassing the base58 encoding code) can result in map lookups up to 60x faster.
This commit is contained in:
parent
9d6dd6fa6b
commit
de31674a8d
1 changed files with 122 additions and 10 deletions
130
rpcwebsocket.go
130
rpcwebsocket.go
|
@ -6,6 +6,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"code.google.com/p/go.crypto/ripemd160"
|
||||||
"code.google.com/p/go.net/websocket"
|
"code.google.com/p/go.net/websocket"
|
||||||
"container/list"
|
"container/list"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
@ -1431,10 +1432,24 @@ func handleNotifyNewTXs(wsc *wsClient, icmd btcjson.Cmd) (interface{}, *btcjson.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rescanKeys struct {
|
||||||
|
fallbacks map[string]struct{}
|
||||||
|
pubKeyHashes map[[ripemd160.Size]byte]struct{}
|
||||||
|
scriptHashes map[[ripemd160.Size]byte]struct{}
|
||||||
|
compressedPubkeys map[[33]byte]struct{}
|
||||||
|
uncompressedPubkeys map[[65]byte]struct{}
|
||||||
|
unspent map[btcwire.OutPoint]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
// rescanBlock rescans all transactions in a single block. This is a helper
|
// rescanBlock rescans all transactions in a single block. This is a helper
|
||||||
// function for handleRescan.
|
// function for handleRescan.
|
||||||
func rescanBlock(wsc *wsClient, cmd *btcws.RescanCmd, blk *btcutil.Block,
|
func rescanBlock(wsc *wsClient, lookups *rescanKeys, blk *btcutil.Block) {
|
||||||
unspent map[btcwire.OutPoint]struct{}) {
|
// Vars used for map keys. The item being checked is copied into
|
||||||
|
// these and then used as the lookup key. Saves on GC.
|
||||||
|
var ripemd160Hash [ripemd160.Size]byte
|
||||||
|
var compressedPubkey [33]byte
|
||||||
|
var uncompressedPubkey [65]byte
|
||||||
|
var outpoint btcwire.OutPoint
|
||||||
|
|
||||||
for _, tx := range blk.Transactions() {
|
for _, tx := range blk.Transactions() {
|
||||||
// Hexadecimal representation of this tx. Only created if
|
// Hexadecimal representation of this tx. Only created if
|
||||||
|
@ -1449,8 +1464,8 @@ func rescanBlock(wsc *wsClient, cmd *btcws.RescanCmd, blk *btcutil.Block,
|
||||||
recvNotified := false
|
recvNotified := false
|
||||||
|
|
||||||
for _, txin := range tx.MsgTx().TxIn {
|
for _, txin := range tx.MsgTx().TxIn {
|
||||||
if _, ok := unspent[txin.PreviousOutpoint]; ok {
|
if _, ok := lookups.unspent[txin.PreviousOutpoint]; ok {
|
||||||
delete(unspent, txin.PreviousOutpoint)
|
delete(lookups.unspent, txin.PreviousOutpoint)
|
||||||
|
|
||||||
if spentNotified {
|
if spentNotified {
|
||||||
continue
|
continue
|
||||||
|
@ -1480,12 +1495,53 @@ func rescanBlock(wsc *wsClient, cmd *btcws.RescanCmd, blk *btcutil.Block,
|
||||||
txout.PkScript, wsc.server.server.btcnet)
|
txout.PkScript, wsc.server.server.btcnet)
|
||||||
|
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
encodedAddr := addr.EncodeAddress()
|
switch a := addr.(type) {
|
||||||
if _, ok := cmd.Addresses[encodedAddr]; !ok {
|
case *btcutil.AddressPubKeyHash:
|
||||||
|
copy(ripemd160Hash[:], a.ScriptAddress())
|
||||||
|
if _, ok := lookups.pubKeyHashes[ripemd160Hash]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
unspent[*btcwire.NewOutPoint(tx.Sha(), uint32(txOutIdx))] = struct{}{}
|
case *btcutil.AddressScriptHash:
|
||||||
|
copy(ripemd160Hash[:], a.ScriptAddress())
|
||||||
|
if _, ok := lookups.scriptHashes[ripemd160Hash]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
case *btcutil.AddressPubKey:
|
||||||
|
pubkeyBytes := a.ScriptAddress()
|
||||||
|
switch len(pubkeyBytes) {
|
||||||
|
case 33: // Compressed
|
||||||
|
copy(compressedPubkey[:], pubkeyBytes)
|
||||||
|
_, ok := lookups.compressedPubkeys[compressedPubkey]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
case 65: // Uncompressed
|
||||||
|
copy(uncompressedPubkey[:], pubkeyBytes)
|
||||||
|
_, ok := lookups.uncompressedPubkeys[uncompressedPubkey]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
default: // wtf
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// A new address type must have been added. Encode as a
|
||||||
|
// payment address string and check the fallback map.
|
||||||
|
addrStr := addr.EncodeAddress()
|
||||||
|
_, ok := lookups.fallbacks[addrStr]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(outpoint.Hash[:], tx.Sha()[:])
|
||||||
|
outpoint.Index = uint32(txOutIdx)
|
||||||
|
lookups.unspent[outpoint] = struct{}{}
|
||||||
|
|
||||||
if recvNotified {
|
if recvNotified {
|
||||||
continue
|
continue
|
||||||
|
@ -1529,9 +1585,65 @@ func handleRescan(wsc *wsClient, icmd btcjson.Cmd) (interface{}, *btcjson.Error)
|
||||||
rpcsLog.Infof("Beginning rescan for %d addresses", numAddrs)
|
rpcsLog.Infof("Beginning rescan for %d addresses", numAddrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build lookup maps.
|
||||||
|
lookups := rescanKeys{
|
||||||
|
fallbacks: map[string]struct{}{},
|
||||||
|
pubKeyHashes: map[[ripemd160.Size]byte]struct{}{},
|
||||||
|
scriptHashes: map[[ripemd160.Size]byte]struct{}{},
|
||||||
|
compressedPubkeys: map[[33]byte]struct{}{},
|
||||||
|
uncompressedPubkeys: map[[65]byte]struct{}{},
|
||||||
|
unspent: map[btcwire.OutPoint]struct{}{},
|
||||||
|
}
|
||||||
|
var ripemd160Hash [ripemd160.Size]byte
|
||||||
|
var compressedPubkey [33]byte
|
||||||
|
var uncompressedPubkey [65]byte
|
||||||
|
for addrStr := range cmd.Addresses {
|
||||||
|
addr, err := btcutil.DecodeAddress(addrStr, activeNetParams.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
jsonErr := btcjson.Error{
|
||||||
|
Code: btcjson.ErrInvalidAddressOrKey.Code,
|
||||||
|
Message: "Rescan address " + addrStr + ": " + err.Error(),
|
||||||
|
}
|
||||||
|
return nil, &jsonErr
|
||||||
|
}
|
||||||
|
switch a := addr.(type) {
|
||||||
|
case *btcutil.AddressPubKeyHash:
|
||||||
|
copy(ripemd160Hash[:], a.ScriptAddress())
|
||||||
|
lookups.pubKeyHashes[ripemd160Hash] = struct{}{}
|
||||||
|
|
||||||
|
case *btcutil.AddressScriptHash:
|
||||||
|
copy(ripemd160Hash[:], a.ScriptAddress())
|
||||||
|
lookups.scriptHashes[ripemd160Hash] = struct{}{}
|
||||||
|
|
||||||
|
case *btcutil.AddressPubKey:
|
||||||
|
pubkeyBytes := a.ScriptAddress()
|
||||||
|
switch len(pubkeyBytes) {
|
||||||
|
case 33: // Compressed
|
||||||
|
copy(compressedPubkey[:], pubkeyBytes)
|
||||||
|
lookups.compressedPubkeys[compressedPubkey] = struct{}{}
|
||||||
|
|
||||||
|
case 65: // Uncompressed
|
||||||
|
copy(uncompressedPubkey[:], pubkeyBytes)
|
||||||
|
lookups.uncompressedPubkeys[uncompressedPubkey] = struct{}{}
|
||||||
|
|
||||||
|
default:
|
||||||
|
jsonErr := btcjson.Error{
|
||||||
|
Code: btcjson.ErrInvalidAddressOrKey.Code,
|
||||||
|
Message: "Pubkey " + addrStr + " is of unknown length",
|
||||||
|
}
|
||||||
|
return nil, &jsonErr
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// A new address type must have been added. Use encoded
|
||||||
|
// payment address string as a fallback until a fast path
|
||||||
|
// is added.
|
||||||
|
lookups.fallbacks[addrStr] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
minBlock := int64(cmd.BeginBlock)
|
minBlock := int64(cmd.BeginBlock)
|
||||||
maxBlock := int64(cmd.EndBlock)
|
maxBlock := int64(cmd.EndBlock)
|
||||||
unspent := make(map[btcwire.OutPoint]struct{})
|
|
||||||
|
|
||||||
// FetchHeightRange may not return a complete list of block shas for
|
// FetchHeightRange may not return a complete list of block shas for
|
||||||
// the given range, so fetch range as many times as necessary.
|
// the given range, so fetch range as many times as necessary.
|
||||||
|
@ -1561,7 +1673,7 @@ func handleRescan(wsc *wsClient, icmd btcjson.Cmd) (interface{}, *btcjson.Error)
|
||||||
blk.Height())
|
blk.Height())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
default:
|
default:
|
||||||
rescanBlock(wsc, cmd, blk, unspent)
|
rescanBlock(wsc, &lookups, blk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue