Skip to content

Commit a431ae0

Browse files
authored
Add defensive coding and matching unit tests for fee bumps (#605)
1 parent 5689333 commit a431ae0

File tree

2 files changed

+122
-2
lines changed

2 files changed

+122
-2
lines changed

cmd/stellar-rpc/internal/methods/simulate_transaction.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,34 @@ func formatResponse(preflight preflight.Preflight,
249249
return simResp, nil
250250
}
251251

252+
func sorobanDataFromEnvelope(txEnvelope xdr.TransactionEnvelope) (*xdr.SorobanTransactionData, bool) {
253+
switch txEnvelope.Type {
254+
case xdr.EnvelopeTypeEnvelopeTypeTx:
255+
if txEnvelope.V1 == nil {
256+
return nil, false
257+
}
258+
return sorobanDataFromTx(txEnvelope.V1.Tx)
259+
case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump:
260+
if txEnvelope.FeeBump == nil {
261+
return nil, false
262+
}
263+
inner := txEnvelope.FeeBump.Tx.InnerTx
264+
if inner.Type != xdr.EnvelopeTypeEnvelopeTypeTx || inner.V1 == nil {
265+
return nil, false
266+
}
267+
return sorobanDataFromTx(inner.V1.Tx)
268+
default:
269+
return nil, false
270+
}
271+
}
272+
273+
func sorobanDataFromTx(tx xdr.Transaction) (*xdr.SorobanTransactionData, bool) {
274+
if tx.Ext.V != 1 || tx.Ext.SorobanData == nil {
275+
return nil, false
276+
}
277+
return tx.Ext.SorobanData, true
278+
}
279+
252280
// NewSimulateTransactionHandler returns a JSON rpc handler to run preflight simulations
253281
//
254282
//nolint:cyclop
@@ -297,13 +325,14 @@ func NewSimulateTransactionHandler(logger *log.Entry,
297325
switch op.Body.Type {
298326
case xdr.OperationTypeInvokeHostFunction: // no-op
299327
case xdr.OperationTypeExtendFootprintTtl, xdr.OperationTypeRestoreFootprint:
300-
if txEnvelope.Type != xdr.EnvelopeTypeEnvelopeTypeTx && txEnvelope.V1.Tx.Ext.V != 1 {
328+
sorobanData, ok := sorobanDataFromEnvelope(txEnvelope)
329+
if !ok {
301330
return protocol.SimulateTransactionResponse{
302331
Error: "To perform a SimulateTransaction for ExtendFootprintTtl or RestoreFootprint operations," +
303332
" SorobanTransactionData must be provided",
304333
}
305334
}
306-
footprint = txEnvelope.V1.Tx.Ext.SorobanData.Resources.Footprint
335+
footprint = sorobanData.Resources.Footprint
307336

308337
default:
309338
return protocol.SimulateTransactionResponse{

cmd/stellar-rpc/internal/methods/simulate_transaction_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package methods
22

33
import (
4+
"context"
45
"encoding/base64"
56
"encoding/json"
7+
"fmt"
68
"testing"
79

10+
"github.com/creachadair/jrpc2"
811
"github.com/stretchr/testify/require"
912

1013
protocol "github.com/stellar/go-stellar-sdk/protocols/rpc"
14+
"github.com/stellar/go-stellar-sdk/support/log"
1115
"github.com/stellar/go-stellar-sdk/xdr"
1216

1317
"github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/preflight"
@@ -142,3 +146,90 @@ func TestLedgerEntryChange(t *testing.T) {
142146
require.ErrorIs(t, err, errMissingDiff)
143147
require.Equal(t, protocol.LedgerEntryChange{}, change)
144148
}
149+
150+
type panicPreflightGetter struct{}
151+
152+
func (panicPreflightGetter) GetPreflight(
153+
context.Context,
154+
preflight.GetterParameters,
155+
) (preflight.Preflight, error) {
156+
panic("unexpected GetPreflight call")
157+
}
158+
159+
func TestSimulateTransactionFeeBumpMissingSorobanData(t *testing.T) {
160+
logger := log.New()
161+
ledgerReader := &MockLedgerReader{}
162+
handler := NewSimulateTransactionHandler(logger, ledgerReader, nil, panicPreflightGetter{})
163+
164+
txEnvelope := feeBumpExtendFootprintMissingSorobanData(t)
165+
txB64, err := xdr.MarshalBase64(txEnvelope)
166+
require.NoError(t, err)
167+
168+
requestJSON := fmt.Sprintf(`{
169+
"jsonrpc": "2.0",
170+
"id": 1,
171+
"method": "simulateTransaction",
172+
"params": { "transaction": "%s" }
173+
}`, txB64)
174+
requests, err := jrpc2.ParseRequests([]byte(requestJSON))
175+
require.NoError(t, err)
176+
require.Len(t, requests, 1)
177+
178+
resp, err := handler(t.Context(), requests[0].ToRequest())
179+
require.NoError(t, err)
180+
181+
simResp, ok := resp.(protocol.SimulateTransactionResponse)
182+
require.True(t, ok)
183+
require.Equal(t,
184+
"To perform a SimulateTransaction for ExtendFootprintTtl or RestoreFootprint operations,"+
185+
" SorobanTransactionData must be provided",
186+
simResp.Error,
187+
)
188+
}
189+
190+
func feeBumpExtendFootprintMissingSorobanData(t *testing.T) xdr.TransactionEnvelope {
191+
t.Helper()
192+
193+
sourceAccountID := xdr.MustAddress("GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON")
194+
source := (&sourceAccountID).ToMuxedAccount()
195+
feeSourceAccountID := xdr.MustAddress("GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON")
196+
feeSource := (&feeSourceAccountID).ToMuxedAccount()
197+
198+
op := xdr.Operation{
199+
Body: xdr.OperationBody{
200+
Type: xdr.OperationTypeExtendFootprintTtl,
201+
ExtendFootprintTtlOp: &xdr.ExtendFootprintTtlOp{
202+
Ext: xdr.ExtensionPoint{V: 0},
203+
ExtendTo: xdr.Uint32(100),
204+
},
205+
},
206+
}
207+
208+
tx := xdr.Transaction{
209+
SourceAccount: source,
210+
Fee: xdr.Uint32(100),
211+
SeqNum: xdr.SequenceNumber(1),
212+
Cond: xdr.Preconditions{Type: xdr.PreconditionTypePrecondNone},
213+
Memo: xdr.Memo{Type: xdr.MemoTypeMemoNone},
214+
Operations: []xdr.Operation{op},
215+
Ext: xdr.TransactionExt{V: 0},
216+
}
217+
218+
innerV1 := xdr.TransactionV1Envelope{Tx: tx}
219+
innerTx := xdr.FeeBumpTransactionInnerTx{
220+
Type: xdr.EnvelopeTypeEnvelopeTypeTx,
221+
V1: &innerV1,
222+
}
223+
feeBumpTx := xdr.FeeBumpTransaction{
224+
FeeSource: feeSource,
225+
Fee: xdr.Int64(1000),
226+
InnerTx: innerTx,
227+
Ext: xdr.FeeBumpTransactionExt{V: 0},
228+
}
229+
feeBumpEnvelope := xdr.FeeBumpTransactionEnvelope{Tx: feeBumpTx}
230+
231+
return xdr.TransactionEnvelope{
232+
Type: xdr.EnvelopeTypeEnvelopeTypeTxFeeBump,
233+
FeeBump: &feeBumpEnvelope,
234+
}
235+
}

0 commit comments

Comments
 (0)