kaspad/server/rpc/handle_search_raw_transactions.go
stasatdaglabs 40342eb45a [NOD-275] Split rpcserver.go to separate files (#417)
* [NOD-275] Moved getBlockTemplate and related functionality to a separate file.

* [NOD-275] Started moving handlers to separate files.

* [NOD-275] Fixed merge errors.

* [NOD-275] Moved all handlers out of rpcserver.go.

* [NOD-275] Moved non-shared functions out of rpcserver.go.

* [NOD-275] Moved handleGetAllManualNodesInfo to a separate file.

* [NOD-275] Moved handlers out of rpcwebsocket.go to separate files.

* [NOD-275] Fixed import error.

* [NOD-275] Renamed all handler files to include underscores.

* [NOD-275] Moved common rpc helper functions to common.go.
2019-09-22 16:41:37 +03:00

475 lines
15 KiB
Go

package rpc
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/daglabs/btcd/btcjson"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/database"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/util/daghash"
"github.com/daglabs/btcd/wire"
)
// retrievedTx represents a transaction that was either loaded from the
// transaction memory pool or from the database. When a transaction is loaded
// from the database, it is loaded with the raw serialized bytes while the
// mempool has the fully deserialized structure. This structure therefore will
// have one of the two fields set depending on where is was retrieved from.
// This is mainly done for efficiency to avoid extra serialization steps when
// possible.
type retrievedTx struct {
txBytes []byte
blkHash *daghash.Hash // Only set when transaction is in a block.
tx *util.Tx
}
// handleSearchRawTransactions implements the searchRawTransactions command.
func handleSearchRawTransactions(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
// Respond with an error if the address index is not enabled.
addrIndex := s.cfg.AddrIndex
if addrIndex == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCMisc,
Message: "Address index must be enabled (--addrindex)",
}
}
// Override the flag for including extra previous output information in
// each input if needed.
c := cmd.(*btcjson.SearchRawTransactionsCmd)
vinExtra := false
if c.VinExtra != nil {
vinExtra = *c.VinExtra
}
// Including the extra previous output information requires the
// transaction index. Currently the address index relies on the
// transaction index, so this check is redundant, but it's better to be
// safe in case the address index is ever changed to not rely on it.
if vinExtra && s.cfg.TxIndex == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCMisc,
Message: "Transaction index must be enabled (--txindex)",
}
}
// Attempt to decode the supplied address.
params := s.cfg.DAGParams
addr, err := util.DecodeAddress(c.Address, params.Prefix)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidAddressOrKey,
Message: "Invalid address or key: " + err.Error(),
}
}
// Override the default number of requested entries if needed. Also,
// just return now if the number of requested entries is zero to avoid
// extra work.
numRequested := 100
if c.Count != nil {
numRequested = *c.Count
if numRequested < 0 {
numRequested = 1
}
}
if numRequested == 0 {
return nil, nil
}
// Override the default number of entries to skip if needed.
var numToSkip int
if c.Skip != nil {
numToSkip = *c.Skip
if numToSkip < 0 {
numToSkip = 0
}
}
// Override the reverse flag if needed.
var reverse bool
if c.Reverse != nil {
reverse = *c.Reverse
}
// Add transactions from mempool first if client asked for reverse
// order. Otherwise, they will be added last (as needed depending on
// the requested counts).
//
// NOTE: This code doesn't sort by dependency. This might be something
// to do in the future for the client's convenience, or leave it to the
// client.
numSkipped := uint32(0)
addressTxns := make([]retrievedTx, 0, numRequested)
if reverse {
// Transactions in the mempool are not in a block header yet,
// so the block header field in the retieved transaction struct
// is left nil.
mpTxns, mpSkipped := fetchMempoolTxnsForAddress(s, addr,
uint32(numToSkip), uint32(numRequested))
numSkipped += mpSkipped
for _, tx := range mpTxns {
addressTxns = append(addressTxns, retrievedTx{tx: tx})
}
}
// Fetch transactions from the database in the desired order if more are
// needed.
if len(addressTxns) < numRequested {
err = s.cfg.DB.View(func(dbTx database.Tx) error {
regions, dbSkipped, err := addrIndex.TxRegionsForAddress(
dbTx, addr, uint32(numToSkip)-numSkipped,
uint32(numRequested-len(addressTxns)), reverse)
if err != nil {
return err
}
// Load the raw transaction bytes from the database.
serializedTxns, err := dbTx.FetchBlockRegions(regions)
if err != nil {
return err
}
// Add the transaction and the hash of the block it is
// contained in to the list. Note that the transaction
// is left serialized here since the caller might have
// requested non-verbose output and hence there would be
// no point in deserializing it just to reserialize it
// later.
for i, serializedTx := range serializedTxns {
addressTxns = append(addressTxns, retrievedTx{
txBytes: serializedTx,
blkHash: regions[i].Hash,
})
}
numSkipped += dbSkipped
return nil
})
if err != nil {
context := "Failed to load address index entries"
return nil, internalRPCError(err.Error(), context)
}
}
// Add transactions from mempool last if client did not request reverse
// order and the number of results is still under the number requested.
if !reverse && len(addressTxns) < numRequested {
// Transactions in the mempool are not in a block header yet,
// so the block header field in the retieved transaction struct
// is left nil.
mpTxns, mpSkipped := fetchMempoolTxnsForAddress(s, addr,
uint32(numToSkip)-numSkipped, uint32(numRequested-
len(addressTxns)))
numSkipped += mpSkipped
for _, tx := range mpTxns {
addressTxns = append(addressTxns, retrievedTx{tx: tx})
}
}
// Address has never been used if neither source yielded any results.
if len(addressTxns) == 0 {
return []btcjson.SearchRawTransactionsResult{}, nil
}
// Serialize all of the transactions to hex.
hexTxns := make([]string, len(addressTxns))
for i := range addressTxns {
// Simply encode the raw bytes to hex when the retrieved
// transaction is already in serialized form.
rtx := &addressTxns[i]
if rtx.txBytes != nil {
hexTxns[i] = hex.EncodeToString(rtx.txBytes)
continue
}
// Serialize the transaction first and convert to hex when the
// retrieved transaction is the deserialized structure.
hexTxns[i], err = messageToHex(rtx.tx.MsgTx())
if err != nil {
return nil, err
}
}
// When not in verbose mode, simply return a list of serialized txns.
if c.Verbose != nil && !*c.Verbose {
return hexTxns, nil
}
// Normalize the provided filter addresses (if any) to ensure there are
// no duplicates.
filterAddrMap := make(map[string]struct{})
if c.FilterAddrs != nil && len(*c.FilterAddrs) > 0 {
for _, addr := range *c.FilterAddrs {
filterAddrMap[addr] = struct{}{}
}
}
// The verbose flag is set, so generate the JSON object and return it.
srtList := make([]btcjson.SearchRawTransactionsResult, len(addressTxns))
for i := range addressTxns {
// The deserialized transaction is needed, so deserialize the
// retrieved transaction if it's in serialized form (which will
// be the case when it was lookup up from the database).
// Otherwise, use the existing deserialized transaction.
rtx := &addressTxns[i]
var mtx *wire.MsgTx
if rtx.tx == nil {
// Deserialize the transaction.
mtx = new(wire.MsgTx)
err := mtx.Deserialize(bytes.NewReader(rtx.txBytes))
if err != nil {
context := "Failed to deserialize transaction"
return nil, internalRPCError(err.Error(),
context)
}
} else {
mtx = rtx.tx.MsgTx()
}
result := &srtList[i]
result.Hex = hexTxns[i]
result.TxID = mtx.TxID().String()
result.Vin, err = createVinListPrevOut(s, mtx, params, vinExtra,
filterAddrMap)
if err != nil {
return nil, err
}
result.Vout = createVoutList(mtx, params, filterAddrMap)
result.Version = mtx.Version
result.LockTime = mtx.LockTime
// Transactions grabbed from the mempool aren't yet in a block,
// so conditionally fetch block details here. This will be
// reflected in the final JSON output (mempool won't have
// confirmations or block information).
var blkHeader *wire.BlockHeader
var blkHashStr string
if blkHash := rtx.blkHash; blkHash != nil {
// Fetch the header from chain.
header, err := s.cfg.DAG.HeaderByHash(blkHash)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCBlockNotFound,
Message: "Block not found",
}
}
blkHeader = header
blkHashStr = blkHash.String()
}
// Add the block information to the result if there is any.
if blkHeader != nil {
// This is not a typo, they are identical in Bitcoin
// Core as well.
result.Time = uint64(blkHeader.Timestamp.Unix())
result.Blocktime = uint64(blkHeader.Timestamp.Unix())
result.BlockHash = blkHashStr
}
// rtx.tx is only set when the transaction was retrieved from the mempool
result.IsInMempool = rtx.tx != nil
if s.cfg.TxIndex != nil && !result.IsInMempool {
confirmations, err := txConfirmations(s, mtx.TxID())
if err != nil {
context := "Failed to obtain block confirmations"
return nil, internalRPCError(err.Error(), context)
}
result.Confirmations = &confirmations
}
}
return srtList, nil
}
// createVinListPrevOut returns a slice of JSON objects for the inputs of the
// passed transaction.
func createVinListPrevOut(s *Server, mtx *wire.MsgTx, chainParams *dagconfig.Params, vinExtra bool, filterAddrMap map[string]struct{}) ([]btcjson.VinPrevOut, error) {
// Use a dynamically sized list to accommodate the address filter.
vinList := make([]btcjson.VinPrevOut, 0, len(mtx.TxIn))
// Lookup all of the referenced transaction outputs needed to populate the
// previous output information if requested. Coinbase transactions do not contain
// valid inputs: block hash instead of transaction ID.
var originOutputs map[wire.Outpoint]wire.TxOut
if !mtx.IsCoinBase() && (vinExtra || len(filterAddrMap) > 0) {
var err error
originOutputs, err = fetchInputTxos(s, mtx)
if err != nil {
return nil, err
}
}
for _, txIn := range mtx.TxIn {
// The disassembled string will contain [error] inline
// if the script doesn't fully parse, so ignore the
// error here.
disbuf, _ := txscript.DisasmString(txIn.SignatureScript)
// Create the basic input entry without the additional optional
// previous output details which will be added later if
// requested and available.
prevOut := &txIn.PreviousOutpoint
vinEntry := btcjson.VinPrevOut{
TxID: prevOut.TxID.String(),
Vout: prevOut.Index,
Sequence: txIn.Sequence,
ScriptSig: &btcjson.ScriptSig{
Asm: disbuf,
Hex: hex.EncodeToString(txIn.SignatureScript),
},
}
// Add the entry to the list now if it already passed the filter
// since the previous output might not be available.
passesFilter := len(filterAddrMap) == 0
if passesFilter {
vinList = append(vinList, vinEntry)
}
// Only populate previous output information if requested and
// available.
if len(originOutputs) == 0 {
continue
}
originTxOut, ok := originOutputs[*prevOut]
if !ok {
continue
}
// Ignore the error here since an error means the script
// couldn't parse and there is no additional information about
// it anyways.
_, addr, _ := txscript.ExtractScriptPubKeyAddress(
originTxOut.ScriptPubKey, chainParams)
var encodedAddr *string
if addr != nil {
// Encode the address while checking if the address passes the
// filter when needed.
encodedAddr = btcjson.String(addr.EncodeAddress())
// If the filter doesn't already pass, make it pass if
// the address exists in the filter.
if _, exists := filterAddrMap[*encodedAddr]; exists {
passesFilter = true
}
}
// Ignore the entry if it doesn't pass the filter.
if !passesFilter {
continue
}
// Add entry to the list if it wasn't already done above.
if len(filterAddrMap) != 0 {
vinList = append(vinList, vinEntry)
}
// Update the entry with previous output information if
// requested.
if vinExtra {
vinListEntry := &vinList[len(vinList)-1]
vinListEntry.PrevOut = &btcjson.PrevOut{
Address: encodedAddr,
Value: util.Amount(originTxOut.Value).ToBTC(),
}
}
}
return vinList, nil
}
// fetchInputTxos fetches the outpoints from all transactions referenced by the
// inputs to the passed transaction by checking the transaction mempool first
// then the transaction index for those already mined into blocks.
func fetchInputTxos(s *Server, tx *wire.MsgTx) (map[wire.Outpoint]wire.TxOut, error) {
mp := s.cfg.TxMemPool
originOutputs := make(map[wire.Outpoint]wire.TxOut)
for txInIndex, txIn := range tx.TxIn {
// Attempt to fetch and use the referenced transaction from the
// memory pool.
origin := &txIn.PreviousOutpoint
originTx, err := mp.FetchTransaction(&origin.TxID)
if err == nil {
txOuts := originTx.MsgTx().TxOut
if origin.Index >= uint32(len(txOuts)) {
errStr := fmt.Sprintf("unable to find output "+
"%s referenced from transaction %s:%d",
origin, tx.TxID(), txInIndex)
return nil, internalRPCError(errStr, "")
}
originOutputs[*origin] = *txOuts[origin.Index]
continue
}
// Look up the location of the transaction.
blockRegion, err := s.cfg.TxIndex.TxFirstBlockRegion(&origin.TxID)
if err != nil {
context := "Failed to retrieve transaction location"
return nil, internalRPCError(err.Error(), context)
}
if blockRegion == nil {
return nil, rpcNoTxInfoError(&origin.TxID)
}
// Load the raw transaction bytes from the database.
var txBytes []byte
err = s.cfg.DB.View(func(dbTx database.Tx) error {
var err error
txBytes, err = dbTx.FetchBlockRegion(blockRegion)
return err
})
if err != nil {
return nil, rpcNoTxInfoError(&origin.TxID)
}
// Deserialize the transaction
var msgTx wire.MsgTx
err = msgTx.Deserialize(bytes.NewReader(txBytes))
if err != nil {
context := "Failed to deserialize transaction"
return nil, internalRPCError(err.Error(), context)
}
// Add the referenced output to the map.
if origin.Index >= uint32(len(msgTx.TxOut)) {
errStr := fmt.Sprintf("unable to find output %s "+
"referenced from transaction %s:%d", origin,
tx.TxID(), txInIndex)
return nil, internalRPCError(errStr, "")
}
originOutputs[*origin] = *msgTx.TxOut[origin.Index]
}
return originOutputs, nil
}
// fetchMempoolTxnsForAddress queries the address index for all unconfirmed
// transactions that involve the provided address. The results will be limited
// by the number to skip and the number requested.
func fetchMempoolTxnsForAddress(s *Server, addr util.Address, numToSkip, numRequested uint32) ([]*util.Tx, uint32) {
// There are no entries to return when there are less available than the
// number being skipped.
mpTxns := s.cfg.AddrIndex.UnconfirmedTxnsForAddress(addr)
numAvailable := uint32(len(mpTxns))
if numToSkip > numAvailable {
return nil, numAvailable
}
// Filter the available entries based on the number to skip and number
// requested.
rangeEnd := numToSkip + numRequested
if rangeEnd > numAvailable {
rangeEnd = numAvailable
}
return mpTxns[numToSkip:rangeEnd], numToSkip
}