Skip to content

Commit

Permalink
Port rpc wallet spend
Browse files Browse the repository at this point in the history
  • Loading branch information
timemarkovqtum committed Feb 29, 2024
1 parent a2f28f9 commit 8722f7a
Show file tree
Hide file tree
Showing 5 changed files with 674 additions and 118 deletions.
90 changes: 3 additions & 87 deletions src/rpc/output_script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ static RPCHelpMan validateaddress()
{
return RPCHelpMan{
"validateaddress",
"\nReturn information about the given bitcoin address.\n",
"\nReturn information about the given qtum address.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"},
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address to validate"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The qtum address validated"},
{RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"},
{RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"},
{RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"},
Expand Down Expand Up @@ -85,89 +85,6 @@ static RPCHelpMan validateaddress()
};
}

static RPCHelpMan createmultisig()
{
return RPCHelpMan{"createmultisig",
"\nCreates a multi-signature address with n signature of m keys required.\n"
"It returns a json object with the address and redeemScript.\n",
{
{"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."},
{"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.",
{
{"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"},
}},
{"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "address", "The value of the new multisig address."},
{RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."},
{RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
{RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig",
{
{RPCResult::Type::STR, "", ""},
}},
}
},
RPCExamples{
"\nCreate a multisig address from 2 public keys\n"
+ HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
int required = request.params[0].getInt<int>();

// Get the public keys
const UniValue& keys = request.params[1].get_array();
std::vector<CPubKey> pubkeys;
for (unsigned int i = 0; i < keys.size(); ++i) {
if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
pubkeys.push_back(HexToPubKey(keys[i].get_str()));
} else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str()));
}
}

// Get the output type
OutputType output_type = OutputType::LEGACY;
if (!request.params[2].isNull()) {
std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str());
if (!parsed) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
} else if (parsed.value() == OutputType::BECH32M) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
}
output_type = parsed.value();
}

// Construct using pay-to-script-hash:
FillableSigningProvider keystore;
CScript inner;
const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);

// Make the descriptor
std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore);

UniValue result(UniValue::VOBJ);
result.pushKV("address", EncodeDestination(dest));
result.pushKV("redeemScript", HexStr(inner));
result.pushKV("descriptor", descriptor->ToString());

UniValue warnings(UniValue::VARR);
if (descriptor->GetOutputType() != output_type) {
// Only warns if the user has explicitly chosen an address type we cannot generate
warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
}
PushWarnings(warnings, result);

return result;
},
};
}

static RPCHelpMan getdescriptorinfo()
{
const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)";
Expand Down Expand Up @@ -306,7 +223,6 @@ void RegisterOutputScriptRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
{"util", &validateaddress},
{"util", &createmultisig},
{"util", &deriveaddresses},
{"util", &getdescriptorinfo},
};
Expand Down
72 changes: 68 additions & 4 deletions src/rpc/rawtransaction_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, std::optio
}
}

void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in)
void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in, IRawContract* rawContract)
{
if (outputs_in.isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null");
Expand Down Expand Up @@ -99,6 +99,7 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in)
std::set<CTxDestination> destinations;
bool has_data{false};

int i = 0;
for (const std::string& name_ : outputs.getKeys()) {
if (name_ == "data") {
if (has_data) {
Expand All @@ -109,10 +110,14 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in)

CTxOut out(0, CScript() << OP_RETURN << data);
rawTx.vout.push_back(out);
} else if (rawContract && name_ == "contract") {
// Get the contract object
UniValue contract = outputs[i];
rawContract->addContract(rawTx, contract);
} else {
CTxDestination destination = DecodeDestination(name_);
if (!IsValidDestination(destination)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_);
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Qtum address: ") + name_);
}

if (!destinations.insert(destination).second) {
Expand All @@ -125,10 +130,11 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in)
CTxOut out(nAmount, scriptPubKey);
rawTx.vout.push_back(out);
}
++i;
}
}

CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf)
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf, IRawContract* rawContract)
{
CMutableTransaction rawTx;

Expand All @@ -140,7 +146,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
}

AddInputs(rawTx, inputs_in, rbf);
AddOutputs(rawTx, outputs_in);
AddOutputs(rawTx, outputs_in, rawContract);

if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
Expand Down Expand Up @@ -287,6 +293,26 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst
}
}

void CheckSenderSignatures(CMutableTransaction& mtx)
{
// Check the sender signatures are inside the outputs, before signing the inputs
if(mtx.HasOpSender())
{
int nOut = 0;
for (const auto& output : mtx.vout)
{
if(output.scriptPubKey.HasOpSender())
{
CScript senderPubKey, senderSig;
if(!ExtractSenderData(output.scriptPubKey, &senderPubKey, &senderSig))
throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing contract sender signature,"
"use signrawsendertransactionwithwallet or signrawsendertransactionwithkey to sign the outputs");
}
nOut++;
}
}
}

void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result)
{
int nHashType = ParseSighashString(hashType);
Expand Down Expand Up @@ -319,3 +345,41 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const
result.pushKV("errors", vErrors);
}
}

static void TxOutErrorToJSON(const CTxOut& output, UniValue& vErrorsRet, const std::string& strMessage)
{
UniValue entry(UniValue::VOBJ);
entry.pushKV("amount", ValueFromAmount(output.nValue));
entry.pushKV("scriptPubKey", HexStr(MakeUCharSpan(output.scriptPubKey)));
entry.pushKV("error", strMessage);
vErrorsRet.push_back(entry);
}

void SignTransactionOutput(CMutableTransaction &mtx, FillableSigningProvider *keystore, const UniValue &hashType, UniValue &result)
{
int nHashType = ParseSighashString(hashType);

// Script verification errors
std::map<int, std::string> output_errors;

bool complete = SignTransactionOutput(mtx, keystore, nHashType, output_errors);
SignTransactionOutputResultToJSON(mtx, complete, output_errors, result);
}

void SignTransactionOutputResultToJSON(CMutableTransaction &mtx, bool complete, std::map<int, std::string> &output_errors, UniValue &result)
{
// Make errors UniValue
UniValue vErrors(UniValue::VARR);
for (const auto& err_pair : output_errors) {
TxOutErrorToJSON(mtx.vout.at(err_pair.first), vErrors, err_pair.second);
}

result.pushKV("hex", EncodeHexTx(CTransaction(mtx)));
result.pushKV("complete", complete);
if (!vErrors.empty()) {
if (result.exists("errors")) {
vErrors.push_backV(result["errors"].getValues());
}
result.pushKV("errors", vErrors);
}
}
18 changes: 16 additions & 2 deletions src/rpc/rawtransaction_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ class Coin;
class COutPoint;
class SigningProvider;

/**
* @brief The IRawContract class Parse the contract output for raw transaction
*/
class IRawContract
{
public:
virtual void addContract(CMutableTransaction& rawTx, const UniValue& contract) = 0;
};

/**
* Sign a transaction with the given keystore and previous transactions
*
Expand All @@ -43,9 +52,14 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst
void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, bool rbf);

/** Normalize univalue-represented outputs and add them to the transaction */
void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in);
void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in, IRawContract* rawContract = nullptr);

/** Create a transaction from univalue parameters */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf);
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf, IRawContract* rawContract = nullptr);

void SignTransactionOutput(CMutableTransaction& mtx, FillableSigningProvider *keystore, const UniValue& hashType, UniValue& result);
void SignTransactionOutputResultToJSON(CMutableTransaction& mtx, bool complete, std::map<int, std::string>& output_errors, UniValue& result);

void CheckSenderSignatures(CMutableTransaction& mtx);

#endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H
Loading

0 comments on commit 8722f7a

Please sign in to comment.