rawtransaction_util.cpp raw

   1  // Copyright (c) 2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2022 The Limenka developers
   3  // Distributed under the MIT software license, see the accompanying
   4  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   6  #include <rpc/rawtransaction_util.h>
   7  
   8  #include <coins.h>
   9  #include <consensus/amount.h>
  10  #include <core_io.h>
  11  #include <key_io.h>
  12  #include <policy/policy.h>
  13  #include <primitives/transaction.h>
  14  #include <rpc/request.h>
  15  #include <rpc/util.h>
  16  #include <script/sign.h>
  17  #include <script/signingprovider.h>
  18  #include <tinyformat.h>
  19  #include <univalue.h>
  20  #include <util/rbf.h>
  21  #include <util/strencodings.h>
  22  #include <util/translation.h>
  23  
  24  #include <optional>
  25  
  26  void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, std::optional<bool> rbf)
  27  {
  28      UniValue inputs;
  29      if (inputs_in.isNull()) {
  30          inputs = UniValue::VARR;
  31      } else {
  32          inputs = inputs_in.get_array();
  33      }
  34  
  35      for (unsigned int idx = 0; idx < inputs.size(); idx++) {
  36          const UniValue& input = inputs[idx];
  37          const UniValue& o = input.get_obj();
  38  
  39          Txid txid = Txid::FromUint256(ParseHashO(o, "txid"));
  40  
  41          const UniValue& vout_v = o.find_value("vout");
  42          if (!vout_v.isNum())
  43              throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key");
  44          int nOutput = vout_v.getInt<int>();
  45          if (nOutput < 0)
  46              throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
  47  
  48          uint32_t nSequence;
  49  
  50          if (rbf.value_or(true)) {
  51              nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */
  52          } else if (rawTx.nLockTime) {
  53              nSequence = CTxIn::MAX_SEQUENCE_NONFINAL; /* CTxIn::SEQUENCE_FINAL - 1 */
  54          } else {
  55              nSequence = CTxIn::SEQUENCE_FINAL;
  56          }
  57  
  58          // set the sequence number if passed in the parameters object
  59          const UniValue& sequenceObj = o.find_value("sequence");
  60          if (sequenceObj.isNum()) {
  61              int64_t seqNr64 = sequenceObj.getInt<int64_t>();
  62              if (seqNr64 < 0 || seqNr64 > CTxIn::SEQUENCE_FINAL) {
  63                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
  64              } else {
  65                  nSequence = (uint32_t)seqNr64;
  66              }
  67          }
  68  
  69          CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);
  70  
  71          rawTx.vin.push_back(in);
  72      }
  73  }
  74  
  75  UniValue NormalizeOutputs(const UniValue& outputs_in)
  76  {
  77      if (outputs_in.isNull()) {
  78          throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null");
  79      }
  80  
  81      const bool outputs_is_obj = outputs_in.isObject();
  82      UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array();
  83  
  84      if (!outputs_is_obj) {
  85          // Translate array of key-value pairs into dict
  86          UniValue outputs_dict = UniValue(UniValue::VOBJ);
  87          for (size_t i = 0; i < outputs.size(); ++i) {
  88              const UniValue& output = outputs[i];
  89              if (!output.isObject()) {
  90                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair not an object as expected");
  91              }
  92              if (output.size() != 1) {
  93                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair must contain exactly one key");
  94              }
  95              outputs_dict.pushKVs(output);
  96          }
  97          outputs = std::move(outputs_dict);
  98      }
  99      return outputs;
 100  }
 101  
 102  std::vector<std::pair<CTxDestination, CAmount>> ParseOutputs(const UniValue& outputs)
 103  {
 104      // Duplicate checking
 105      std::set<CTxDestination> destinations;
 106      std::vector<std::pair<CTxDestination, CAmount>> parsed_outputs;
 107      bool has_data{false};
 108      for (const std::string& name_ : outputs.getKeys()) {
 109          if (name_ == "data") {
 110              if (has_data) {
 111                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, duplicate key: data");
 112              }
 113              has_data = true;
 114              std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data");
 115              CTxDestination destination{CNoDestination{CScript() << OP_RETURN << data}};
 116              CAmount amount{0};
 117              parsed_outputs.emplace_back(destination, amount);
 118          } else {
 119              CTxDestination destination{DecodeDestination(name_)};
 120              CAmount amount{AmountFromValue(outputs[name_])};
 121              if (!IsValidDestination(destination)) {
 122                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Limenka address: ") + name_);
 123              }
 124  
 125              if (!destinations.insert(destination).second) {
 126                  throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
 127              }
 128              parsed_outputs.emplace_back(destination, amount);
 129          }
 130      }
 131      return parsed_outputs;
 132  }
 133  
 134  void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in)
 135  {
 136      UniValue outputs(UniValue::VOBJ);
 137      outputs = NormalizeOutputs(outputs_in);
 138  
 139      std::vector<std::pair<CTxDestination, CAmount>> parsed_outputs = ParseOutputs(outputs);
 140      for (const auto& [destination, nAmount] : parsed_outputs) {
 141          CScript scriptPubKey = GetScriptForDestination(destination);
 142  
 143          CTxOut out(nAmount, scriptPubKey);
 144          rawTx.vout.push_back(out);
 145      }
 146  }
 147  
 148  CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf)
 149  {
 150      CMutableTransaction rawTx;
 151  
 152      if (!locktime.isNull()) {
 153          int64_t nLockTime = locktime.getInt<int64_t>();
 154          if (nLockTime < 0 || nLockTime > LOCKTIME_MAX)
 155              throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
 156          rawTx.nLockTime = nLockTime;
 157      }
 158  
 159      AddInputs(rawTx, inputs_in, rbf);
 160      AddOutputs(rawTx, outputs_in);
 161  
 162      if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) {
 163          throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
 164      }
 165  
 166      return rawTx;
 167  }
 168  
 169  /** Pushes a JSON object for script verification or signing errors to vErrorsRet. */
 170  static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::string& strMessage)
 171  {
 172      UniValue entry(UniValue::VOBJ);
 173      entry.pushKV("txid", txin.prevout.hash.ToString());
 174      entry.pushKV("vout", (uint64_t)txin.prevout.n);
 175      UniValue witness(UniValue::VARR);
 176      for (unsigned int i = 0; i < txin.scriptWitness.stack.size(); i++) {
 177          witness.push_back(HexStr(txin.scriptWitness.stack[i]));
 178      }
 179      entry.pushKV("witness", std::move(witness));
 180      entry.pushKV("scriptSig", HexStr(txin.scriptSig));
 181      entry.pushKV("sequence", (uint64_t)txin.nSequence);
 182      entry.pushKV("error", strMessage);
 183      vErrorsRet.push_back(std::move(entry));
 184  }
 185  
 186  void ParsePrevouts(const UniValue& prevTxsUnival, FlatSigningProvider* keystore, std::map<COutPoint, Coin>& coins)
 187  {
 188      if (!prevTxsUnival.isNull()) {
 189          const UniValue& prevTxs = prevTxsUnival.get_array();
 190          for (unsigned int idx = 0; idx < prevTxs.size(); ++idx) {
 191              const UniValue& p = prevTxs[idx];
 192              if (!p.isObject()) {
 193                  throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}");
 194              }
 195  
 196              const UniValue& prevOut = p.get_obj();
 197  
 198              RPCTypeCheckObj(prevOut,
 199                  {
 200                      {"txid", UniValueType(UniValue::VSTR)},
 201                      {"vout", UniValueType(UniValue::VNUM)},
 202                      {"scriptPubKey", UniValueType(UniValue::VSTR)},
 203                  });
 204  
 205              Txid txid = Txid::FromUint256(ParseHashO(prevOut, "txid"));
 206  
 207              int nOut = prevOut.find_value("vout").getInt<int>();
 208              if (nOut < 0) {
 209                  throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout cannot be negative");
 210              }
 211  
 212              COutPoint out(txid, nOut);
 213              std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
 214              CScript scriptPubKey(pkData.begin(), pkData.end());
 215  
 216              {
 217                  auto coin = coins.find(out);
 218                  if (coin != coins.end() && !coin->second.IsSpent() && coin->second.out.scriptPubKey != scriptPubKey) {
 219                      std::string err("Previous output scriptPubKey mismatch:\n");
 220                      err = err + ScriptToAsmStr(coin->second.out.scriptPubKey) + "\nvs:\n"+
 221                          ScriptToAsmStr(scriptPubKey);
 222                      throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
 223                  }
 224                  Coin newcoin;
 225                  newcoin.out.scriptPubKey = scriptPubKey;
 226                  newcoin.out.nValue = MAX_MONEY;
 227                  if (prevOut.exists("amount")) {
 228                      newcoin.out.nValue = AmountFromValue(prevOut.find_value("amount"));
 229                  }
 230                  newcoin.nHeight = 1;
 231                  coins[out] = std::move(newcoin);
 232              }
 233  
 234              // if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed
 235              const bool is_p2sh = scriptPubKey.IsPayToScriptHash();
 236              const bool is_p2wsh = scriptPubKey.IsPayToWitnessScriptHash();
 237              if (keystore && (is_p2sh || is_p2wsh)) {
 238                  RPCTypeCheckObj(prevOut,
 239                      {
 240                          {"redeemScript", UniValueType(UniValue::VSTR)},
 241                          {"witnessScript", UniValueType(UniValue::VSTR)},
 242                      }, true);
 243                  const UniValue& rs{prevOut.find_value("redeemScript")};
 244                  const UniValue& ws{prevOut.find_value("witnessScript")};
 245                  if (rs.isNull() && ws.isNull()) {
 246                      throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing redeemScript/witnessScript");
 247                  }
 248  
 249                  // work from witnessScript when possible
 250                  std::vector<unsigned char> scriptData(!ws.isNull() ? ParseHexV(ws, "witnessScript") : ParseHexV(rs, "redeemScript"));
 251                  CScript script(scriptData.begin(), scriptData.end());
 252                  keystore->scripts.emplace(CScriptID(script), script);
 253                  // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
 254                  // This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead.
 255                  CScript witness_output_script{GetScriptForDestination(WitnessV0ScriptHash(script))};
 256                  keystore->scripts.emplace(CScriptID(witness_output_script), witness_output_script);
 257  
 258                  if (!ws.isNull() && !rs.isNull()) {
 259                      // if both witnessScript and redeemScript are provided,
 260                      // they should either be the same (for backwards compat),
 261                      // or the redeemScript should be the encoded form of
 262                      // the witnessScript (ie, for p2sh-p2wsh)
 263                      if (ws.get_str() != rs.get_str()) {
 264                          std::vector<unsigned char> redeemScriptData(ParseHexV(rs, "redeemScript"));
 265                          CScript redeemScript(redeemScriptData.begin(), redeemScriptData.end());
 266                          if (redeemScript != witness_output_script) {
 267                              throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript does not correspond to witnessScript");
 268                          }
 269                      }
 270                  }
 271  
 272                  if (is_p2sh) {
 273                      const CTxDestination p2sh{ScriptHash(script)};
 274                      const CTxDestination p2sh_p2wsh{ScriptHash(witness_output_script)};
 275                      if (scriptPubKey == GetScriptForDestination(p2sh)) {
 276                          // traditional p2sh; arguably an error if
 277                          // we got here with rs.IsNull(), because
 278                          // that means the p2sh script was specified
 279                          // via witnessScript param, but for now
 280                          // we'll just quietly accept it
 281                      } else if (scriptPubKey == GetScriptForDestination(p2sh_p2wsh)) {
 282                          // p2wsh encoded as p2sh; ideally the witness
 283                          // script was specified in the witnessScript
 284                          // param, but also support specifying it via
 285                          // redeemScript param for backwards compat
 286                          // (in which case ws.IsNull() == true)
 287                      } else {
 288                          // otherwise, can't generate scriptPubKey from
 289                          // either script, so we got unusable parameters
 290                          throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey");
 291                      }
 292                  } else if (is_p2wsh) {
 293                      // plain p2wsh; could throw an error if script
 294                      // was specified by redeemScript rather than
 295                      // witnessScript (ie, ws.IsNull() == true), but
 296                      // accept it for backwards compat
 297                      const CTxDestination p2wsh{WitnessV0ScriptHash(script)};
 298                      if (scriptPubKey != GetScriptForDestination(p2wsh)) {
 299                          throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey");
 300                      }
 301                  }
 302              }
 303          }
 304      }
 305  }
 306  
 307  void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result)
 308  {
 309      int nHashType = ParseSighashString(hashType);
 310  
 311      // Script verification errors
 312      std::map<int, bilingual_str> input_errors;
 313      std::optional<CAmount> inputs_amount_sum;
 314  
 315      bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors, &inputs_amount_sum);
 316      SignTransactionResultToJSON(mtx, complete, coins, input_errors, result, inputs_amount_sum);
 317  }
 318  
 319  void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, bilingual_str>& input_errors, UniValue& result, const std::optional<CAmount>& inputs_amount_sum)
 320  {
 321      // Make errors UniValue
 322      UniValue vErrors(UniValue::VARR);
 323      for (const auto& err_pair : input_errors) {
 324          if (err_pair.second.original == "Missing amount") {
 325              // This particular error needs to be an exception for some reason
 326              throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coins.at(mtx.vin.at(err_pair.first).prevout).out.ToString()));
 327          }
 328          TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second.original);
 329      }
 330  
 331      CTransaction tx(mtx);
 332      result.pushKV("hex", EncodeHexTx(tx));
 333      result.pushKV("complete", complete);
 334      if (inputs_amount_sum) {
 335          CAmount inout_amount = *inputs_amount_sum;
 336          for (const CTxOut& txout : mtx.vout) {
 337              inout_amount -= txout.nValue;
 338          }
 339          result.pushKV("fee", ValueFromAmount(inout_amount));
 340          result.pushKV("feerate",
 341              ValueFromAmount(
 342                  CFeeRate(inout_amount, GetVirtualTransactionSize(tx)).GetFeePerK()
 343              )
 344          );
 345      }
 346      if (!vErrors.empty()) {
 347          if (result.exists("errors")) {
 348              vErrors.push_backV(result["errors"].getValues());
 349          }
 350          result.pushKV("errors", std::move(vErrors));
 351      }
 352  }
 353