Skip to content

Commit

Permalink
Add ledger entry diff to simulateTransaction response
Browse files Browse the repository at this point in the history
It adds the following field to `SimulateTransactionResponse`

```
	StateDiff       []LedgerEntryDiff            `json:"stateDiff,omitempty"`       // If present, it indicates how the state (ledger entries) will change as a result of the transaction execution.

```

where:

```
// LedgerEntryDiff designates a change in a ledger entry. Before and After cannot be be omitted at the same time.
// If Before is omitted, it constitutes a creation, if After is omitted, it constitutes a delation.
type LedgerEntryDiff struct {
	Before string `json:"before,omitempty"` // LedgerEntry XDR in base64
	After  string `json:"after,omitempty"`  // LedgerEntry XDR in base64
}
```
  • Loading branch information
2opremio committed Apr 2, 2024
1 parent 1f735e2 commit 84ca477
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 25 deletions.
15 changes: 15 additions & 0 deletions cmd/soroban-rpc/internal/methods/simulate_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ type RestorePreamble struct {
MinResourceFee int64 `json:"minResourceFee,string"`
}

// LedgerEntryDiff designates a change in a ledger entry. Before and After cannot be be omitted at the same time.
// If Before is omitted, it constitutes a creation, if After is omitted, it constitutes a delation.
type LedgerEntryDiff struct {
Before string `json:"before,omitempty"` // LedgerEntry XDR in base64
After string `json:"after,omitempty"` // LedgerEntry XDR in base64
}

type SimulateTransactionResponse struct {
Error string `json:"error,omitempty"`
TransactionData string `json:"transactionData,omitempty"` // SorobanTransactionData XDR in base64
Expand All @@ -43,6 +50,7 @@ type SimulateTransactionResponse struct {
Results []SimulateHostFunctionResult `json:"results,omitempty"` // an array of the individual host function call results
Cost SimulateTransactionCost `json:"cost,omitempty"` // the effective cpu and memory cost of the invoked transaction execution.
RestorePreamble *RestorePreamble `json:"restorePreamble,omitempty"` // If present, it indicates that a prior RestoreFootprint is required
StateDiff []LedgerEntryDiff `json:"stateDiff,omitempty"` // If present, it indicates how the state (ledger entries) will change as a result of the transaction execution.
LatestLedger uint32 `json:"latestLedger"`
}

Expand Down Expand Up @@ -149,6 +157,12 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge
}
}

stateDiff := make([]LedgerEntryDiff, len(result.LedgerEntryDiff))
for i := 0; i < len(stateDiff); i++ {
stateDiff[i].Before = base64.StdEncoding.EncodeToString(result.LedgerEntryDiff[i].Before)
stateDiff[i].After = base64.StdEncoding.EncodeToString(result.LedgerEntryDiff[i].After)
}

return SimulateTransactionResponse{
Error: result.Error,
Results: results,
Expand All @@ -161,6 +175,7 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge
},
LatestLedger: latestLedger,
RestorePreamble: restorePreamble,
StateDiff: stateDiff,
}
})
}
Expand Down
17 changes: 17 additions & 0 deletions cmd/soroban-rpc/internal/preflight/preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ type PreflightParameters struct {
EnableDebug bool
}

type XDRDiff struct {
Before []byte // optional before XDR
After []byte // optional after XDR
}

type Preflight struct {
Error string
Events [][]byte // DiagnosticEvents XDR
Expand All @@ -116,6 +121,7 @@ type Preflight struct {
MemoryBytes uint64
PreRestoreTransactionData []byte // SorobanTransactionData XDR
PreRestoreMinFee int64
LedgerEntryDiff []XDRDiff
}

func CXDR(xdr []byte) C.xdr_t {
Expand All @@ -138,6 +144,16 @@ func GoXDRVector(xdrVector C.xdr_vector_t) [][]byte {
return result
}

func GoXDRDiffVector(xdrDiffVector C.xdr_diff_vector_t) []XDRDiff {
result := make([]XDRDiff, xdrDiffVector.len)
inputSlice := unsafe.Slice(xdrDiffVector.array, xdrDiffVector.len)
for i, v := range inputSlice {
result[i].Before = GoXDR(v.before)
result[i].After = GoXDR(v.after)
}
return result
}

func GetPreflight(ctx context.Context, params PreflightParameters) (Preflight, error) {
switch params.OpBody.Type {
case xdr.OperationTypeInvokeHostFunction:
Expand Down Expand Up @@ -259,6 +275,7 @@ func GoPreflight(result *C.preflight_result_t) Preflight {
MemoryBytes: uint64(result.memory_bytes),
PreRestoreTransactionData: GoXDR(result.pre_restore_transaction_data),
PreRestoreMinFee: int64(result.pre_restore_min_fee),
LedgerEntryDiff: GoXDRDiffVector(result.ledger_entry_diff),
}
return preflight
}
8 changes: 8 additions & 0 deletions cmd/soroban-rpc/internal/test/simulate_transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ func TestSimulateTransactionSucceeds(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expectedXdr, resultXdr)

// Check state diff
assert.Len(t, result.StateDiff, 1)
assert.Empty(t, result.StateDiff[0].Before)
assert.NotEmpty(t, result.StateDiff[0].After)
var after xdr.LedgerEntry
assert.NoError(t, xdr.SafeUnmarshalBase64(result.StateDiff[0].After, &after))
assert.Equal(t, xdr.LedgerEntryTypeContractCode, after.Data.Type)

// test operation which does not have a source account
withoutSourceAccountOp := createInstallContractCodeOperation("", contractBinary)
params = txnbuild.TransactionParams{
Expand Down
31 changes: 21 additions & 10 deletions cmd/soroban-rpc/lib/preflight.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,32 @@ typedef struct xdr_vector_t {
size_t len;
} xdr_vector_t;

typedef struct xdr_diff_t {
xdr_t before;
xdr_t after;
} xdr_diff_t;

typedef struct xdr_diff_vector_t {
xdr_diff_t *array;
size_t len;
} xdr_diff_vector_t;

typedef struct resource_config_t {
uint64_t instruction_leeway; // Allow this many extra instructions when budgeting
} resource_config_t;

typedef struct preflight_result_t {
char *error; // Error string in case of error, otherwise null
xdr_vector_t auth; // array of SorobanAuthorizationEntries
xdr_t result; // XDR SCVal
xdr_t transaction_data;
int64_t min_fee; // Minimum recommended resource fee
xdr_vector_t events; // array of XDR DiagnosticEvents
uint64_t cpu_instructions;
uint64_t memory_bytes;
xdr_t pre_restore_transaction_data; // SorobanTransactionData XDR for a prerequired RestoreFootprint operation
int64_t pre_restore_min_fee; // Minimum recommended resource fee for a prerequired RestoreFootprint operation
char *error; // Error string in case of error, otherwise null
xdr_vector_t auth; // array of SorobanAuthorizationEntries
xdr_t result; // XDR SCVal
xdr_t transaction_data;
int64_t min_fee; // Minimum recommended resource fee
xdr_vector_t events; // array of XDR DiagnosticEvents
uint64_t cpu_instructions;
uint64_t memory_bytes;
xdr_t pre_restore_transaction_data; // SorobanTransactionData XDR for a prerequired RestoreFootprint operation
int64_t pre_restore_min_fee; // Minimum recommended resource fee for a prerequired RestoreFootprint operation
xdr_diff_vector_t ledger_entry_diff; // Contains the ledger entry changes which would be caused by the transaction execution
} preflight_result_t;

preflight_result_t *preflight_invoke_hf_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet
Expand Down
98 changes: 83 additions & 15 deletions cmd/soroban-rpc/lib/preflight/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use soroban_env_host::xdr::{
use soroban_env_host::{HostError, LedgerInfo, DEFAULT_XDR_RW_LIMITS};
use soroban_simulation::simulation::{
simulate_extend_ttl_op, simulate_invoke_host_function_op, simulate_restore_op,
InvokeHostFunctionSimulationResult, RestoreOpSimulationResult, SimulationAdjustmentConfig,
InvokeHostFunctionSimulationResult, LedgerEntryDiff, RestoreOpSimulationResult,
SimulationAdjustmentConfig,
};
use soroban_simulation::{AutoRestoringSnapshotSource, NetworkConfig, SnapshotSourceWithArchive};
use std::cell::RefCell;
Expand Down Expand Up @@ -59,10 +60,12 @@ pub struct CXDR {

// It would be nicer to derive Default, but we can't. It errors with:
// The trait bound `*mut u8: std::default::Default` is not satisfied
fn get_default_c_xdr() -> CXDR {
CXDR {
xdr: null_mut(),
len: 0,
impl Default for CXDR {
fn default() -> Self {
CXDR {
xdr: null_mut(),
len: 0,
}
}
}

Expand All @@ -73,10 +76,35 @@ pub struct CXDRVector {
pub len: libc::size_t,
}

fn get_default_c_xdr_vector() -> CXDRVector {
CXDRVector {
array: null_mut(),
len: 0,
impl Default for CXDRVector {
fn default() -> Self {
CXDRVector {
array: null_mut(),
len: 0,
}
}
}

#[repr(C)]
#[derive(Copy, Clone)]
pub struct CXDRDiff {
pub before: CXDR,
pub after: CXDR,
}

#[repr(C)]
#[derive(Copy, Clone)]
pub struct CXDRDiffVector {
pub array: *mut CXDRDiff,
pub len: libc::size_t,
}

impl Default for CXDRDiffVector {
fn default() -> Self {
CXDRDiffVector {
array: null_mut(),
len: 0,
}
}
}

Expand Down Expand Up @@ -107,21 +135,24 @@ pub struct CPreflightResult {
pub pre_restore_transaction_data: CXDR,
// Minimum recommended resource fee for a prerequired RestoreFootprint operation
pub pre_restore_min_fee: i64,
// Contains the ledger entry changes which would be caused by the transaction execution
pub ledger_entry_diff: CXDRDiffVector,
}

impl Default for CPreflightResult {
fn default() -> Self {
Self {
error: CString::new(String::new()).unwrap().into_raw(),
auth: get_default_c_xdr_vector(),
result: get_default_c_xdr(),
transaction_data: get_default_c_xdr(),
auth: Default::default(),
result: Default::default(),
transaction_data: Default::default(),
min_fee: 0,
events: get_default_c_xdr_vector(),
events: Default::default(),
cpu_instructions: 0,
memory_bytes: 0,
pre_restore_transaction_data: get_default_c_xdr(),
pre_restore_transaction_data: Default::default(),
pre_restore_min_fee: 0,
ledger_entry_diff: Default::default(),
}
}
}
Expand Down Expand Up @@ -149,6 +180,7 @@ impl CPreflightResult {
events: xdr_vec_to_c(invoke_hf_result.diagnostic_events),
cpu_instructions: invoke_hf_result.simulated_instructions as u64,
memory_bytes: invoke_hf_result.simulated_memory as u64,
ledger_entry_diff: ledger_entry_diff_vec_to_c(invoke_hf_result.modified_entries),
..Default::default()
};
if let Some(p) = restore_preamble {
Expand Down Expand Up @@ -272,6 +304,7 @@ pub extern "C" fn preflight_footprint_ttl_op(
preflight_footprint_ttl_op_or_maybe_panic(handle, op_body, footprint, ledger_info)
}))
}

fn preflight_footprint_ttl_op_or_maybe_panic(
handle: libc::uintptr_t,
op_body: CXDR,
Expand All @@ -289,14 +322,15 @@ fn preflight_footprint_ttl_op_or_maybe_panic(
match op_body {
OperationBody::ExtendFootprintTtl(extend_op) => {
preflight_extend_ttl_op(extend_op, footprint.read_only.as_slice(), go_storage, &network_config, &ledger_info)
},
}
OperationBody::RestoreFootprint(_) => {
preflight_restore_op(footprint.read_write.as_slice(), go_storage, &network_config, &ledger_info)
}
_ => Err(anyhow!("encountered unsupported operation type: '{:?}', instead of 'ExtendFootprintTtl' or 'RestoreFootprint' operations.",
op_body.discriminant()).into())
}
}

fn preflight_extend_ttl_op(
extend_op: ExtendFootprintTtlOp,
keys_to_extend: &[LedgerKey],
Expand Down Expand Up @@ -382,6 +416,10 @@ fn catch_preflight_panic(op: Box<dyn Fn() -> Result<CPreflightResult>>) -> *mut
Box::into_raw(Box::new(c_preflight_result))
}

// TODO: We could use something like https://github.com/sonos/ffi-convert-rs
// to replace all the free_* , *_to_c and from_c_* functions by implementations of CDrop,
// CReprOf and AsRust

fn xdr_to_c(v: impl WriteXdr) -> CXDR {
let (xdr, len) = vec_to_c_array(v.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap());
CXDR { xdr, len }
Expand All @@ -397,6 +435,13 @@ fn option_xdr_to_c(v: Option<impl WriteXdr>) -> CXDR {
)
}

fn ledger_entry_diff_to_c(v: LedgerEntryDiff) -> CXDRDiff {
CXDRDiff {
before: option_xdr_to_c(v.state_before),
after: option_xdr_to_c(v.state_after),
}
}

fn xdr_vec_to_c(v: Vec<impl WriteXdr>) -> CXDRVector {
let c_v = v.into_iter().map(xdr_to_c).collect();
let (array, len) = vec_to_c_array(c_v);
Expand All @@ -422,6 +467,15 @@ fn vec_to_c_array<T>(mut v: Vec<T>) -> (*mut T, libc::size_t) {
(ptr, len)
}

fn ledger_entry_diff_vec_to_c(modified_entries: Vec<LedgerEntryDiff>) -> CXDRDiffVector {
let c_diffs = modified_entries
.into_iter()
.map(ledger_entry_diff_to_c)
.collect();
let (array, len) = vec_to_c_array(c_diffs);
CXDRDiffVector { array, len }
}

/// .
///
/// # Safety
Expand All @@ -439,6 +493,7 @@ pub unsafe extern "C" fn free_preflight_result(result: *mut CPreflightResult) {
free_c_xdr(boxed.transaction_data);
free_c_xdr_array(boxed.events);
free_c_xdr(boxed.pre_restore_transaction_data);
free_c_xdr_diff_array(boxed.ledger_entry_diff);
}

fn free_c_string(str: *mut libc::c_char) {
Expand Down Expand Up @@ -471,6 +526,19 @@ fn free_c_xdr_array(xdr_array: CXDRVector) {
}
}

fn free_c_xdr_diff_array(xdr_array: CXDRDiffVector) {
if xdr_array.array.is_null() {
return;
}
unsafe {
let v = Vec::from_raw_parts(xdr_array.array, xdr_array.len, xdr_array.len);
for diff in v {
free_c_xdr(diff.before);
free_c_xdr(diff.after);
}
}
}

fn from_c_string(str: *const libc::c_char) -> String {
let c_str = unsafe { CStr::from_ptr(str) };
c_str.to_str().unwrap().to_string()
Expand Down

0 comments on commit 84ca477

Please sign in to comment.