11#include < test/jtx.h>
22#include < test/jtx/Env.h>
3+ #include < test/jtx/batch.h>
34#include < test/jtx/envconfig.h>
45
56#include < xrpld/app/rdb/backend/SQLiteDatabase.h>
67#include < xrpld/rpc/CTID.h>
78
89#include < xrpl/core/NetworkIDService.h>
910#include < xrpl/protocol/ErrorCodes.h>
11+ #include < xrpl/protocol/SField.h>
1012#include < xrpl/protocol/STBase.h>
13+ #include < xrpl/protocol/TxFlags.h>
1114#include < xrpl/protocol/jss.h>
1215#include < xrpl/protocol/serialize.h>
1316
@@ -842,6 +845,112 @@ class Transaction_test : public beast::unit_test::suite
842845 }
843846 }
844847
848+ void
849+ testBatchInnerTransactions (FeatureBitset features, unsigned apiVersion)
850+ {
851+ testcase (" Test Batch inner_transactions API version " + std::to_string (apiVersion));
852+
853+ using namespace test ::jtx;
854+ using std::to_string;
855+
856+ Env env{*this , features};
857+ Account const alice{" alice" };
858+ Account const bob{" bob" };
859+
860+ env.fund (XRP (10000 ), alice, bob);
861+ env.close ();
862+
863+ auto const aliceSeq = env.seq (alice);
864+
865+ // Create a Batch transaction with two inner Payment transactions
866+ auto const batchFee = batch::calcBatchFee (env, 0 , 2 );
867+ auto batchTxn = env.jt (
868+ batch::outer (alice, aliceSeq, batchFee, tfAllOrNothing),
869+ batch::inner (pay (alice, bob, XRP (100 )), aliceSeq + 1 ),
870+ batch::inner (pay (alice, bob, XRP (200 )), aliceSeq + 2 ));
871+ env (batchTxn, ter (tesSUCCESS));
872+ env.close ();
873+
874+ // Get the batch transaction ID and inner transaction IDs
875+ auto const batchID = batchTxn.stx ->getTransactionID ();
876+ auto const innerIDs = batchTxn.stx ->getBatchTransactionIDs ();
877+ BEAST_EXPECT (innerIDs.size () == 2 );
878+
879+ // Test non-binary mode
880+ {
881+ Json::Value params{Json::objectValue};
882+ params[jss::transaction] = to_string (batchID);
883+ params[jss::binary] = false ;
884+ params[jss::api_version] = apiVersion;
885+ auto const result = env.client ().invoke (" tx" , params);
886+
887+ BEAST_EXPECT (result[jss::result][jss::status] == jss::success);
888+ BEAST_EXPECT (result[jss::result][jss::validated] == true );
889+
890+ // Check inner_transactions field exists
891+ if (BEAST_EXPECT (result[jss::result].isMember (jss::inner_transactions)))
892+ {
893+ auto const & innerTxns = result[jss::result][jss::inner_transactions];
894+ if (BEAST_EXPECT (innerTxns.isArray () && innerTxns.size () == 2 ))
895+ {
896+ // Check first inner transaction
897+ auto const & inner0 = innerTxns[0u ];
898+ BEAST_EXPECT (inner0[jss::hash] == to_string (innerIDs[0 ]));
899+ BEAST_EXPECT (inner0[jss::engine_result] == " tesSUCCESS" );
900+ BEAST_EXPECT (inner0.isMember (jss::tx_json));
901+ BEAST_EXPECT (inner0.isMember (jss::meta));
902+ BEAST_EXPECT (inner0[jss::tx_json][jss::TransactionType] == " Payment" );
903+ // Validate ParentBatchID in metadata matches outer Batch hash
904+ BEAST_EXPECT (inner0[jss::meta][sfParentBatchID.jsonName ] == to_string (batchID));
905+
906+ // Check second inner transaction
907+ auto const & inner1 = innerTxns[1u ];
908+ BEAST_EXPECT (inner1[jss::hash] == to_string (innerIDs[1 ]));
909+ BEAST_EXPECT (inner1[jss::engine_result] == " tesSUCCESS" );
910+ BEAST_EXPECT (inner1.isMember (jss::tx_json));
911+ BEAST_EXPECT (inner1.isMember (jss::meta));
912+ BEAST_EXPECT (inner1[jss::tx_json][jss::TransactionType] == " Payment" );
913+ // Validate ParentBatchID in metadata matches outer Batch hash
914+ BEAST_EXPECT (inner1[jss::meta][sfParentBatchID.jsonName ] == to_string (batchID));
915+ }
916+ }
917+ }
918+
919+ // Test binary mode
920+ {
921+ Json::Value params{Json::objectValue};
922+ params[jss::transaction] = to_string (batchID);
923+ params[jss::binary] = true ;
924+ params[jss::api_version] = apiVersion;
925+ auto const result = env.client ().invoke (" tx" , params);
926+
927+ BEAST_EXPECT (result[jss::result][jss::status] == jss::success);
928+ BEAST_EXPECT (result[jss::result][jss::validated] == true );
929+
930+ // Check inner_transactions field exists in binary mode
931+ if (BEAST_EXPECT (result[jss::result].isMember (jss::inner_transactions)))
932+ {
933+ auto const & innerTxns = result[jss::result][jss::inner_transactions];
934+ if (BEAST_EXPECT (innerTxns.isArray () && innerTxns.size () == 2 ))
935+ {
936+ // Check first inner transaction has binary blobs
937+ auto const & inner0 = innerTxns[0u ];
938+ BEAST_EXPECT (inner0[jss::hash] == to_string (innerIDs[0 ]));
939+ BEAST_EXPECT (inner0[jss::engine_result] == " tesSUCCESS" );
940+ BEAST_EXPECT (inner0.isMember (jss::tx_blob));
941+ BEAST_EXPECT (inner0.isMember (jss::meta_blob));
942+
943+ // Check second inner transaction has binary blobs
944+ auto const & inner1 = innerTxns[1u ];
945+ BEAST_EXPECT (inner1[jss::hash] == to_string (innerIDs[1 ]));
946+ BEAST_EXPECT (inner1[jss::engine_result] == " tesSUCCESS" );
947+ BEAST_EXPECT (inner1.isMember (jss::tx_blob));
948+ BEAST_EXPECT (inner1.isMember (jss::meta_blob));
949+ }
950+ }
951+ }
952+ }
953+
845954public:
846955 void
847956 run () override
@@ -861,6 +970,8 @@ class Transaction_test : public beast::unit_test::suite
861970 testCTIDValidation (features);
862971 testRPCsForCTID (features);
863972 forAllApiVersions (std::bind_front (&Transaction_test::testRequest, this , features));
973+ forAllApiVersions (
974+ std::bind_front (&Transaction_test::testBatchInnerTransactions, this , features));
864975 }
865976};
866977
0 commit comments