Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,84 @@ func TestSimulateInvokeContractTransactionSucceeds(t *testing.T) {
require.Equal(t, xdr.ScString("auth"), *event.Event.Body.V0.Topics[0].Str)
}

// TestSimulateInvokeContractTransactionUseUpgradedAuth verifies that setting UseUpgradedAuth on the
// simulate request causes recorded authorization entries to use AddressV2 ("v2")
// credentials instead of Address ("v1"). v2 credentials are only emitted by the
// curr soroban-env host, so this requires a protocol that routes to it.
func TestSimulateInvokeContractTransactionUseUpgradedAuth(t *testing.T) {
test := infrastructure.NewTest(t, nil)
if test.GetProtocolVersion() < 27 {
t.Skip("AddressV2 credentials require protocol >= 27 (curr soroban-env host)")
}

_, contractID, _ := test.CreateHelloWorldContract()

contractFnParameterSym := xdr.ScSymbol("world")
authAddrArg := "GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H"
authAccountIDArg := xdr.MustAddress(authAddrArg)
test.SendMasterOperation(&txnbuild.CreateAccount{
Destination: authAddrArg,
Amount: "100000",
SourceAccount: test.MasterAccount().GetAccountID(),
})
params := infrastructure.CreateTransactionParams(
test.MasterAccount(),
infrastructure.CreateInvokeHostOperation(
test.MasterAccount().GetAccountID(),
contractID,
"auth",
xdr.ScVal{
Type: xdr.ScValTypeScvAddress,
Address: &xdr.ScAddress{
Type: xdr.ScAddressTypeScAddressTypeAccount,
AccountId: &authAccountIDArg,
},
},
xdr.ScVal{
Type: xdr.ScValTypeScvSymbol,
Sym: &contractFnParameterSym,
},
),
)
tx, err := txnbuild.NewTransaction(params)
require.NoError(t, err)
txB64, err := tx.Base64()
require.NoError(t, err)

// Baseline: without UseUpgradedAuth, the recorded credential is v1 (Address).
v1Resp, err := test.GetRPCLient().SimulateTransaction(t.Context(),
protocol.SimulateTransactionRequest{Transaction: txB64})
require.NoError(t, err)
require.Empty(t, v1Resp.Error)
v1Auth := firstSimulateAuthEntry(t, v1Resp)
require.Equal(t, xdr.SorobanCredentialsTypeSorobanCredentialsAddress, v1Auth.Credentials.Type)

// With UseUpgradedAuth, the same recorded entry uses v2 (AddressV2) credentials.
v2Resp, err := test.GetRPCLient().SimulateTransaction(t.Context(),
protocol.SimulateTransactionRequest{Transaction: txB64, UseUpgradedAuth: true})
require.NoError(t, err)
require.Empty(t, v2Resp.Error)
v2Auth := firstSimulateAuthEntry(t, v2Resp)
require.Equal(t, xdr.SorobanCredentialsTypeSorobanCredentialsAddressV2, v2Auth.Credentials.Type)
require.NotNil(t, v2Auth.Credentials.AddressV2)
require.Nil(t, v2Auth.Credentials.Address)
// The authorized invocation itself is unchanged -- only the credential format differs.
require.Equal(t, xdr.ScSymbol("auth"), v2Auth.RootInvocation.Function.ContractFn.FunctionName)
}

func firstSimulateAuthEntry(
t *testing.T,
resp protocol.SimulateTransactionResponse,
) xdr.SorobanAuthorizationEntry {
t.Helper()
require.Len(t, resp.Results, 1)
require.NotNil(t, resp.Results[0].AuthXDR)
require.Len(t, *resp.Results[0].AuthXDR, 1)
var auth xdr.SorobanAuthorizationEntry
require.NoError(t, xdr.SafeUnmarshalBase64((*resp.Results[0].AuthXDR)[0], &auth))
return auth
}

func TestSimulateTransactionError(t *testing.T) {
test := infrastructure.NewTest(t, nil)

Expand Down
1 change: 1 addition & 0 deletions cmd/stellar-rpc/internal/methods/simulate_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
Footprint: footprint,
ResourceConfig: resourceConfig,
AuthMode: request.AuthMode,
UseUpgradedAuth: request.UseUpgradedAuth,

Check failure on line 373 in cmd/stellar-rpc/internal/methods/simulate_transaction.go

View workflow job for this annotation

GitHub Actions / Unit tests (ubuntu-22.04)

request.UseUpgradedAuth undefined (type "github.com/stellar/go-stellar-sdk/protocols/rpc".SimulateTransactionRequest has no field or method UseUpgradedAuth)

Check failure on line 373 in cmd/stellar-rpc/internal/methods/simulate_transaction.go

View workflow job for this annotation

GitHub Actions / Build (macos-latest, aarch64-apple-darwin, arm64)

request.UseUpgradedAuth undefined (type "github.com/stellar/go-stellar-sdk/protocols/rpc".SimulateTransactionRequest has no field or method UseUpgradedAuth)

Check failure on line 373 in cmd/stellar-rpc/internal/methods/simulate_transaction.go

View workflow job for this annotation

GitHub Actions / Integration tests (P26) / Integration tests

request.UseUpgradedAuth undefined (type "github.com/stellar/go-stellar-sdk/protocols/rpc".SimulateTransactionRequest has no field or method UseUpgradedAuth)

Check failure on line 373 in cmd/stellar-rpc/internal/methods/simulate_transaction.go

View workflow job for this annotation

GitHub Actions / golangci-lint

request.UseUpgradedAuth undefined (type "github.com/stellar/go-stellar-sdk/protocols/rpc".SimulateTransactionRequest has no field or method UseUpgradedAuth) (typecheck)

Check failure on line 373 in cmd/stellar-rpc/internal/methods/simulate_transaction.go

View workflow job for this annotation

GitHub Actions / golangci-lint

request.UseUpgradedAuth undefined (type "github.com/stellar/go-stellar-sdk/protocols/rpc".SimulateTransactionRequest has no field or method UseUpgradedAuth)) (typecheck)

Check failure on line 373 in cmd/stellar-rpc/internal/methods/simulate_transaction.go

View workflow job for this annotation

GitHub Actions / Integration tests (P27) / Integration tests

request.UseUpgradedAuth undefined (type "github.com/stellar/go-stellar-sdk/protocols/rpc".SimulateTransactionRequest has no field or method UseUpgradedAuth)
ProtocolVersion: protocolVersion,
LedgerEntryGetter: ledgerEntryGetter,
LedgerSeq: latestLedger,
Expand Down
113 changes: 113 additions & 0 deletions cmd/stellar-rpc/internal/methods/simulate_transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/creachadair/jrpc2"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

protocol "github.com/stellar/go-stellar-sdk/protocols/rpc"
Expand Down Expand Up @@ -234,3 +235,115 @@ func feeBumpExtendFootprintMissingSorobanData(t *testing.T) xdr.TransactionEnvel
FeeBump: &feeBumpEnvelope,
}
}

// capturingPreflightGetter records the GetterParameters it receives so tests can
// assert on how the handler populates them, and returns an empty (but valid)
// Preflight result.
type capturingPreflightGetter struct {
called bool
params preflight.GetterParameters
}

func (c *capturingPreflightGetter) GetPreflight(
_ context.Context,
params preflight.GetterParameters,
) (preflight.Preflight, error) {
c.called = true
c.params = params
return preflight.Preflight{}, nil
}

func invokeHostFunctionEnvelope(t *testing.T) xdr.TransactionEnvelope {
t.Helper()

sourceAccountID := xdr.MustAddress("GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON")
source := (&sourceAccountID).ToMuxedAccount()
contractID := xdr.ContractId{0xa, 0xb, 0xc}
argSymbol := xdr.ScSymbol("world")

op := xdr.Operation{
Body: xdr.OperationBody{
Type: xdr.OperationTypeInvokeHostFunction,
InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{
HostFunction: xdr.HostFunction{
Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract,
InvokeContract: &xdr.InvokeContractArgs{
ContractAddress: xdr.ScAddress{
Type: xdr.ScAddressTypeScAddressTypeContract,
ContractId: &contractID,
},
FunctionName: "hello",
Args: []xdr.ScVal{{Type: xdr.ScValTypeScvSymbol, Sym: &argSymbol}},
},
},
},
},
}

tx := xdr.Transaction{
SourceAccount: source,
Fee: xdr.Uint32(100),
SeqNum: xdr.SequenceNumber(1),
Cond: xdr.Preconditions{Type: xdr.PreconditionTypePrecondNone},
Memo: xdr.Memo{Type: xdr.MemoTypeMemoNone},
Operations: []xdr.Operation{op},
Ext: xdr.TransactionExt{V: 0},
}

return xdr.TransactionEnvelope{
Type: xdr.EnvelopeTypeEnvelopeTypeTx,
V1: &xdr.TransactionV1Envelope{Tx: tx},
}
}

// TestSimulateTransactionThreadsUseUpgradedAuth verifies that the request's useUpgradedAuth flag is
// forwarded into the preflight GetterParameters (and defaults to false when omitted).
func TestSimulateTransactionThreadsUseUpgradedAuth(t *testing.T) {
closeMeta := xdr.LedgerCloseMeta{
V: 1,
V1: &xdr.LedgerCloseMetaV1{
LedgerHeader: xdr.LedgerHeaderHistoryEntry{
Header: xdr.LedgerHeader{LedgerVersion: 23},
},
TotalByteSizeOfLiveSorobanState: 100,
},
}

txB64, err := xdr.MarshalBase64(invokeHostFunctionEnvelope(t))
require.NoError(t, err)

for _, tc := range []struct {
name string
paramsField string
expected bool
}{
{name: "useUpgradedAuth true is forwarded", paramsField: `, "useUpgradedAuth": true`, expected: true},
{name: "useUpgradedAuth false is forwarded", paramsField: `, "useUpgradedAuth": false`, expected: false},
{name: "useUpgradedAuth omitted defaults to false", paramsField: "", expected: false},
} {
t.Run(tc.name, func(t *testing.T) {
ledgerReader := &MockLedgerReader{}
ledgerReader.On("GetLatestLedgerSequence", mock.Anything).Return(uint32(2), nil)
ledgerReader.On("GetLedger", mock.Anything, uint32(2)).Return(closeMeta, true, nil)

getter := &capturingPreflightGetter{}
handler := NewSimulateTransactionHandler(log.New(), ledgerReader, nil, getter, xdr.DecodeOptions{})

requestJSON := fmt.Sprintf(`{
"jsonrpc": "2.0",
"id": 1,
"method": "simulateTransaction",
"params": { "transaction": "%s"%s }
}`, txB64, tc.paramsField)
requests, err := jrpc2.ParseRequests([]byte(requestJSON))
require.NoError(t, err)
require.Len(t, requests, 1)

_, err = handler(t.Context(), requests[0].ToRequest())
require.NoError(t, err)

require.True(t, getter.called, "GetPreflight should have been called")
require.Equal(t, tc.expected, getter.params.UseUpgradedAuth)
})
}
}
2 changes: 2 additions & 0 deletions cmd/stellar-rpc/internal/preflight/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ type GetterParameters struct {
Footprint xdr.LedgerFootprint
ResourceConfig protocol.ResourceConfig
AuthMode string
UseUpgradedAuth bool
ProtocolVersion uint32
LedgerEntryGetter ledgerentries.LedgerEntryGetter
LedgerSeq uint32
Expand All @@ -188,6 +189,7 @@ func (pwp *WorkerPool) GetPreflight(ctx context.Context, params GetterParameters
ResourceConfig: params.ResourceConfig,
EnableDebug: pwp.enableDebug,
AuthMode: params.AuthMode,
UseUpgradedAuth: params.UseUpgradedAuth,
ProtocolVersion: params.ProtocolVersion,
}
resultC := make(chan workerResult)
Expand Down
2 changes: 2 additions & 0 deletions cmd/stellar-rpc/internal/preflight/preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type Parameters struct {
ResourceConfig protocol.ResourceConfig
EnableDebug bool
AuthMode string
UseUpgradedAuth bool
ProtocolVersion uint32
}

Expand Down Expand Up @@ -268,6 +269,7 @@ func getInvokeHostFunctionPreflight(ctx context.Context, params Parameters) (Pre
resourceConfig,
C.bool(params.EnableDebug),
C.uint32_t(authMode),
C.bool(params.UseUpgradedAuth),
)

return GoPreflight(res), nil
Expand Down
16 changes: 16 additions & 0 deletions cmd/stellar-rpc/internal/preflight/preflight_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,19 @@ func BenchmarkGetPreflight(b *testing.B) {
require.Empty(b, result.Error)
}
}

// TestGetPreflightUseUpgradedAuthSilentlyIgnoredOnPrevProtocol locks in the agreed
// behavior that requesting v2 (AddressV2) credentials on a protocol served by
// the prev soroban-env host -- which predates v2 credentials -- is silently
// ignored rather than rejected: v1 behavior is used and no error is returned.
// It runs at protocol 26, which routes to the prev host. (Asserting the
// credential version itself requires an auth-recording contract on the curr
// host; that is covered by the integration tests.)
func TestGetPreflightUseUpgradedAuthSilentlyIgnoredOnPrevProtocol(t *testing.T) {
const prevHostProtocol = 26
params := getPreflightParameters(t, prevHostProtocol)
params.UseUpgradedAuth = true
result, err := GetPreflight(t.Context(), params)
require.NoError(t, err)
require.Empty(t, result.Error)
}
3 changes: 2 additions & 1 deletion cmd/stellar-rpc/lib/preflight.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ preflight_result_t *preflight_invoke_hf_op(uintptr_t handle, // Go Handle to for
const ledger_info_t ledger_info,
const resource_config_t resource_config,
bool enable_debug,
const uint32_t auth_mode);
const uint32_t auth_mode,
bool use_upgraded_auth); // record AddressV2 ("upgraded") credentials

preflight_result_t *preflight_footprint_ttl_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet
const xdr_t op_body, // OperationBody XDR
Expand Down
24 changes: 13 additions & 11 deletions cmd/stellar-rpc/lib/preflight/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,16 @@ mod curr {

pub(crate) const PROTOCOL: u32 = soroban_env_host::meta::INTERFACE_VERSION.protocol;

// Builds the recording auth mode for this protocol version. The shape of
// `RecordingInvocationAuthMode::Recording` differs between soroban versions,
// so each version-specific module provides its own constructor and
// `shared.rs` calls into it via `super::`. From protocol 27
// the recording params also carry `use_address_v2`, which we leave `false`
// at this layer until v28.
pub(crate) fn recording_auth_mode(
// Constructs the recording auth mode for this protocol version. The current
// protocol's soroban-env can emit either v1 (`Address`) or v2 (`AddressV2`)
// credentials, selected by `use_upgraded_auth`.
pub(crate) fn make_recording_auth_mode(
disable_non_root_auth: bool,
use_upgraded_auth: bool,
) -> soroban_env_host::e2e_invoke::RecordingInvocationAuthMode {
soroban_env_host::e2e_invoke::RecordingInvocationAuthMode::recording(
disable_non_root_auth,
false,
use_upgraded_auth,
)
}
}
Expand All @@ -68,10 +66,11 @@ mod prev {

pub(crate) const PROTOCOL: u32 = soroban_env_host::meta::INTERFACE_VERSION.protocol;

// See the matching `curr::recording_auth_mode`. The previous soroban version
// models the recording auth mode as a bare `disable_non_root_auth` bool.
pub(crate) fn recording_auth_mode(
// The previous protocol's soroban-env predates v2 Address credentials, so
// `use_upgraded_auth` is ignored and v1 `Address` credentials are always used.
pub(crate) fn make_recording_auth_mode(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the obsolete prev recording helper

Adding make_recording_auth_mode here while switching shared.rs to call that new name leaves the existing prev::recording_auth_mode above with no callers. This repo’s Makefile sets RUSTFLAGS=-Dwarnings -Dclippy::all -Dclippy::pedantic, so the resulting dead_code warning is promoted to a Rust build failure once the lockfile parses; remove or rename the old helper instead of keeping both.

Useful? React with 👍 / 👎.

disable_non_root_auth: bool,
_use_upgraded_auth: bool,
) -> soroban_env_host::e2e_invoke::RecordingInvocationAuthMode {
soroban_env_host::e2e_invoke::RecordingInvocationAuthMode::Recording(disable_non_root_auth)
}
Expand Down Expand Up @@ -203,6 +202,7 @@ pub extern "C" fn preflight_invoke_hf_op(
resource_config: CResourceConfig,
enable_debug: bool,
auth_mode: u32,
use_upgraded_auth: bool,
) -> *mut CPreflightResult {
let proto = ledger_info.protocol_version;
catch_preflight_panic(&move || {
Expand All @@ -215,6 +215,7 @@ pub extern "C" fn preflight_invoke_hf_op(
resource_config,
enable_debug,
auth_mode.into(),
use_upgraded_auth,
)
} else if proto == curr::PROTOCOL {
curr::shared::preflight_invoke_hf_op_or_maybe_panic(
Expand All @@ -225,6 +226,7 @@ pub extern "C" fn preflight_invoke_hf_op(
resource_config,
enable_debug,
auth_mode.into(),
use_upgraded_auth,
)
} else {
bail!("unsupported protocol version: {proto}")
Expand Down
5 changes: 3 additions & 2 deletions cmd/stellar-rpc/lib/preflight/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub(crate) fn preflight_invoke_hf_op_or_maybe_panic(
resource_config: CResourceConfig,
enable_debug: bool,
auth_mode: AuthMode,
use_upgraded_auth: bool,
) -> Result<CPreflightResult> {
let invoke_hf_op =
InvokeHostFunctionOp::from_xdr(unsafe { from_c_xdr(invoke_hf_op) }, DEFAULT_XDR_RW_LIMITS)
Expand Down Expand Up @@ -149,8 +150,8 @@ pub(crate) fn preflight_invoke_hf_op_or_maybe_panic(
// ignore the list entirely even if it's present.
let auth_mode = match auth_mode {
AuthMode::Enforce => RecordingInvocationAuthMode::Enforcing(auth_entries),
AuthMode::Record => super::recording_auth_mode(true),
AuthMode::RecordAllowNonroot => super::recording_auth_mode(false),
AuthMode::Record => super::make_recording_auth_mode(true, use_upgraded_auth),
AuthMode::RecordAllowNonroot => super::make_recording_auth_mode(false, use_upgraded_auth),
};

preflight_invoke_hf_op_post_autorestore_or_maybe_panic(
Expand Down
Loading