Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ledger entry diff to simulateTransaction response #120

Merged
merged 5 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions .github/actions/setup-integration-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ runs:
sudo apt-get remove -y moby-compose
sudo apt-get install -y docker-compose-plugin

# add alias for docker compose
ln -f -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin/docker-compose

echo "Docker Compose Version:"
docker-compose version

Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/soroban-rpc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ jobs:
matrix:
os: [ubuntu-20.04, ubuntu-22.04]
go: [1.22]
test: ['.*CLI.*', '^Test(([^C])|(C[^L])|(CL[^I])).*$']
runs-on: ${{ matrix.os }}
env:
SOROBAN_RPC_INTEGRATION_TESTS_ENABLED: true
Expand All @@ -127,4 +126,4 @@ jobs:
- name: Run Soroban RPC Integration Tests
run: |
make install_rust
go test -race -run '${{ matrix.test }}' -timeout 60m -v ./cmd/soroban-rpc/internal/test/...
go test -race -timeout 60m -v ./cmd/soroban-rpc/internal/test/...
112 changes: 112 additions & 0 deletions cmd/soroban-rpc/internal/methods/simulate_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package methods
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/handler"
Expand Down Expand Up @@ -34,6 +37,108 @@ type RestorePreamble struct {
TransactionData string `json:"transactionData"` // SorobanTransactionData XDR in base64
MinResourceFee int64 `json:"minResourceFee,string"`
}
type LedgerEntryChangeType int

const (
LedgerEntryChangeTypeCreated LedgerEntryChangeType = iota + 1
LedgerEntryChangeTypeUpdated
LedgerEntryChangeTypeDeleted
)

var (
LedgerEntryChangeTypeName = map[LedgerEntryChangeType]string{
LedgerEntryChangeTypeCreated: "created",
LedgerEntryChangeTypeUpdated: "updated",
LedgerEntryChangeTypeDeleted: "deleted",
}
LedgerEntryChangeTypeValue = map[string]LedgerEntryChangeType{
"created": LedgerEntryChangeTypeCreated,
"updated": LedgerEntryChangeTypeUpdated,
"deleted": LedgerEntryChangeTypeDeleted,
}
)

func (l LedgerEntryChangeType) String() string {
return LedgerEntryChangeTypeName[l]
}

func (l LedgerEntryChangeType) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}

func (l *LedgerEntryChangeType) Parse(s string) error {
s = strings.TrimSpace(strings.ToLower(s))
value, ok := LedgerEntryChangeTypeValue[s]
if !ok {
return fmt.Errorf("%q is not a valid ledger entry change type", s)
}
*l = value
return nil
}

func (l *LedgerEntryChangeType) UnmarshalJSON(data []byte) (err error) {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
return l.Parse(s)
}

func (l *LedgerEntryChange) FromXDRDiff(diff preflight.XDRDiff) error {
beforePresent := len(diff.Before) > 0
afterPresent := len(diff.After) > 0
var (
entryXDR []byte
changeType LedgerEntryChangeType
)
switch {
case beforePresent:
entryXDR = diff.Before
if afterPresent {
changeType = LedgerEntryChangeTypeUpdated
} else {
2opremio marked this conversation as resolved.
Show resolved Hide resolved
changeType = LedgerEntryChangeTypeDeleted
}
case afterPresent:
entryXDR = diff.After
changeType = LedgerEntryChangeTypeCreated
default:
return errors.New("missing before and after")
}
var entry xdr.LedgerEntry

if err := xdr.SafeUnmarshal(entryXDR, &entry); err != nil {
return err
}
key, err := entry.LedgerKey()
if err != nil {
return err
}
keyB64, err := xdr.MarshalBase64(key)
if err != nil {
return err
}
l.Type = changeType
l.Key = keyB64
if beforePresent {
before := base64.StdEncoding.EncodeToString(diff.Before)
l.Before = &before
}
if afterPresent {
after := base64.StdEncoding.EncodeToString(diff.After)
l.After = &after
}
return nil
}

// LedgerEntryChange 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 LedgerEntryChange struct {
Type LedgerEntryChangeType
Key string // LedgerEntryKey in base64
Before *string `json:"before"` // LedgerEntry XDR in base64
After *string `json:"after"` // LedgerEntry XDR in base64
}

type SimulateTransactionResponse struct {
Error string `json:"error,omitempty"`
Expand All @@ -43,6 +148,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
StateChanges []LedgerEntryChange `json:"stateChanges,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 +255,11 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge
}
}

stateChanges := make([]LedgerEntryChange, len(result.LedgerEntryDiff))
for i := 0; i < len(stateChanges); i++ {
stateChanges[i].FromXDRDiff(result.LedgerEntryDiff[i])
}

return SimulateTransactionResponse{
Error: result.Error,
Results: results,
Expand All @@ -161,6 +272,7 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge
},
LatestLedger: latestLedger,
RestorePreamble: restorePreamble,
StateChanges: stateChanges,
}
})
}
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
}
14 changes: 14 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,20 @@ func TestSimulateTransactionSucceeds(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expectedXdr, resultXdr)

// Check state diff
assert.Len(t, result.StateChanges, 1)
assert.Nil(t, result.StateChanges[0].Before)
assert.NotNil(t, result.StateChanges[0].After)
assert.Equal(t, methods.LedgerEntryChangeTypeCreated, result.StateChanges[0].Type)
var after xdr.LedgerEntry
assert.NoError(t, xdr.SafeUnmarshalBase64(*result.StateChanges[0].After, &after))
assert.Equal(t, xdr.LedgerEntryTypeContractCode, after.Data.Type)
entryKey, err := after.LedgerKey()
assert.NoError(t, err)
entryKeyB64, err := xdr.MarshalBase64(entryKey)
assert.NoError(t, err)
assert.Equal(t, entryKeyB64, result.StateChanges[0].Key)

// 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
Loading
Loading