package rpc import ( "bytes" "encoding/hex" "fmt" "github.com/kaspanet/kaspad/dagconfig" "github.com/kaspanet/kaspad/database" "github.com/kaspanet/kaspad/rpcmodel" "github.com/kaspanet/kaspad/txscript" "github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util/daghash" "github.com/kaspanet/kaspad/util/pointers" "github.com/kaspanet/kaspad/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, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCMisc, Message: "Address index must be enabled (--addrindex)", } } // Override the flag for including extra previous output information in // each input if needed. c := cmd.(*rpcmodel.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, &rpcmodel.RPCError{ Code: rpcmodel.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, &rpcmodel.RPCError{ Code: rpcmodel.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 []rpcmodel.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([]rpcmodel.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 DAG. header, err := s.cfg.DAG.HeaderByHash(blkHash) if err != nil { return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCBlockNotFound, Message: "Block not found", } } blkHeader = header blkHashStr = blkHash.String() } // Add the block information to the result if there is any. if blkHeader != nil { 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, dagParams *dagconfig.Params, vinExtra bool, filterAddrMap map[string]struct{}) ([]rpcmodel.VinPrevOut, error) { // Use a dynamically sized list to accommodate the address filter. vinList := make([]rpcmodel.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 := rpcmodel.VinPrevOut{ TxID: prevOut.TxID.String(), Vout: prevOut.Index, Sequence: txIn.Sequence, ScriptSig: &rpcmodel.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, dagParams) var encodedAddr *string if addr != nil { // Encode the address while checking if the address passes the // filter when needed. encodedAddr = pointers.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 = &rpcmodel.PrevOut{ Address: encodedAddr, Value: util.Amount(originTxOut.Value).ToKAS(), } } } 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 }