Skip to content

Commit 0bc107f

Browse files
Merge pull request #150 from Peersyst/xrpl/feat/batch-integration-tests
[TA-5091]: batch transaction integration tests
2 parents 1a0610d + df6ac82 commit 0bc107f

File tree

15 files changed

+552
-128
lines changed

15 files changed

+552
-128
lines changed

xrpl/rpc/client.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,20 +249,20 @@ func (c *Client) Autofill(tx *transaction.FlatTransaction) error {
249249
return err
250250
}
251251
}
252-
if txType, ok := (*tx)["TransactionType"].(transaction.TxType); ok {
253-
if acc, ok := (*tx)["Account"].(types.Address); txType == transaction.AccountDeleteTx && ok {
252+
if txType, ok := (*tx)["TransactionType"].(string); ok {
253+
if acc, ok := (*tx)["Account"].(types.Address); txType == transaction.AccountDeleteTx.String() && ok {
254254
err := c.checkAccountDeleteBlockers(acc)
255255
if err != nil {
256256
return err
257257
}
258258
}
259-
if txType == transaction.PaymentTx {
259+
if txType == transaction.PaymentTx.String() {
260260
err := c.checkPaymentAmounts(tx)
261261
if err != nil {
262262
return err
263263
}
264264
}
265-
if txType == transaction.BatchTx {
265+
if txType == transaction.BatchTx.String() {
266266
err := c.autofillRawTransactions(tx)
267267
if err != nil {
268268
return err

xrpl/rpc/helpers.go

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -603,55 +603,33 @@ func (c *Client) calculateBatchFees(tx *transaction.FlatTransaction) (uint64, er
603603
var totalFees uint64
604604

605605
// Get RawTransactions from the batch transaction
606-
rawTransactions, ok := (*tx)["RawTransactions"]
606+
rawTransactions, ok := (*tx)["RawTransactions"].([]map[string]any)
607607
if !ok {
608608
return 0, errors.New("RawTransactions field missing from Batch transaction")
609609
}
610610

611-
// Convert to array of interfaces
612-
rawTxArray, ok := rawTransactions.([]interface{})
613-
if !ok {
614-
return 0, errors.New("RawTransactions field is not an array")
615-
}
616-
617611
// Iterate through each raw transaction
618-
for _, rawTxItem := range rawTxArray {
619-
// Each item should be a map containing a "RawTransaction" field
620-
rawTxWrapper, ok := rawTxItem.(map[string]interface{})
621-
if !ok {
622-
return 0, errors.New("RawTransaction item is not an object")
623-
}
624-
612+
for _, rawTx := range rawTransactions {
625613
// Extract the actual transaction from the wrapper
626-
innerTx, ok := rawTxWrapper["RawTransaction"]
614+
innerTx, ok := rawTx["RawTransaction"].(map[string]any)
627615
if !ok {
628616
return 0, errors.New("RawTransaction field missing from wrapper")
629617
}
630618

631-
innerTxMap, ok := innerTx.(map[string]interface{})
632-
if !ok {
633-
return 0, errors.New("RawTransaction field is not an object")
634-
}
635-
636-
// Convert to FlatTransaction for fee calculation
637-
flatInnerTx := transaction.FlatTransaction(innerTxMap)
638-
639619
// Calculate fee for this inner transaction (no multi-signing for inner transactions)
640-
err := c.calculateFeePerTransactionType(&flatInnerTx, 0)
620+
innerTxFlat := transaction.FlatTransaction(innerTx)
621+
err := c.calculateFeePerTransactionType(&innerTxFlat, 0)
641622
if err != nil {
642623
return 0, err
643624
}
644625

645626
// Extract the calculated fee
646-
feeValue, ok := flatInnerTx["Fee"]
627+
feeStr, ok := innerTx["Fee"].(string)
647628
if !ok {
648629
return 0, errors.New("fee field missing after calculation")
649630
}
650631

651-
feeStr, ok := feeValue.(string)
652-
if !ok {
653-
return 0, errors.New("fee field is not a string")
654-
}
632+
innerTx["Fee"] = "0"
655633

656634
// Convert fee string to uint64 and add to total
657635
feeUint, err := strconv.ParseUint(feeStr, 10, 64)

xrpl/testutil/integration/faucet.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (f *Runner) fundWalletWithGenesis(w *wallet.Wallet) error {
5757
}
5858

5959
flatTx := payment.Flatten()
60-
_, err = f.TestTransaction(&flatTx, &genesisWallet, "tesSUCCESS")
60+
_, err = f.TestTransaction(&flatTx, &genesisWallet, "tesSUCCESS", nil)
6161
if err != nil {
6262
return err
6363
}

xrpl/testutil/integration/runner.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ type Runner struct {
1818
wallets []*wallet.Wallet
1919
}
2020

21+
type TestTransactionOptions struct {
22+
SkipAutofill bool
23+
}
24+
2125
// NewRunner creates a new runner. It doesn't connect to the websocket or generate wallets until Setup is called.
2226
// A testing.T is required to use the require package.
2327
func NewRunner(t *testing.T, client Client, config *RunnerConfig) *Runner {
@@ -67,8 +71,8 @@ func (r *Runner) Teardown() error {
6771

6872
// TestTransaction submits a signed transaction and validates the result.
6973
// If validate is nil, the transaction is not validated.
70-
func (r *Runner) TestTransaction(flatTx *transaction.FlatTransaction, signer *wallet.Wallet, expectedEngineResult string) (*transactions.SubmitResponse, error) {
71-
tx, hash, err := r.processTransaction(flatTx, signer)
74+
func (r *Runner) TestTransaction(flatTx *transaction.FlatTransaction, signer *wallet.Wallet, expectedEngineResult string, opts *TestTransactionOptions) (*transactions.SubmitResponse, error) {
75+
tx, hash, err := r.processTransaction(flatTx, signer, opts)
7276
if err != nil {
7377
return nil, err
7478
}
@@ -112,13 +116,15 @@ func (r *Runner) GetClient() Client {
112116
return r.client
113117
}
114118

115-
func (r *Runner) processTransaction(flatTx *transaction.FlatTransaction, signer *wallet.Wallet) (*transactions.SubmitResponse, string, error) {
119+
func (r *Runner) processTransaction(flatTx *transaction.FlatTransaction, signer *wallet.Wallet, opts *TestTransactionOptions) (*transactions.SubmitResponse, string, error) {
116120
attempts := 0
117121

118122
for {
119-
err := r.client.Autofill(flatTx)
120-
if err != nil {
121-
return nil, "", err
123+
if opts == nil || !opts.SkipAutofill {
124+
err := r.client.Autofill(flatTx)
125+
if err != nil {
126+
return nil, "", err
127+
}
122128
}
123129

124130
blob, hash, err := signer.Sign(*flatTx)
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package integration
2+
3+
import (
4+
"testing"
5+
6+
"github.com/Peersyst/xrpl-go/xrpl/rpc"
7+
"github.com/Peersyst/xrpl-go/xrpl/testutil/integration"
8+
"github.com/Peersyst/xrpl-go/xrpl/transaction"
9+
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
10+
"github.com/Peersyst/xrpl-go/xrpl/wallet"
11+
"github.com/Peersyst/xrpl-go/xrpl/websocket"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
type BatchTest struct {
16+
Name string
17+
Batch *transaction.Batch
18+
ExpectedError string
19+
}
20+
21+
var (
22+
CreatePaymentTx = func(sender, receiver *wallet.Wallet, amount types.CurrencyAmount) *transaction.Payment {
23+
return &transaction.Payment{
24+
BaseTx: transaction.BaseTx{
25+
Account: sender.GetAddress(),
26+
TransactionType: transaction.PaymentTx,
27+
Flags: 0x40000000,
28+
},
29+
Amount: amount,
30+
Destination: receiver.GetAddress(),
31+
}
32+
}
33+
)
34+
35+
func TestIntegrationBatch_Websocket(t *testing.T) {
36+
env := integration.GetWebsocketEnv(t)
37+
client := websocket.NewClient(websocket.NewClientConfig().WithHost(env.Host).WithFaucetProvider(env.FaucetProvider))
38+
39+
runner := integration.NewRunner(t, client, &integration.RunnerConfig{
40+
WalletCount: 3,
41+
})
42+
43+
err := runner.Setup()
44+
require.NoError(t, err)
45+
defer runner.Teardown()
46+
47+
sender := runner.GetWallet(0)
48+
receiver := runner.GetWallet(1)
49+
50+
tt := []BatchTest{
51+
{
52+
Name: "pass - valid batch transaction",
53+
Batch: &transaction.Batch{
54+
BaseTx: transaction.BaseTx{
55+
Account: runner.GetWallet(0).GetAddress(),
56+
TransactionType: transaction.BatchTx,
57+
},
58+
RawTransactions: []types.RawTransaction{
59+
{
60+
RawTransaction: CreatePaymentTx(sender, receiver, types.XRPCurrencyAmount(1)).Flatten(),
61+
},
62+
{
63+
RawTransaction: CreatePaymentTx(sender, receiver, types.XRPCurrencyAmount(1)).Flatten(),
64+
},
65+
},
66+
},
67+
},
68+
}
69+
70+
for _, tc := range tt {
71+
t.Run(tc.Name, func(t *testing.T) {
72+
tc.Batch.SetAllOrNothingFlag()
73+
flatTx := tc.Batch.Flatten()
74+
err := client.Autofill(&flatTx)
75+
76+
require.NoError(t, err)
77+
78+
_, err = runner.TestTransaction(&flatTx, sender, "tesSUCCESS", &integration.TestTransactionOptions{
79+
SkipAutofill: true,
80+
})
81+
require.NoError(t, err)
82+
})
83+
}
84+
}
85+
86+
func TestIntegrationBatchMultisign_Websocket(t *testing.T) {
87+
env := integration.GetWebsocketEnv(t)
88+
client := websocket.NewClient(websocket.NewClientConfig().WithHost(env.Host).WithFaucetProvider(env.FaucetProvider))
89+
90+
runner := integration.NewRunner(t, client, &integration.RunnerConfig{
91+
WalletCount: 3,
92+
})
93+
94+
err := runner.Setup()
95+
require.NoError(t, err)
96+
defer runner.Teardown()
97+
98+
sender := runner.GetWallet(0)
99+
sender2 := runner.GetWallet(1)
100+
receiver := runner.GetWallet(2)
101+
102+
tt := []BatchTest{
103+
{
104+
Name: "pass - valid batch transaction",
105+
Batch: &transaction.Batch{
106+
BaseTx: transaction.BaseTx{
107+
Account: sender.GetAddress(),
108+
TransactionType: transaction.BatchTx,
109+
},
110+
RawTransactions: []types.RawTransaction{
111+
{
112+
RawTransaction: CreatePaymentTx(sender, receiver, types.XRPCurrencyAmount(1)).Flatten(),
113+
},
114+
{
115+
RawTransaction: CreatePaymentTx(sender2, receiver, types.XRPCurrencyAmount(1)).Flatten(),
116+
},
117+
},
118+
},
119+
},
120+
}
121+
122+
for _, tc := range tt {
123+
t.Run(tc.Name, func(t *testing.T) {
124+
tc.Batch.SetAllOrNothingFlag()
125+
flatTx := tc.Batch.Flatten()
126+
err := client.AutofillMultisigned(&flatTx, 1)
127+
require.NoError(t, err)
128+
129+
err = wallet.SignMultiBatch(*sender2, &flatTx, nil)
130+
131+
require.NoError(t, err)
132+
133+
_, err = runner.TestTransaction(&flatTx, sender, "tesSUCCESS", &integration.TestTransactionOptions{
134+
SkipAutofill: true,
135+
})
136+
137+
require.NoError(t, err)
138+
})
139+
}
140+
}
141+
142+
func TestIntegrationBatch_RPC(t *testing.T) {
143+
env := integration.GetRPCEnv(t)
144+
clientCfg, err := rpc.NewClientConfig(env.Host, rpc.WithFaucetProvider(env.FaucetProvider))
145+
require.NoError(t, err)
146+
client := rpc.NewClient(clientCfg)
147+
148+
runner := integration.NewRunner(t, client, integration.NewRunnerConfig(
149+
integration.WithWallets(3),
150+
))
151+
152+
err = runner.Setup()
153+
require.NoError(t, err)
154+
defer runner.Teardown()
155+
156+
sender := runner.GetWallet(0)
157+
receiver := runner.GetWallet(1)
158+
159+
tt := []BatchTest{
160+
{
161+
Name: "pass - valid batch transaction",
162+
Batch: &transaction.Batch{
163+
BaseTx: transaction.BaseTx{
164+
Account: runner.GetWallet(0).GetAddress(),
165+
TransactionType: transaction.BatchTx,
166+
},
167+
RawTransactions: []types.RawTransaction{
168+
{
169+
RawTransaction: CreatePaymentTx(sender, receiver, types.XRPCurrencyAmount(1)).Flatten(),
170+
},
171+
{
172+
RawTransaction: CreatePaymentTx(sender, receiver, types.XRPCurrencyAmount(1)).Flatten(),
173+
},
174+
},
175+
},
176+
},
177+
}
178+
179+
for _, tc := range tt {
180+
t.Run(tc.Name, func(t *testing.T) {
181+
tc.Batch.SetAllOrNothingFlag()
182+
flatTx := tc.Batch.Flatten()
183+
err := client.Autofill(&flatTx)
184+
185+
require.NoError(t, err)
186+
187+
_, err = runner.TestTransaction(&flatTx, sender, "tesSUCCESS", &integration.TestTransactionOptions{
188+
SkipAutofill: true,
189+
})
190+
require.NoError(t, err)
191+
})
192+
}
193+
}
194+
195+
func TestIntegrationBatchMultisign_RPC(t *testing.T) {
196+
env := integration.GetRPCEnv(t)
197+
clientCfg, err := rpc.NewClientConfig(env.Host, rpc.WithFaucetProvider(env.FaucetProvider))
198+
require.NoError(t, err)
199+
client := rpc.NewClient(clientCfg)
200+
201+
runner := integration.NewRunner(t, client, integration.NewRunnerConfig(
202+
integration.WithWallets(3),
203+
))
204+
205+
err = runner.Setup()
206+
require.NoError(t, err)
207+
defer runner.Teardown()
208+
209+
sender := runner.GetWallet(0)
210+
sender2 := runner.GetWallet(1)
211+
receiver := runner.GetWallet(2)
212+
213+
tt := []BatchTest{
214+
{
215+
Name: "pass - valid batch transaction",
216+
Batch: &transaction.Batch{
217+
BaseTx: transaction.BaseTx{
218+
Account: sender.GetAddress(),
219+
TransactionType: transaction.BatchTx,
220+
},
221+
RawTransactions: []types.RawTransaction{
222+
{
223+
RawTransaction: CreatePaymentTx(sender, receiver, types.XRPCurrencyAmount(1)).Flatten(),
224+
},
225+
{
226+
RawTransaction: CreatePaymentTx(sender2, receiver, types.XRPCurrencyAmount(1)).Flatten(),
227+
},
228+
},
229+
},
230+
},
231+
}
232+
233+
for _, tc := range tt {
234+
t.Run(tc.Name, func(t *testing.T) {
235+
tc.Batch.SetAllOrNothingFlag()
236+
flatTx := tc.Batch.Flatten()
237+
err := client.AutofillMultisigned(&flatTx, 1)
238+
require.NoError(t, err)
239+
240+
err = wallet.SignMultiBatch(*sender2, &flatTx, nil)
241+
242+
require.NoError(t, err)
243+
244+
_, err = runner.TestTransaction(&flatTx, sender, "tesSUCCESS", &integration.TestTransactionOptions{
245+
SkipAutofill: true,
246+
})
247+
248+
require.NoError(t, err)
249+
})
250+
}
251+
}

xrpl/transaction/integration/credential/credential_create_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func TestIntegrationCredentialCreateWebsocket(t *testing.T) {
118118
t.Run(tc.Name, func(t *testing.T) {
119119
flatTx := tc.CredentialCreate.Flatten()
120120

121-
_, err = runner.TestTransaction(&flatTx, sender, tc.ExpectedResultCode.String())
121+
_, err = runner.TestTransaction(&flatTx, sender, tc.ExpectedResultCode.String(), nil)
122122
if tc.ExpectedError != "" {
123123
require.Error(t, err)
124124
require.Contains(t, err.Error(), tc.ExpectedError)

0 commit comments

Comments
 (0)