|
27 | 27 | #include <streams.h>
|
28 | 28 | #include <sync.h>
|
29 | 29 | #include <txmempool.h>
|
| 30 | +#include <undo.h> |
30 | 31 | #include <util/any.h>
|
31 | 32 | #include <util/check.h>
|
32 | 33 | #include <util/strencodings.h>
|
@@ -281,6 +282,113 @@ static bool rest_headers(const std::any& context,
|
281 | 282 | }
|
282 | 283 | }
|
283 | 284 |
|
| 285 | +/** |
| 286 | + * Serialize spent outputs as a list of per-transaction CTxOut lists using binary format. |
| 287 | + */ |
| 288 | +static void SerializeBlockUndo(DataStream& stream, const CBlockUndo& block_undo) |
| 289 | +{ |
| 290 | + WriteCompactSize(stream, block_undo.vtxundo.size() + 1); |
| 291 | + WriteCompactSize(stream, 0); // block_undo.vtxundo doesn't contain coinbase tx |
| 292 | + for (const CTxUndo& tx_undo : block_undo.vtxundo) { |
| 293 | + WriteCompactSize(stream, tx_undo.vprevout.size()); |
| 294 | + for (const Coin& coin : tx_undo.vprevout) { |
| 295 | + coin.out.Serialize(stream); |
| 296 | + } |
| 297 | + } |
| 298 | +} |
| 299 | + |
| 300 | +/** |
| 301 | + * Serialize spent outputs as a list of per-transaction CTxOut lists using JSON format. |
| 302 | + */ |
| 303 | +static void BlockUndoToJSON(const CBlockUndo& block_undo, UniValue& result) |
| 304 | +{ |
| 305 | + result.push_back({UniValue::VARR}); // block_undo.vtxundo doesn't contain coinbase tx |
| 306 | + for (const CTxUndo& tx_undo : block_undo.vtxundo) { |
| 307 | + UniValue tx_prevouts(UniValue::VARR); |
| 308 | + for (const Coin& coin : tx_undo.vprevout) { |
| 309 | + UniValue prevout(UniValue::VOBJ); |
| 310 | + prevout.pushKV("value", ValueFromAmount(coin.out.nValue)); |
| 311 | + |
| 312 | + UniValue script_pub_key(UniValue::VOBJ); |
| 313 | + ScriptToUniv(coin.out.scriptPubKey, /*out=*/script_pub_key, /*include_hex=*/true, /*include_address=*/true); |
| 314 | + prevout.pushKV("scriptPubKey", std::move(script_pub_key)); |
| 315 | + |
| 316 | + tx_prevouts.push_back(std::move(prevout)); |
| 317 | + } |
| 318 | + result.push_back(std::move(tx_prevouts)); |
| 319 | + } |
| 320 | +} |
| 321 | + |
| 322 | +static bool rest_spent_txouts(const std::any& context, HTTPRequest* req, const std::string& strURIPart) |
| 323 | +{ |
| 324 | + if (!CheckWarmup(req)) { |
| 325 | + return false; |
| 326 | + } |
| 327 | + std::string param; |
| 328 | + const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); |
| 329 | + std::vector<std::string> path = SplitString(param, '/'); |
| 330 | + |
| 331 | + std::string hashStr; |
| 332 | + if (path.size() == 1) { |
| 333 | + // path with query parameter: /rest/spenttxouts/<hash> |
| 334 | + hashStr = path[0]; |
| 335 | + } else { |
| 336 | + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/spenttxouts/<hash>.<ext>"); |
| 337 | + } |
| 338 | + |
| 339 | + auto hash{uint256::FromHex(hashStr)}; |
| 340 | + if (!hash) { |
| 341 | + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); |
| 342 | + } |
| 343 | + |
| 344 | + ChainstateManager* chainman = GetChainman(context, req); |
| 345 | + if (!chainman) { |
| 346 | + return false; |
| 347 | + } |
| 348 | + |
| 349 | + const CBlockIndex* pblockindex = WITH_LOCK(cs_main, return chainman->m_blockman.LookupBlockIndex(*hash)); |
| 350 | + if (!pblockindex) { |
| 351 | + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); |
| 352 | + } |
| 353 | + |
| 354 | + CBlockUndo block_undo; |
| 355 | + if (pblockindex->nHeight > 0 && !chainman->m_blockman.ReadBlockUndo(block_undo, *pblockindex)) { |
| 356 | + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " undo not available"); |
| 357 | + } |
| 358 | + |
| 359 | + switch (rf) { |
| 360 | + case RESTResponseFormat::BINARY: { |
| 361 | + DataStream ssSpentResponse{}; |
| 362 | + SerializeBlockUndo(ssSpentResponse, block_undo); |
| 363 | + req->WriteHeader("Content-Type", "application/octet-stream"); |
| 364 | + req->WriteReply(HTTP_OK, ssSpentResponse); |
| 365 | + return true; |
| 366 | + } |
| 367 | + |
| 368 | + case RESTResponseFormat::HEX: { |
| 369 | + DataStream ssSpentResponse{}; |
| 370 | + SerializeBlockUndo(ssSpentResponse, block_undo); |
| 371 | + const std::string strHex{HexStr(ssSpentResponse) + "\n"}; |
| 372 | + req->WriteHeader("Content-Type", "text/plain"); |
| 373 | + req->WriteReply(HTTP_OK, strHex); |
| 374 | + return true; |
| 375 | + } |
| 376 | + |
| 377 | + case RESTResponseFormat::JSON: { |
| 378 | + UniValue result(UniValue::VARR); |
| 379 | + BlockUndoToJSON(block_undo, result); |
| 380 | + std::string strJSON = result.write() + "\n"; |
| 381 | + req->WriteHeader("Content-Type", "application/json"); |
| 382 | + req->WriteReply(HTTP_OK, strJSON); |
| 383 | + return true; |
| 384 | + } |
| 385 | + |
| 386 | + default: { |
| 387 | + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); |
| 388 | + } |
| 389 | + } |
| 390 | +} |
| 391 | + |
284 | 392 | static bool rest_block(const std::any& context,
|
285 | 393 | HTTPRequest* req,
|
286 | 394 | const std::string& strURIPart,
|
@@ -1021,6 +1129,7 @@ static const struct {
|
1021 | 1129 | {"/rest/deploymentinfo/", rest_deploymentinfo},
|
1022 | 1130 | {"/rest/deploymentinfo", rest_deploymentinfo},
|
1023 | 1131 | {"/rest/blockhashbyheight/", rest_blockhash_by_height},
|
| 1132 | + {"/rest/spenttxouts/", rest_spent_txouts}, |
1024 | 1133 | };
|
1025 | 1134 |
|
1026 | 1135 | void StartREST(const std::any& context)
|
|
0 commit comments