diff --git a/src/test/app/Export_test.cpp b/src/test/app/Export_test.cpp index 36b1451a8..725d7d1f9 100644 --- a/src/test/app/Export_test.cpp +++ b/src/test/app/Export_test.cpp @@ -695,8 +695,20 @@ struct Export_test : public beast::unit_test::suite { auto const& result = exportMeta->peekAtField(sfExportResult).downcast(); - if (result.isFieldPresent(sfBlob)) - multisignedBlob = result.getFieldVL(sfBlob); + if (result.isFieldPresent(sfExportedTxn)) + { + // Serialize the nested object to get the raw blob + // for submission to XRPL. + auto const& expTxn = const_cast(result) + .peekFieldObject(sfExportedTxn); + Serializer s; + expTxn.add(s); + multisignedBlob = s.peekData(); + + log << "Xahau: ExportResult.ExportedTxn = " + << expTxn.getJson(JsonOptions::none).toStyledString() + << std::endl; + } } log << "Xahau: multisigned blob size = " << multisignedBlob.size() << std::endl; diff --git a/src/xrpld/app/tx/detail/Export.cpp b/src/xrpld/app/tx/detail/Export.cpp index 9d179d807..a98780264 100644 --- a/src/xrpld/app/tx/detail/Export.cpp +++ b/src/xrpld/app/tx/detail/Export.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -228,29 +229,31 @@ Export::doApply() return a.getAccountID(sfAccount) < b.getAccountID(sfAccount); }); - // Build the multisigned STTx: inner tx + empty SigningPubKey - // + Signers array. - STObject multiSigned(innerTx); - - if (multiSigned.isFieldPresent(sfTxnSignature)) - multiSigned.makeFieldAbsent(sfTxnSignature); + // Build the multisigned tx. Use sfExportedTxn as the field type + // so it nests properly in ExportResult metadata as readable JSON. + STObject multiSigned(sfExportedTxn); + { + // Copy all non-signing fields from innerTx, then we'll add + // signing fields (empty SigningPubKey + Signers) below. + Serializer s; + innerTx.addWithoutSigningFields(s); + SerialIter sit(s.slice()); + multiSigned.set(sit); + } + // Set empty SigningPubKey (indicates multisigned). multiSigned.setFieldVL(sfSigningPubKey, Slice{}); if (signers.size() > 0) multiSigned.setFieldArray(sfSigners, signers); - // Serialize to get the blob, then deserialize as STTx to - // compute the correct transaction ID (includes Signers). - Serializer outSer; - multiSigned.add(outSer); - Blob multisignedBlob = outSer.peekData(); + // Compute the signed tx hash for the shadow ticket. + // getHash(transactionID) includes ALL fields (Signers etc.), + // matching what STTx::getTransactionID() produces. + auto const signedTxHash = + multiSigned.getHash(HashPrefix::transactionID); - SerialIter finalSit(outSer.slice()); - STTx const signedTx(std::ref(finalSit)); - auto const signedTxHash = signedTx.getTransactionID(); - - // Now create the shadow ticket with the signed tx hash. + // Create the shadow ticket with the signed tx hash. { TER ter = ExportLedgerOps::createShadowTicket( view(), account, innerTx, signedTxHash, j_); @@ -258,12 +261,13 @@ Export::doApply() return ter; } - // Write the export result to metadata. + // Write the export result to metadata. The multisigned tx is + // stored as sfExportedTxn (OBJECT) so it renders as readable + // JSON in metadata, not an opaque hex blob. STObject exportResult(sfExportResult); exportResult.setFieldU32(sfLedgerSequence, currentSeq); exportResult.setFieldH256(sfTransactionHash, txId); - if (!multisignedBlob.empty()) - exportResult.setFieldVL(sfBlob, multisignedBlob); + exportResult.set(std::move(multiSigned)); auto* avi = dynamic_cast(&view()); if (!avi)