From 0d0af89dc4be40618161d85ee8e534e418055b1e Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Wed, 2 Aug 2023 18:58:07 +0200 Subject: [PATCH] soroban-rpc: Add getPreflight benchmark and test (#692) --- .github/workflows/soroban-rpc.yml | 5 +- .../internal/preflight/preflight_test.go | 331 ++++++++++++++++++ .../internal/test/get_ledger_entry_test.go | 2 +- 3 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 cmd/soroban-rpc/internal/preflight/preflight_test.go diff --git a/.github/workflows/soroban-rpc.yml b/.github/workflows/soroban-rpc.yml index 777feaa3..4ce3313f 100644 --- a/.github/workflows/soroban-rpc.yml +++ b/.github/workflows/soroban-rpc.yml @@ -29,8 +29,11 @@ jobs: - uses: ./.github/actions/setup-go with: go-version: ${{ matrix.go }} - - run: | + - name: Build soroban contract fixtures + run: | rustup update + rustup target add wasm32-unknown-unknown + make build-test-wasms - run: make build-libpreflight - run: go test -race -cover -timeout 25m -v ./cmd/soroban-rpc/... diff --git a/cmd/soroban-rpc/internal/preflight/preflight_test.go b/cmd/soroban-rpc/internal/preflight/preflight_test.go new file mode 100644 index 00000000..41475606 --- /dev/null +++ b/cmd/soroban-rpc/internal/preflight/preflight_test.go @@ -0,0 +1,331 @@ +package preflight + +import ( + "context" + "os" + "path" + "runtime" + "testing" + + "github.com/stellar/go/support/log" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/require" + + "github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/db" +) + +var mockContractID = xdr.Hash{0xa, 0xb, 0xc} +var mockContractHash = xdr.Hash{0xd, 0xe, 0xf} + +var contractCostParams = func() *xdr.ContractCostParams { + var result xdr.ContractCostParams + + for i := 0; i < 22; i++ { + result = append(result, xdr.ContractCostParamEntry{ + Ext: xdr.ExtensionPoint{}, + ConstTerm: 0, + LinearTerm: 0, + }) + } + + return &result +}() + +var mockLedgerEntries = []xdr.LedgerEntry{ + { + LastModifiedLedgerSeq: 1, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeContractData, + ContractData: &xdr.ContractDataEntry{ + Contract: xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: &mockContractID, + }, + Key: xdr.ScVal{ + Type: xdr.ScValTypeScvLedgerKeyContractInstance, + }, + Durability: xdr.ContractDataDurabilityPersistent, + ExpirationLedgerSeq: 100000, + Body: xdr.ContractDataEntryBody{ + BodyType: xdr.ContractEntryBodyTypeDataEntry, + Data: &xdr.ContractDataEntryData{ + Flags: 0, + Val: xdr.ScVal{ + Type: xdr.ScValTypeScvContractInstance, + Instance: &xdr.ScContractInstance{ + Executable: xdr.ContractExecutable{ + Type: xdr.ContractExecutableTypeContractExecutableWasm, + WasmHash: &mockContractHash, + }, + Storage: nil, + }, + }, + }, + }, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeContractCode, + ContractCode: &xdr.ContractCodeEntry{ + Hash: mockContractHash, + Body: xdr.ContractCodeEntryBody{ + BodyType: xdr.ContractEntryBodyTypeDataEntry, + Code: &helloWorldContract, + }, + ExpirationLedgerSeq: 20000, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeConfigSetting, + ConfigSetting: &xdr.ConfigSettingEntry{ + ConfigSettingId: xdr.ConfigSettingIdConfigSettingContractComputeV0, + ContractCompute: &xdr.ConfigSettingContractComputeV0{ + LedgerMaxInstructions: 100000000, + TxMaxInstructions: 100000000, + FeeRatePerInstructionsIncrement: 1, + TxMemoryLimit: 100000000, + }, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeConfigSetting, + ConfigSetting: &xdr.ConfigSettingEntry{ + ConfigSettingId: xdr.ConfigSettingIdConfigSettingContractLedgerCostV0, + ContractLedgerCost: &xdr.ConfigSettingContractLedgerCostV0{ + LedgerMaxReadLedgerEntries: 100, + LedgerMaxReadBytes: 100, + LedgerMaxWriteLedgerEntries: 100, + LedgerMaxWriteBytes: 100, + TxMaxReadLedgerEntries: 100, + TxMaxReadBytes: 100, + TxMaxWriteLedgerEntries: 100, + TxMaxWriteBytes: 100, + FeeReadLedgerEntry: 100, + FeeWriteLedgerEntry: 100, + FeeRead1Kb: 100, + BucketListTargetSizeBytes: 100, + WriteFee1KbBucketListLow: 1, + WriteFee1KbBucketListHigh: 1, + BucketListWriteFeeGrowthFactor: 1, + }, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeConfigSetting, + ConfigSetting: &xdr.ConfigSettingEntry{ + ConfigSettingId: xdr.ConfigSettingIdConfigSettingContractHistoricalDataV0, + ContractHistoricalData: &xdr.ConfigSettingContractHistoricalDataV0{ + FeeHistorical1Kb: 100, + }, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeConfigSetting, + ConfigSetting: &xdr.ConfigSettingEntry{ + ConfigSettingId: xdr.ConfigSettingIdConfigSettingContractMetaDataV0, + ContractMetaData: &xdr.ConfigSettingContractMetaDataV0{ + TxMaxExtendedMetaDataSizeBytes: 100, + FeeExtendedMetaData1Kb: 100, + }, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeConfigSetting, + ConfigSetting: &xdr.ConfigSettingEntry{ + ConfigSettingId: xdr.ConfigSettingIdConfigSettingContractBandwidthV0, + ContractBandwidth: &xdr.ConfigSettingContractBandwidthV0{ + LedgerMaxPropagateSizeBytes: 100, + TxMaxSizeBytes: 100, + FeePropagateData1Kb: 100, + }, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeConfigSetting, + ConfigSetting: &xdr.ConfigSettingEntry{ + ConfigSettingId: xdr.ConfigSettingIdConfigSettingStateExpiration, + StateExpirationSettings: &xdr.StateExpirationSettings{ + MaxEntryExpiration: 100, + MinTempEntryExpiration: 100, + MinPersistentEntryExpiration: 100, + AutoBumpLedgers: 100, + PersistentRentRateDenominator: 100, + TempRentRateDenominator: 100, + MaxEntriesToExpire: 100, + BucketListSizeWindowSampleSize: 100, + EvictionScanSize: 100, + }, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeConfigSetting, + ConfigSetting: &xdr.ConfigSettingEntry{ + ConfigSettingId: xdr.ConfigSettingIdConfigSettingContractCostParamsCpuInstructions, + // Obtained with TestGetLedgerEntryConfigSettings + ContractCostParamsCpuInsns: contractCostParams, + }, + }, + }, + { + LastModifiedLedgerSeq: 2, + Data: xdr.LedgerEntryData{ + Type: xdr.LedgerEntryTypeConfigSetting, + ConfigSetting: &xdr.ConfigSettingEntry{ + ConfigSettingId: xdr.ConfigSettingIdConfigSettingContractCostParamsMemoryBytes, + // Obtained with TestGetLedgerEntryConfigSettings + ContractCostParamsMemBytes: contractCostParams, + }, + }, + }, +} + +var helloWorldContract = func() []byte { + _, filename, _, _ := runtime.Caller(0) + testDirName := path.Dir(filename) + contractFile := path.Join(testDirName, "../../../../target/wasm32-unknown-unknown/test-wasms/test_hello_world.wasm") + ret, err := os.ReadFile(contractFile) + if err != nil { + log.Fatalf("unable to read test_hello_world.wasm (%v) please run `make build-test-wasms` at the project root directory", err) + } + return ret +}() + +type inMemoryLedgerEntryReadTx map[string]xdr.LedgerEntry + +func newInMemoryLedgerEntryReadTx(entries []xdr.LedgerEntry) (inMemoryLedgerEntryReadTx, error) { + result := make(map[string]xdr.LedgerEntry, len(entries)) + for _, entry := range entries { + key, err := entry.LedgerKey() + if err != nil { + return inMemoryLedgerEntryReadTx{}, err + } + serialized, err := key.MarshalBinaryBase64() + if err != nil { + return inMemoryLedgerEntryReadTx{}, err + } + result[serialized] = entry + } + return result, nil +} + +func (m inMemoryLedgerEntryReadTx) GetLatestLedgerSequence() (uint32, error) { + return 2, nil +} + +func (m inMemoryLedgerEntryReadTx) GetLedgerEntry(key xdr.LedgerKey, includeExpired bool) (bool, xdr.LedgerEntry, error) { + serializedKey, err := key.MarshalBinaryBase64() + if err != nil { + return false, xdr.LedgerEntry{}, err + } + entry, ok := m[serializedKey] + if !ok { + return false, xdr.LedgerEntry{}, nil + } + return true, entry, nil +} + +func (m inMemoryLedgerEntryReadTx) Done() error { + return nil +} + +func getPreflightParameters(t testing.TB, inMemory bool) PreflightParameters { + var ledgerEntryReadTx db.LedgerEntryReadTx + if inMemory { + var err error + ledgerEntryReadTx, err = newInMemoryLedgerEntryReadTx(mockLedgerEntries) + require.NoError(t, err) + } else { + d := t.TempDir() + dbInstance, err := db.OpenSQLiteDB(path.Join(d, "soroban_rpc.sqlite")) + require.NoError(t, err) + readWriter := db.NewReadWriter(dbInstance, 100, 10000) + tx, err := readWriter.NewTx(context.Background()) + require.NoError(t, err) + for _, e := range mockLedgerEntries { + err := tx.LedgerEntryWriter().UpsertLedgerEntry(e) + require.NoError(t, err) + } + err = tx.Commit(2) + require.NoError(t, err) + ledgerEntryReadTx, err = db.NewLedgerEntryReader(dbInstance).NewTx(context.Background()) + require.NoError(t, err) + } + argSymbol := xdr.ScSymbol("world") + params := PreflightParameters{ + Logger: log.New(), + SourceAccount: xdr.MustAddress("GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H"), + OpBody: xdr.OperationBody{Type: xdr.OperationTypeInvokeHostFunction, + InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, + InvokeContract: &xdr.InvokeContractArgs{ + ContractAddress: xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: &mockContractID, + }, + FunctionName: "hello", + Args: []xdr.ScVal{ + { + Type: xdr.ScValTypeScvSymbol, + Sym: &argSymbol, + }, + }, + }, + }, + }}, + NetworkPassphrase: "foo", + LedgerEntryReadTx: ledgerEntryReadTx, + BucketListSize: 200, + } + return params +} + +func TestGetPreflight(t *testing.T) { + params := getPreflightParameters(t, false) + _, err := GetPreflight(context.Background(), params) + require.NoError(t, err) + + params = getPreflightParameters(t, true) + _, err = GetPreflight(context.Background(), params) + require.NoError(t, err) +} + +func benchmark(b *testing.B, inMemory bool) { + params := getPreflightParameters(b, inMemory) + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StartTimer() + _, err := GetPreflight(context.Background(), params) + b.StopTimer() + require.NoError(b, err) + } +} + +func BenchmarkGetPreflight(b *testing.B) { + b.Run("In-memory storage", func(b *testing.B) { benchmark(b, true) }) + b.Run("DB storage", func(b *testing.B) { benchmark(b, false) }) +} diff --git a/cmd/soroban-rpc/internal/test/get_ledger_entry_test.go b/cmd/soroban-rpc/internal/test/get_ledger_entry_test.go index c7acac08..27d52eb3 100644 --- a/cmd/soroban-rpc/internal/test/get_ledger_entry_test.go +++ b/cmd/soroban-rpc/internal/test/get_ledger_entry_test.go @@ -8,11 +8,11 @@ import ( "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/code" "github.com/creachadair/jrpc2/jhttp" + "github.com/stellar/go/txnbuild" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stellar/go/keypair" - "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" "github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/methods"