Skip to content

Commit

Permalink
soroban-rpc: libpreflight: Remove base64 encoding between Go and Rust (
Browse files Browse the repository at this point in the history
  • Loading branch information
2opremio authored Aug 23, 2023
1 parent 3758c2e commit ff5ffa1
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 214 deletions.
23 changes: 16 additions & 7 deletions cmd/soroban-rpc/internal/methods/simulate_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package methods

import (
"context"
"encoding/base64"
"fmt"

"github.com/creachadair/jrpc2"
Expand Down Expand Up @@ -121,25 +122,25 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge
}

var results []SimulateHostFunctionResult
if result.Result != "" {
if len(result.Result) != 0 {
results = append(results, SimulateHostFunctionResult{
XDR: result.Result,
Auth: result.Auth,
XDR: base64.StdEncoding.EncodeToString(result.Result),
Auth: base64EncodeSlice(result.Auth),
})
}
restorePreable := RestorePreamble{}
if result.PreRestoreTransactionData != "" {
if len(result.PreRestoreTransactionData) != 0 {
restorePreable = RestorePreamble{
TransactionData: result.PreRestoreTransactionData,
TransactionData: base64.StdEncoding.EncodeToString(result.PreRestoreTransactionData),
MinResourceFee: result.PreRestoreMinFee,
}
}

return SimulateTransactionResponse{
Error: result.Error,
Results: results,
Events: result.Events,
TransactionData: result.TransactionData,
Events: base64EncodeSlice(result.Events),
TransactionData: base64.StdEncoding.EncodeToString(result.TransactionData),
MinResourceFee: result.MinFee,
Cost: SimulateTransactionCost{
CPUInstructions: result.CPUInstructions,
Expand All @@ -151,6 +152,14 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge
})
}

func base64EncodeSlice(in [][]byte) []string {
result := make([]string, len(in))
for i, v := range in {
result[i] = base64.StdEncoding.EncodeToString(v)
}
return result
}

func getBucketListSize(ctx context.Context, ledgerReader db.LedgerReader, latestLedger uint32) (uint64, error) {
// obtain bucket size
var closeMeta, ok, err = ledgerReader.GetLedger(ctx, latestLedger)
Expand Down
110 changes: 58 additions & 52 deletions cmd/soroban-rpc/internal/preflight/preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,31 +39,35 @@ type snapshotSourceHandle struct {
// It's used by the Rust preflight code to obtain ledger entries.
//
//export SnapshotSourceGet
func SnapshotSourceGet(handle C.uintptr_t, cLedgerKey *C.char, includeExpired C.int) *C.char {
func SnapshotSourceGet(handle C.uintptr_t, cLedgerKey C.xdr_t, includeExpired C.int) C.xdr_t {
h := cgo.Handle(handle).Value().(snapshotSourceHandle)
ledgerKeyB64 := C.GoString(cLedgerKey)
ledgerKeyXDR := GoXDR(cLedgerKey)
var ledgerKey xdr.LedgerKey
if err := xdr.SafeUnmarshalBase64(ledgerKeyB64, &ledgerKey); err != nil {
if err := xdr.SafeUnmarshal(ledgerKeyXDR, &ledgerKey); err != nil {
panic(err)
}
present, entry, err := h.readTx.GetLedgerEntry(ledgerKey, includeExpired != 0)
if err != nil {
h.logger.WithError(err).Error("SnapshotSourceGet(): GetLedgerEntry() failed")
return nil
return C.xdr_t{}
}
if !present {
return nil
return C.xdr_t{}
}
out, err := xdr.MarshalBase64(entry)
out, err := entry.MarshalBinary()
if err != nil {
panic(err)
}
return C.CString(out)

return C.xdr_t{
xdr: (*C.uchar)(C.CBytes(out)),
len: C.size_t(len(out)),
}
}

//export FreeGoCString
func FreeGoCString(str *C.char) {
C.free(unsafe.Pointer(str))
//export FreeGoXDR
func FreeGoXDR(xdr C.xdr_t) {
C.free(unsafe.Pointer(xdr.xdr))
}

type PreflightParameters struct {
Expand All @@ -78,31 +82,33 @@ type PreflightParameters struct {

type Preflight struct {
Error string
Events []string // DiagnosticEvents XDR in base64
TransactionData string // SorobanTransactionData XDR in base64
Events [][]byte // DiagnosticEvents XDR
TransactionData []byte // SorobanTransactionData XDR
MinFee int64
Result string // XDR SCVal in base64
Auth []string // SorobanAuthorizationEntrys XDR in base64
Result []byte // XDR SCVal in base64
Auth [][]byte // SorobanAuthorizationEntries XDR
CPUInstructions uint64
MemoryBytes uint64
PreRestoreTransactionData string // SorobanTransactionData XDR in base64
PreRestoreTransactionData []byte // SorobanTransactionData XDR
PreRestoreMinFee int64
}

// GoNullTerminatedStringSlice transforms a C NULL-terminated char** array to a Go string slice
func GoNullTerminatedStringSlice(str **C.char) []string {
var result []string
if str != nil {
// CGo doesn't have an easy way to do pointer arithmetic so,
// we are better off transforming the memory buffer into a large slice
// and finding the NULL termination after that
for _, a := range unsafe.Slice(str, 1<<20) {
if a == nil {
// we found the ending nil
break
}
result = append(result, C.GoString(a))
}
func CXDR(xdr []byte) C.xdr_t {
return C.xdr_t{
xdr: (*C.uchar)(C.CBytes(xdr)),
len: C.size_t(len(xdr)),
}
}

func GoXDR(xdr C.xdr_t) []byte {
return C.GoBytes(unsafe.Pointer(xdr.xdr), C.int(xdr.len))
}

func GoXDRVector(xdrVector C.xdr_vector_t) [][]byte {
result := make([][]byte, xdrVector.len)
inputSlice := unsafe.Slice(xdrVector.array, xdrVector.len)
for i, v := range inputSlice {
result[i] = GoXDR(v)
}
return result
}
Expand All @@ -119,16 +125,16 @@ func GetPreflight(ctx context.Context, params PreflightParameters) (Preflight, e
}

func getFootprintExpirationPreflight(params PreflightParameters) (Preflight, error) {
opBodyB64, err := xdr.MarshalBase64(params.OpBody)
opBodyXDR, err := params.OpBody.MarshalBinary()
if err != nil {
return Preflight{}, err
}
opBodyCString := C.CString(opBodyB64)
footprintB64, err := xdr.MarshalBase64(params.Footprint)
opBodyCXDR := CXDR(opBodyXDR)
footprintXDR, err := params.Footprint.MarshalBinary()
if err != nil {
return Preflight{}, err
}
footprintCString := C.CString(footprintB64)
footprintCXDR := CXDR(footprintXDR)
handle := cgo.NewHandle(snapshotSourceHandle{params.LedgerEntryReadTx, params.Logger})
defer handle.Delete()

Expand All @@ -140,13 +146,13 @@ func getFootprintExpirationPreflight(params PreflightParameters) (Preflight, err
res := C.preflight_footprint_expiration_op(
C.uintptr_t(handle),
C.uint64_t(params.BucketListSize),
opBodyCString,
footprintCString,
opBodyCXDR,
footprintCXDR,
C.uint32_t(simulationLedgerSeq),
)

C.free(unsafe.Pointer(opBodyCString))
C.free(unsafe.Pointer(footprintCString))
FreeGoXDR(opBodyCXDR)
FreeGoXDR(footprintCXDR)

return GoPreflight(res), nil
}
Expand All @@ -164,15 +170,16 @@ func getSimulationLedgerSeq(readTx db.LedgerEntryReadTx) (uint32, error) {
}

func getInvokeHostFunctionPreflight(params PreflightParameters) (Preflight, error) {
invokeHostFunctionB64, err := xdr.MarshalBase64(params.OpBody.MustInvokeHostFunctionOp())
invokeHostFunctionXDR, err := params.OpBody.MustInvokeHostFunctionOp().MarshalBinary()
if err != nil {
return Preflight{}, err
}
invokeHostFunctionCString := C.CString(invokeHostFunctionB64)
sourceAccountB64, err := xdr.MarshalBase64(params.SourceAccount)
invokeHostFunctionCXDR := CXDR(invokeHostFunctionXDR)
sourceAccountXDR, err := params.SourceAccount.MarshalBinary()
if err != nil {
return Preflight{}, err
}
sourceAccountCXDR := CXDR(sourceAccountXDR)

hasConfig, stateExpirationConfig, err := params.LedgerEntryReadTx.GetLedgerEntry(xdr.LedgerKey{
Type: xdr.LedgerEntryTypeConfigSetting,
Expand All @@ -193,7 +200,7 @@ func getInvokeHostFunctionPreflight(params PreflightParameters) (Preflight, erro
}

stateExpiration := stateExpirationConfig.Data.MustConfigSetting().MustStateExpirationSettings()
li := C.CLedgerInfo{
li := C.ledger_info_t{
network_passphrase: C.CString(params.NetworkPassphrase),
sequence_number: C.uint32_t(simulationLedgerSeq),
protocol_version: 20,
Expand All @@ -206,35 +213,34 @@ func getInvokeHostFunctionPreflight(params PreflightParameters) (Preflight, erro
auto_bump_ledgers: C.uint(stateExpiration.AutoBumpLedgers),
}

sourceAccountCString := C.CString(sourceAccountB64)
handle := cgo.NewHandle(snapshotSourceHandle{params.LedgerEntryReadTx, params.Logger})
defer handle.Delete()
res := C.preflight_invoke_hf_op(
C.uintptr_t(handle),
C.uint64_t(params.BucketListSize),
invokeHostFunctionCString,
sourceAccountCString,
invokeHostFunctionCXDR,
sourceAccountCXDR,
li,
)
C.free(unsafe.Pointer(invokeHostFunctionCString))
C.free(unsafe.Pointer(sourceAccountCString))
FreeGoXDR(invokeHostFunctionCXDR)
FreeGoXDR(sourceAccountCXDR)

return GoPreflight(res), nil
}

func GoPreflight(result *C.CPreflightResult) Preflight {
func GoPreflight(result *C.preflight_result_t) Preflight {
defer C.free_preflight_result(result)

preflight := Preflight{
Error: C.GoString(result.error),
Events: GoNullTerminatedStringSlice(result.events),
TransactionData: C.GoString(result.transaction_data),
Events: GoXDRVector(result.events),
TransactionData: GoXDR(result.transaction_data),
MinFee: int64(result.min_fee),
Result: C.GoString(result.result),
Auth: GoNullTerminatedStringSlice(result.auth),
Result: GoXDR(result.result),
Auth: GoXDRVector(result.auth),
CPUInstructions: uint64(result.cpu_instructions),
MemoryBytes: uint64(result.memory_bytes),
PreRestoreTransactionData: C.GoString(result.pre_restore_transaction_data),
PreRestoreTransactionData: GoXDR(result.pre_restore_transaction_data),
PreRestoreMinFee: int64(result.pre_restore_min_fee),
}
return preflight
Expand Down
80 changes: 45 additions & 35 deletions cmd/soroban-rpc/lib/preflight.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include <stdint.h>

typedef struct CLedgerInfo {
typedef struct ledger_info_t {
uint32_t protocol_version;
uint32_t sequence_number;
uint64_t timestamp;
Expand All @@ -13,37 +13,47 @@ typedef struct CLedgerInfo {
uint32_t min_persistent_entry_expiration;
uint32_t max_entry_expiration;
uint32_t auto_bump_ledgers;
} CLedgerInfo;


typedef struct CPreflightResult {
char *error; // Error string in case of error, otherwise null
char **auth; // NULL terminated array of XDR SorobanAuthorizationEntrys in base64
char *result; // XDR SCVal in base64
char *transaction_data; // SorobanTransactionData XDR in base64
int64_t min_fee; // Minimum recommended resource fee
char **events; // NULL terminated array of XDR DiagnosticEvents in base64
uint64_t cpu_instructions;
uint64_t memory_bytes;
char *pre_restore_transaction_data; // SorobanTransactionData XDR in base64 for a prerequired RestoreFootprint operation
int64_t pre_restore_min_fee; // Minimum recommended resource fee for a prerequired RestoreFootprint operation
} CPreflightResult;

CPreflightResult *preflight_invoke_hf_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas
uint64_t bucket_list_size, // Bucket list size of current ledger
const char *invoke_hf_op, // InvokeHostFunctionOp XDR in base64
const char *source_account, // AccountId XDR in base64
const struct CLedgerInfo ledger_info);

CPreflightResult *preflight_footprint_expiration_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet and SnapshotSourceHas
uint64_t bucket_list_size, // Bucket list size of current ledger
const char *op_body, // OperationBody XDR in base64
const char *footprint, // LedgerFootprint XDR in base64
uint32_t current_ledger_seq); // Current ledger sequence

// LedgerKey XDR in base64 string to LedgerEntry XDR in base64 string
extern char *SnapshotSourceGet(uintptr_t handle, char *ledger_key, int include_expired);

void free_preflight_result(CPreflightResult *result);

extern void FreeGoCString(char *str);
} ledger_info_t;

typedef struct xdr_t {
unsigned char *xdr;
size_t len;
} xdr_t;

typedef struct xdr_vector_t {
xdr_t *array;
size_t len;
} xdr_vector_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
} preflight_result_t;

preflight_result_t *preflight_invoke_hf_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet
uint64_t bucket_list_size, // Bucket list size of current ledger
const xdr_t invoke_hf_op, // InvokeHostFunctionOp XDR
const xdr_t source_account, // AccountId XDR
const ledger_info_t ledger_info);

preflight_result_t *preflight_footprint_expiration_op(uintptr_t handle, // Go Handle to forward to SnapshotSourceGet
uint64_t bucket_list_size, // Bucket list size of current ledger
const xdr_t op_body, // OperationBody XDR
const xdr_t footprint, // LedgerFootprint XDR
uint32_t current_ledger_seq); // Current ledger sequence


// LedgerKey XDR to LedgerEntry XDR
extern xdr_t SnapshotSourceGet(uintptr_t handle, xdr_t ledger_key, int include_expired);

void free_preflight_result(preflight_result_t *result);

extern void FreeGoXDR(xdr_t xdr);
Loading

0 comments on commit ff5ffa1

Please sign in to comment.