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

soroban-rpc: libpreflight: Remove base64 encoding between Go and Rust #893

Merged
merged 2 commits into from
Aug 23, 2023
Merged
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
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 {
2opremio marked this conversation as resolved.
Show resolved Hide resolved
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