From 67ba6a418563720eeb38a108e6df4ebe166f2940 Mon Sep 17 00:00:00 2001 From: urvisavla Date: Wed, 24 Jan 2024 17:17:23 -0800 Subject: [PATCH] services/horizon: Add DISABLE_SOROBAN_INGEST flag to skip soroban ingestion processing (#5176) --- .github/workflows/horizon.yml | 2 +- services/horizon/CHANGELOG.md | 5 + services/horizon/cmd/db.go | 1 + services/horizon/internal/config.go | 2 + services/horizon/internal/flags.go | 11 + services/horizon/internal/ingest/main.go | 2 + .../internal/ingest/processor_runner.go | 14 +- .../internal/ingest/processor_runner_test.go | 5 +- .../ingest/processors/effects_processor.go | 22 +- .../processors/effects_processor_test.go | 1 + .../ingest/processors/operations_processor.go | 52 +- .../processors/operations_processor_test.go | 60 ++ .../processors/transactions_processor.go | 29 +- .../processors/transactions_processor_test.go | 2 +- .../horizon/internal/ingest/verify_test.go | 4 +- services/horizon/internal/init.go | 1 + .../integration/invokehostfunction_test.go | 103 +++- .../horizon/internal/integration/sac_test.go | 553 +++++++++++------- 18 files changed, 608 insertions(+), 261 deletions(-) diff --git a/.github/workflows/horizon.yml b/.github/workflows/horizon.yml index ed5397d184..df314900d2 100644 --- a/.github/workflows/horizon.yml +++ b/.github/workflows/horizon.yml @@ -120,7 +120,7 @@ jobs: key: ${{ env.COMBINED_SOURCE_HASH }} - if: ${{ steps.horizon_binary_tests_hash.outputs.cache-hit != 'true' }} - run: go test -race -timeout 45m -v ./services/horizon/internal/integration/... + run: go test -race -timeout 65m -v ./services/horizon/internal/integration/... - name: Save Horizon binary and integration tests source hash to cache if: ${{ success() && steps.horizon_binary_tests_hash.outputs.cache-hit != 'true' }} diff --git a/services/horizon/CHANGELOG.md b/services/horizon/CHANGELOG.md index 2611ade8b2..6c9ca1b69f 100644 --- a/services/horizon/CHANGELOG.md +++ b/services/horizon/CHANGELOG.md @@ -17,6 +17,11 @@ file. This project adheres to [Semantic Versioning](http://semver.org/). - We now include metrics for history archive requests ([5166](https://github.com/stellar/go/pull/5166)) - Http history archive requests now include a unique user agent ([5166](https://github.com/stellar/go/pull/5166)) - Added a deprecation warning for using command-line flags when running Horizon ([5051](https://github.com/stellar/go/pull/5051)) +- New optional config `DISABLE_SOROBAN_INGEST` ([5175](https://github.com/stellar/go/issues/5175)). Defaults to `FALSE`, when `TRUE` and a soroban transaction is ingested, the following will occur: + * no effects will be generated for contract invocations. + * history_transactions.tx_meta column will have serialized xdr that equates to an empty `xdr.TransactionMeta.V3`, `Operations`, `TxChangesAfter`, `TxChangesBefore` will empty arrays and `SorobanMeta` will be nil. + * API transaction model for `result_meta_xdr` will have same empty serialized xdr for `xdr.TransactionMeta.V3`, `Operations`, `TxChangesAfter`, `TxChangesBefore` will empty arrays and `SorobanMeta` will be nil. + * API `Operation` model for `InvokeHostFunctionOp` type, will have empty `asset_balance_changes` ### Breaking Changes - Removed configuration flags `--stellar-core-url-db`, `--cursor-name` `--skip-cursor-update` , they were related to legacy non-captive core ingestion and are no longer usable. diff --git a/services/horizon/cmd/db.go b/services/horizon/cmd/db.go index 725df622b0..07bbf975fa 100644 --- a/services/horizon/cmd/db.go +++ b/services/horizon/cmd/db.go @@ -419,6 +419,7 @@ func runDBReingestRange(ledgerRanges []history.LedgerRange, reingestForce bool, RoundingSlippageFilter: config.RoundingSlippageFilter, EnableIngestionFiltering: config.EnableIngestionFiltering, MaxLedgerPerFlush: maxLedgersPerFlush, + SkipSorobanIngestion: config.SkipSorobanIngestion, } if ingestConfig.HistorySession, err = db.Open("postgres", config.DatabaseURL); err != nil { diff --git a/services/horizon/internal/config.go b/services/horizon/internal/config.go index 7454f52bb7..8fb31075b8 100644 --- a/services/horizon/internal/config.go +++ b/services/horizon/internal/config.go @@ -108,4 +108,6 @@ type Config struct { Network string // DisableTxSub disables transaction submission functionality for Horizon. DisableTxSub bool + // SkipSorobanIngestion skips Soroban related ingestion processing. + SkipSorobanIngestion bool } diff --git a/services/horizon/internal/flags.go b/services/horizon/internal/flags.go index 40bfc08afe..eb229c65b2 100644 --- a/services/horizon/internal/flags.go +++ b/services/horizon/internal/flags.go @@ -57,6 +57,8 @@ const ( EnableIngestionFilteringFlagName = "exp-enable-ingestion-filtering" // DisableTxSubFlagName is the command line flag for disabling transaction submission feature of Horizon DisableTxSubFlagName = "disable-tx-sub" + // SkipSorobanIngestionFlagName is the command line flag for disabling Soroban related ingestion processing + SkipSorobanIngestionFlagName = "disable-soroban-ingest" // StellarPubnet is a constant representing the Stellar public network StellarPubnet = "pubnet" @@ -730,6 +732,15 @@ func Flags() (*Config, support.ConfigOptions) { HistoryArchiveURLsFlagName, CaptiveCoreConfigPathName), UsedInCommands: IngestionCommands, }, + &support.ConfigOption{ + Name: SkipSorobanIngestionFlagName, + ConfigKey: &config.SkipSorobanIngestion, + OptType: types.Bool, + FlagDefault: false, + Required: false, + Usage: "excludes Soroban data during ingestion processing", + UsedInCommands: IngestionCommands, + }, } return config, flags diff --git a/services/horizon/internal/ingest/main.go b/services/horizon/internal/ingest/main.go index dae494efc4..556724bde1 100644 --- a/services/horizon/internal/ingest/main.go +++ b/services/horizon/internal/ingest/main.go @@ -108,6 +108,8 @@ type Config struct { EnableIngestionFiltering bool MaxLedgerPerFlush uint32 + + SkipSorobanIngestion bool } const ( diff --git a/services/horizon/internal/ingest/processor_runner.go b/services/horizon/internal/ingest/processor_runner.go index 34b977c03e..a09442b49d 100644 --- a/services/horizon/internal/ingest/processor_runner.go +++ b/services/horizon/internal/ingest/processor_runner.go @@ -111,6 +111,7 @@ func buildChangeProcessor( source ingestionSource, ledgerSequence uint32, networkPassphrase string, + skipSorobanIngestion bool, ) *groupChangeProcessors { statsChangeProcessor := &statsChangeProcessor{ StatsChangeProcessor: changeStats, @@ -144,13 +145,13 @@ func (s *ProcessorRunner) buildTransactionProcessor(ledgersProcessor *processors processors := []horizonTransactionProcessor{ statsLedgerTransactionProcessor, - processors.NewEffectProcessor(accountLoader, s.historyQ.NewEffectBatchInsertBuilder(), s.config.NetworkPassphrase), + processors.NewEffectProcessor(accountLoader, s.historyQ.NewEffectBatchInsertBuilder(), s.config.NetworkPassphrase, s.config.SkipSorobanIngestion), ledgersProcessor, - processors.NewOperationProcessor(s.historyQ.NewOperationBatchInsertBuilder(), s.config.NetworkPassphrase), + processors.NewOperationProcessor(s.historyQ.NewOperationBatchInsertBuilder(), s.config.NetworkPassphrase, s.config.SkipSorobanIngestion), tradeProcessor, processors.NewParticipantsProcessor(accountLoader, s.historyQ.NewTransactionParticipantsBatchInsertBuilder(), s.historyQ.NewOperationParticipantBatchInsertBuilder()), - processors.NewTransactionProcessor(s.historyQ.NewTransactionBatchInsertBuilder()), + processors.NewTransactionProcessor(s.historyQ.NewTransactionBatchInsertBuilder(), s.config.SkipSorobanIngestion), processors.NewClaimableBalancesTransactionProcessor(cbLoader, s.historyQ.NewTransactionClaimableBalanceBatchInsertBuilder(), s.historyQ.NewOperationClaimableBalanceBatchInsertBuilder()), processors.NewLiquidityPoolsTransactionProcessor(lpLoader, @@ -172,7 +173,10 @@ func (s *ProcessorRunner) buildFilteredOutProcessor() *groupTransactionProcessor // when in online mode, the submission result processor must always run (regardless of filtering) var p []horizonTransactionProcessor if s.config.EnableIngestionFiltering { - txSubProc := processors.NewTransactionFilteredTmpProcessor(s.historyQ.NewTransactionFilteredTmpBatchInsertBuilder()) + txSubProc := processors.NewTransactionFilteredTmpProcessor( + s.historyQ.NewTransactionFilteredTmpBatchInsertBuilder(), + s.config.SkipSorobanIngestion, + ) p = append(p, txSubProc) } @@ -235,6 +239,7 @@ func (s *ProcessorRunner) RunHistoryArchiveIngestion( historyArchiveSource, checkpointLedger, s.config.NetworkPassphrase, + s.config.SkipSorobanIngestion, ) if checkpointLedger == 1 { @@ -493,6 +498,7 @@ func (s *ProcessorRunner) RunAllProcessorsOnLedger(ledger xdr.LedgerCloseMeta) ( ledgerSource, ledger.LedgerSequence(), s.config.NetworkPassphrase, + s.config.SkipSorobanIngestion, ) err = s.runChangeProcessorOnLedger(groupChangeProcessors, ledger) if err != nil { diff --git a/services/horizon/internal/ingest/processor_runner_test.go b/services/horizon/internal/ingest/processor_runner_test.go index eaeca95661..ddac48aa82 100644 --- a/services/horizon/internal/ingest/processor_runner_test.go +++ b/services/horizon/internal/ingest/processor_runner_test.go @@ -180,7 +180,7 @@ func TestProcessorRunnerBuildChangeProcessor(t *testing.T) { } stats := &ingest.StatsChangeProcessor{} - processor := buildChangeProcessor(runner.historyQ, stats, ledgerSource, 123, "") + processor := buildChangeProcessor(runner.historyQ, stats, ledgerSource, 123, "", false) assert.IsType(t, &groupChangeProcessors{}, processor) assert.IsType(t, &statsChangeProcessor{}, processor.processors[0]) @@ -201,7 +201,7 @@ func TestProcessorRunnerBuildChangeProcessor(t *testing.T) { filters: &MockFilters{}, } - processor = buildChangeProcessor(runner.historyQ, stats, historyArchiveSource, 456, "") + processor = buildChangeProcessor(runner.historyQ, stats, historyArchiveSource, 456, "", false) assert.IsType(t, &groupChangeProcessors{}, processor) assert.IsType(t, &statsChangeProcessor{}, processor.processors[0]) @@ -271,6 +271,7 @@ func TestProcessorRunnerWithFilterEnabled(t *testing.T) { config := Config{ NetworkPassphrase: network.PublicNetworkPassphrase, EnableIngestionFiltering: true, + SkipSorobanIngestion: false, } q := &mockDBQ{} diff --git a/services/horizon/internal/ingest/processors/effects_processor.go b/services/horizon/internal/ingest/processors/effects_processor.go index 34e9f9169a..830632f5f5 100644 --- a/services/horizon/internal/ingest/processors/effects_processor.go +++ b/services/horizon/internal/ingest/processors/effects_processor.go @@ -28,17 +28,20 @@ type EffectProcessor struct { accountLoader *history.AccountLoader batch history.EffectBatchInsertBuilder network string + skipSoroban bool } func NewEffectProcessor( accountLoader *history.AccountLoader, batch history.EffectBatchInsertBuilder, network string, + skipSoroban bool, ) *EffectProcessor { return &EffectProcessor{ accountLoader: accountLoader, batch: batch, network: network, + skipSoroban: skipSoroban, } } @@ -50,14 +53,29 @@ func (p *EffectProcessor) ProcessTransaction( return nil } - for opi, op := range transaction.Envelope.Operations() { + elidedTransaction := transaction + + if p.skipSoroban && + elidedTransaction.UnsafeMeta.V == 3 && + elidedTransaction.UnsafeMeta.V3.SorobanMeta != nil { + elidedTransaction.UnsafeMeta.V3 = &xdr.TransactionMetaV3{ + Ext: xdr.ExtensionPoint{}, + TxChangesBefore: xdr.LedgerEntryChanges{}, + Operations: []xdr.OperationMeta{}, + TxChangesAfter: xdr.LedgerEntryChanges{}, + SorobanMeta: nil, + } + } + + for opi, op := range elidedTransaction.Envelope.Operations() { operation := transactionOperationWrapper{ index: uint32(opi), - transaction: transaction, + transaction: elidedTransaction, operation: op, ledgerSequence: uint32(lcm.LedgerSequence()), network: p.network, } + if err := operation.ingestEffects(p.accountLoader, p.batch); err != nil { return errors.Wrapf(err, "reading operation %v effects", operation.ID()) } diff --git a/services/horizon/internal/ingest/processors/effects_processor_test.go b/services/horizon/internal/ingest/processors/effects_processor_test.go index 0243768fde..70af21737a 100644 --- a/services/horizon/internal/ingest/processors/effects_processor_test.go +++ b/services/horizon/internal/ingest/processors/effects_processor_test.go @@ -143,6 +143,7 @@ func (s *EffectsProcessorTestSuiteLedger) SetupTest() { s.accountLoader, s.mockBatchInsertBuilder, networkPassphrase, + false, ) s.txs = []ingest.LedgerTransaction{ diff --git a/services/horizon/internal/ingest/processors/operations_processor.go b/services/horizon/internal/ingest/processors/operations_processor.go index 8ad023145c..92a4b870e9 100644 --- a/services/horizon/internal/ingest/processors/operations_processor.go +++ b/services/horizon/internal/ingest/processors/operations_processor.go @@ -22,14 +22,16 @@ import ( // OperationProcessor operations processor type OperationProcessor struct { - batch history.OperationBatchInsertBuilder - network string + batch history.OperationBatchInsertBuilder + network string + skipSoroban bool } -func NewOperationProcessor(batch history.OperationBatchInsertBuilder, network string) *OperationProcessor { +func NewOperationProcessor(batch history.OperationBatchInsertBuilder, network string, skipSoroban bool) *OperationProcessor { return &OperationProcessor{ - batch: batch, - network: network, + batch: batch, + network: network, + skipSoroban: skipSoroban, } } @@ -37,11 +39,12 @@ func NewOperationProcessor(batch history.OperationBatchInsertBuilder, network st func (p *OperationProcessor) ProcessTransaction(lcm xdr.LedgerCloseMeta, transaction ingest.LedgerTransaction) error { for i, op := range transaction.Envelope.Operations() { operation := transactionOperationWrapper{ - index: uint32(i), - transaction: transaction, - operation: op, - ledgerSequence: lcm.LedgerSequence(), - network: p.network, + index: uint32(i), + transaction: transaction, + operation: op, + ledgerSequence: lcm.LedgerSequence(), + network: p.network, + skipSorobanDetails: p.skipSoroban, } details, err := operation.Details() if err != nil { @@ -82,11 +85,12 @@ func (p *OperationProcessor) Flush(ctx context.Context, session db.SessionInterf // transactionOperationWrapper represents the data for a single operation within a transaction type transactionOperationWrapper struct { - index uint32 - transaction ingest.LedgerTransaction - operation xdr.Operation - ledgerSequence uint32 - network string + index uint32 + transaction ingest.LedgerTransaction + operation xdr.Operation + ledgerSequence uint32 + network string + skipSorobanDetails bool } // ID returns the ID for the operation. @@ -266,6 +270,11 @@ func (operation *transactionOperationWrapper) IsPayment() bool { case xdr.OperationTypeAccountMerge: return true case xdr.OperationTypeInvokeHostFunction: + // #5175, may want to consider skipping this parsing of payment from contracts + // as part of eliding soroban ingestion aspects when DISABLE_SOROBAN_INGEST. + // but, may cause inconsistencies that aren't worth the gain, + // as payments won't be thoroughly accurate, i.e. a payment could have + // happened within a contract invoke. diagnosticEvents, err := operation.transaction.GetDiagnosticEvents() if err != nil { return false @@ -689,11 +698,18 @@ func (operation *transactionOperationWrapper) Details() (map[string]interface{}, } details["parameters"] = params - if balanceChanges, err := operation.parseAssetBalanceChangesFromContractEvents(); err != nil { - return nil, err + var balanceChanges []map[string]interface{} + var parseErr error + if operation.skipSorobanDetails { + // https://github.com/stellar/go/issues/5175 + // intentionally toggle off parsing soroban meta into "asset_balance_changes" + balanceChanges = make([]map[string]interface{}, 0) } else { - details["asset_balance_changes"] = balanceChanges + if balanceChanges, parseErr = operation.parseAssetBalanceChangesFromContractEvents(); parseErr != nil { + return nil, parseErr + } } + details["asset_balance_changes"] = balanceChanges case xdr.HostFunctionTypeHostFunctionTypeCreateContract: args := op.HostFunction.MustCreateContract() diff --git a/services/horizon/internal/ingest/processors/operations_processor_test.go b/services/horizon/internal/ingest/processors/operations_processor_test.go index 4b5fb376cd..275a6056e4 100644 --- a/services/horizon/internal/ingest/processors/operations_processor_test.go +++ b/services/horizon/internal/ingest/processors/operations_processor_test.go @@ -42,6 +42,7 @@ func (s *OperationsProcessorTestSuiteLedger) SetupTest() { s.processor = NewOperationProcessor( s.mockBatchInsertBuilder, "test network", + false, ) } @@ -375,6 +376,65 @@ func (s *OperationsProcessorTestSuiteLedger) TestOperationTypeInvokeHostFunction } s.Assert().Equal(found, 4, "should have one balance changed record for each of mint, burn, clawback, transfer") }) + + s.T().Run("InvokeContractAssetBalancesElidedFromDetails", func(t *testing.T) { + randomIssuer := keypair.MustRandom() + randomAsset := xdr.MustNewCreditAsset("TESTING", randomIssuer.Address()) + passphrase := "passphrase" + randomAccount := keypair.MustRandom().Address() + contractId := [32]byte{} + zeroContractStrKey, err := strkey.Encode(strkey.VersionByteContract, contractId[:]) + s.Assert().NoError(err) + + transferContractEvent := contractevents.GenerateEvent(contractevents.EventTypeTransfer, randomAccount, zeroContractStrKey, "", randomAsset, big.NewInt(10000000), passphrase) + burnContractEvent := contractevents.GenerateEvent(contractevents.EventTypeBurn, zeroContractStrKey, "", "", randomAsset, big.NewInt(10000000), passphrase) + mintContractEvent := contractevents.GenerateEvent(contractevents.EventTypeMint, "", zeroContractStrKey, randomAccount, randomAsset, big.NewInt(10000000), passphrase) + clawbackContractEvent := contractevents.GenerateEvent(contractevents.EventTypeClawback, zeroContractStrKey, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase) + + tx = ingest.LedgerTransaction{ + UnsafeMeta: xdr.TransactionMeta{ + V: 3, + V3: &xdr.TransactionMetaV3{ + SorobanMeta: &xdr.SorobanTransactionMeta{ + Events: []xdr.ContractEvent{ + transferContractEvent, + burnContractEvent, + mintContractEvent, + clawbackContractEvent, + }, + }, + }, + }, + } + wrapper := transactionOperationWrapper{ + skipSorobanDetails: true, + transaction: tx, + operation: xdr.Operation{ + SourceAccount: &source, + Body: xdr.OperationBody{ + Type: xdr.OperationTypeInvokeHostFunction, + InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, + InvokeContract: &xdr.InvokeContractArgs{ + ContractAddress: xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: &xdr.Hash{0x1, 0x2}, + }, + FunctionName: "foo", + Args: xdr.ScVec{}, + }, + }, + }, + }, + }, + network: passphrase, + } + + details, err := wrapper.Details() + s.Assert().NoError(err) + s.Assert().Len(details["asset_balance_changes"], 0, "for invokehostfn op, no asset balances should be in details when skip soroban is enabled") + }) } func (s *OperationsProcessorTestSuiteLedger) assertInvokeHostFunctionParameter(parameters []map[string]string, paramPosition int, expectedType string, expectedVal xdr.ScVal) { diff --git a/services/horizon/internal/ingest/processors/transactions_processor.go b/services/horizon/internal/ingest/processors/transactions_processor.go index 871c72624a..b82934d86a 100644 --- a/services/horizon/internal/ingest/processors/transactions_processor.go +++ b/services/horizon/internal/ingest/processors/transactions_processor.go @@ -11,23 +11,40 @@ import ( ) type TransactionProcessor struct { - batch history.TransactionBatchInsertBuilder + batch history.TransactionBatchInsertBuilder + skipSoroban bool } -func NewTransactionFilteredTmpProcessor(batch history.TransactionBatchInsertBuilder) *TransactionProcessor { +func NewTransactionFilteredTmpProcessor(batch history.TransactionBatchInsertBuilder, skipSoroban bool) *TransactionProcessor { return &TransactionProcessor{ - batch: batch, + batch: batch, + skipSoroban: skipSoroban, } } -func NewTransactionProcessor(batch history.TransactionBatchInsertBuilder) *TransactionProcessor { +func NewTransactionProcessor(batch history.TransactionBatchInsertBuilder, skipSoroban bool) *TransactionProcessor { return &TransactionProcessor{ - batch: batch, + batch: batch, + skipSoroban: skipSoroban, } } func (p *TransactionProcessor) ProcessTransaction(lcm xdr.LedgerCloseMeta, transaction ingest.LedgerTransaction) error { - if err := p.batch.Add(transaction, lcm.LedgerSequence()); err != nil { + elidedTransaction := transaction + + if p.skipSoroban && + elidedTransaction.UnsafeMeta.V == 3 && + elidedTransaction.UnsafeMeta.MustV3().SorobanMeta != nil { + elidedTransaction.UnsafeMeta.V3 = &xdr.TransactionMetaV3{ + Ext: xdr.ExtensionPoint{}, + TxChangesBefore: xdr.LedgerEntryChanges{}, + Operations: []xdr.OperationMeta{}, + TxChangesAfter: xdr.LedgerEntryChanges{}, + SorobanMeta: nil, + } + } + + if err := p.batch.Add(elidedTransaction, lcm.LedgerSequence()); err != nil { return errors.Wrap(err, "Error batch inserting transaction rows") } diff --git a/services/horizon/internal/ingest/processors/transactions_processor_test.go b/services/horizon/internal/ingest/processors/transactions_processor_test.go index 987e8ce6f9..873a72af05 100644 --- a/services/horizon/internal/ingest/processors/transactions_processor_test.go +++ b/services/horizon/internal/ingest/processors/transactions_processor_test.go @@ -29,7 +29,7 @@ func TestTransactionsProcessorTestSuiteLedger(t *testing.T) { func (s *TransactionsProcessorTestSuiteLedger) SetupTest() { s.ctx = context.Background() s.mockBatchInsertBuilder = &history.MockTransactionsBatchInsertBuilder{} - s.processor = NewTransactionProcessor(s.mockBatchInsertBuilder) + s.processor = NewTransactionProcessor(s.mockBatchInsertBuilder, false) } func (s *TransactionsProcessorTestSuiteLedger) TearDownTest() { diff --git a/services/horizon/internal/ingest/verify_test.go b/services/horizon/internal/ingest/verify_test.go index 901f21a0ca..e3c0e4ec56 100644 --- a/services/horizon/internal/ingest/verify_test.go +++ b/services/horizon/internal/ingest/verify_test.go @@ -292,7 +292,7 @@ func TestStateVerifierLockBusy(t *testing.T) { tt.Assert.NoError(q.BeginTx(tt.Ctx, &sql.TxOptions{})) checkpointLedger := uint32(63) - changeProcessor := buildChangeProcessor(q, &ingest.StatsChangeProcessor{}, ledgerSource, checkpointLedger, "") + changeProcessor := buildChangeProcessor(q, &ingest.StatsChangeProcessor{}, ledgerSource, checkpointLedger, "", false) gen := randxdr.NewGenerator() var changes []xdr.LedgerEntryChange @@ -350,7 +350,7 @@ func TestStateVerifier(t *testing.T) { ledger := rand.Int31() checkpointLedger := uint32(ledger - (ledger % 64) - 1) - changeProcessor := buildChangeProcessor(q, &ingest.StatsChangeProcessor{}, ledgerSource, checkpointLedger, "") + changeProcessor := buildChangeProcessor(q, &ingest.StatsChangeProcessor{}, ledgerSource, checkpointLedger, "", false) mockChangeReader := &ingest.MockChangeReader{} gen := randxdr.NewGenerator() diff --git a/services/horizon/internal/init.go b/services/horizon/internal/init.go index 1b6664b8ba..4078c7ad00 100644 --- a/services/horizon/internal/init.go +++ b/services/horizon/internal/init.go @@ -110,6 +110,7 @@ func initIngester(app *App) { EnableExtendedLogLedgerStats: app.config.IngestEnableExtendedLogLedgerStats, RoundingSlippageFilter: app.config.RoundingSlippageFilter, EnableIngestionFiltering: app.config.EnableIngestionFiltering, + SkipSorobanIngestion: app.config.SkipSorobanIngestion, }) if err != nil { diff --git a/services/horizon/internal/integration/invokehostfunction_test.go b/services/horizon/internal/integration/invokehostfunction_test.go index 275f0de23b..1b1edc091a 100644 --- a/services/horizon/internal/integration/invokehostfunction_test.go +++ b/services/horizon/internal/integration/invokehostfunction_test.go @@ -3,11 +3,13 @@ package integration import ( "crypto/sha256" "encoding/hex" + "fmt" "os" "path/filepath" "testing" "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/protocols/horizon/operations" "github.com/stellar/go/services/horizon/internal/test/integration" "github.com/stellar/go/txnbuild" @@ -24,13 +26,42 @@ const increment_contract = "soroban_increment_contract.wasm" // Refer to ./services/horizon/internal/integration/contracts/README.md on how to recompile // contract code if needed to new wasm. -func TestContractInvokeHostFunctionInstallContract(t *testing.T) { +func TestInvokeHostFns(t *testing.T) { + // first test contracts when soroban processing is enabled + DisabledSoroban = false + runAllTests(t) + // now test same contracts when soroban processing is disabled + DisabledSoroban = true + runAllTests(t) +} + +func runAllTests(t *testing.T) { + tests := []struct { + name string + fn func(*testing.T) + }{ + {"CaseContractInvokeHostFunctionInstallContract", CaseContractInvokeHostFunctionInstallContract}, + {"CaseContractInvokeHostFunctionCreateContractByAddress", CaseContractInvokeHostFunctionCreateContractByAddress}, + {"CaseContractInvokeHostFunctionInvokeStatelessContractFn", CaseContractInvokeHostFunctionInvokeStatelessContractFn}, + {"CaseContractInvokeHostFunctionInvokeStatefulContractFn", CaseContractInvokeHostFunctionInvokeStatefulContractFn}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("Soroban Processing Disabled = %v. ", DisabledSoroban)+tt.name, func(t *testing.T) { + tt.fn(t) + }) + } +} + +func CaseContractInvokeHostFunctionInstallContract(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -46,6 +77,7 @@ func TestContractInvokeHostFunctionInstallContract(t *testing.T) { clientTx, err := itest.Client().TransactionDetail(tx.Hash) require.NoError(t, err) + verifySorobanMeta(t, clientTx) assert.Equal(t, tx.Hash, clientTx.Hash) var txResult xdr.TransactionResult @@ -71,16 +103,17 @@ func TestContractInvokeHostFunctionInstallContract(t *testing.T) { invokeHostFunctionOpJson, ok := clientInvokeOp.Embedded.Records[0].(operations.InvokeHostFunction) assert.True(t, ok) assert.Equal(t, invokeHostFunctionOpJson.Function, "HostFunctionTypeHostFunctionTypeUploadContractWasm") - } -func TestContractInvokeHostFunctionCreateContractByAddress(t *testing.T) { +func CaseContractInvokeHostFunctionCreateContractByAddress(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -103,6 +136,7 @@ func TestContractInvokeHostFunctionCreateContractByAddress(t *testing.T) { clientTx, err := itest.Client().TransactionDetail(tx.Hash) require.NoError(t, err) + verifySorobanMeta(t, clientTx) assert.Equal(t, tx.Hash, clientTx.Hash) var txResult xdr.TransactionResult @@ -128,13 +162,15 @@ func TestContractInvokeHostFunctionCreateContractByAddress(t *testing.T) { assert.Equal(t, invokeHostFunctionOpJson.Salt, "110986164698320180327942133831752629430491002266485370052238869825166557303060") } -func TestContractInvokeHostFunctionInvokeStatelessContractFn(t *testing.T) { +func CaseContractInvokeHostFunctionInvokeStatelessContractFn(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -196,6 +232,7 @@ func TestContractInvokeHostFunctionInvokeStatelessContractFn(t *testing.T) { clientTx, err := itest.Client().TransactionDetail(tx.Hash) require.NoError(t, err) + verifySorobanMeta(t, clientTx) assert.Equal(t, tx.Hash, clientTx.Hash) var txResult xdr.TransactionResult @@ -209,12 +246,14 @@ func TestContractInvokeHostFunctionInvokeStatelessContractFn(t *testing.T) { assert.True(t, ok) assert.Equal(t, invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) - // check the function response, should have summed the two input numbers - invokeResult := xdr.Uint64(9) - expectedScVal := xdr.ScVal{Type: xdr.ScValTypeScvU64, U64: &invokeResult} - var transactionMeta xdr.TransactionMeta - assert.NoError(t, xdr.SafeUnmarshalBase64(tx.ResultMetaXdr, &transactionMeta)) - assert.True(t, expectedScVal.Equals(transactionMeta.V3.SorobanMeta.ReturnValue)) + if !DisabledSoroban { + // check the function response, should have summed the two input numbers + invokeResult := xdr.Uint64(9) + expectedScVal := xdr.ScVal{Type: xdr.ScValTypeScvU64, U64: &invokeResult} + var transactionMeta xdr.TransactionMeta + assert.NoError(t, xdr.SafeUnmarshalBase64(tx.ResultMetaXdr, &transactionMeta)) + assert.True(t, expectedScVal.Equals(transactionMeta.V3.SorobanMeta.ReturnValue)) + } clientInvokeOp, err := itest.Client().Operations(horizonclient.OperationRequest{ ForTransaction: tx.Hash, @@ -237,13 +276,15 @@ func TestContractInvokeHostFunctionInvokeStatelessContractFn(t *testing.T) { assert.Equal(t, invokeHostFunctionOpJson.Parameters[3].Type, "U64") } -func TestContractInvokeHostFunctionInvokeStatefulContractFn(t *testing.T) { +func CaseContractInvokeHostFunctionInvokeStatefulContractFn(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -292,6 +333,7 @@ func TestContractInvokeHostFunctionInvokeStatefulContractFn(t *testing.T) { clientTx, err := itest.Client().TransactionDetail(tx.Hash) require.NoError(t, err) + verifySorobanMeta(t, clientTx) assert.Equal(t, tx.Hash, clientTx.Hash) var txResult xdr.TransactionResult @@ -305,12 +347,14 @@ func TestContractInvokeHostFunctionInvokeStatefulContractFn(t *testing.T) { assert.True(t, ok) assert.Equal(t, invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) - // check the function response, should have incremented state from 0 to 1 - invokeResult := xdr.Uint32(1) - expectedScVal := xdr.ScVal{Type: xdr.ScValTypeScvU32, U32: &invokeResult} - var transactionMeta xdr.TransactionMeta - assert.NoError(t, xdr.SafeUnmarshalBase64(clientTx.ResultMetaXdr, &transactionMeta)) - assert.True(t, expectedScVal.Equals(transactionMeta.V3.SorobanMeta.ReturnValue)) + if !DisabledSoroban { + // check the function response, should have incremented state from 0 to 1 + invokeResult := xdr.Uint32(1) + expectedScVal := xdr.ScVal{Type: xdr.ScValTypeScvU32, U32: &invokeResult} + var transactionMeta xdr.TransactionMeta + assert.NoError(t, xdr.SafeUnmarshalBase64(clientTx.ResultMetaXdr, &transactionMeta)) + assert.True(t, expectedScVal.Equals(transactionMeta.V3.SorobanMeta.ReturnValue)) + } clientInvokeOp, err := itest.Client().Operations(horizonclient.OperationRequest{ ForTransaction: tx.Hash, @@ -384,3 +428,20 @@ func assembleCreateContractOp(t *testing.T, sourceAccount string, wasmFileName s SourceAccount: sourceAccount, } } + +func verifySorobanMeta(t *testing.T, clientTx horizon.Transaction) { + var txMeta xdr.TransactionMeta + err := xdr.SafeUnmarshalBase64(clientTx.ResultMetaXdr, &txMeta) + require.NoError(t, err) + require.NotNil(t, txMeta.V3) + + if !DisabledSoroban { + require.NotNil(t, txMeta.V3.SorobanMeta) + return + } + + require.Empty(t, txMeta.V3.Operations) + require.Empty(t, txMeta.V3.TxChangesAfter) + require.Empty(t, txMeta.V3.TxChangesBefore) + require.Nil(t, txMeta.V3.SorobanMeta) +} diff --git a/services/horizon/internal/integration/sac_test.go b/services/horizon/internal/integration/sac_test.go index 64c772b44c..c790b5a54c 100644 --- a/services/horizon/internal/integration/sac_test.go +++ b/services/horizon/internal/integration/sac_test.go @@ -2,6 +2,7 @@ package integration import ( "context" + "fmt" "math" "math/big" "strings" @@ -30,19 +31,127 @@ const sac_contract = "soroban_sac_test.wasm" // of the integration tests. const LongTermTTL = 10000 +var ( + DisabledSoroban bool +) + +func TestSAC(t *testing.T) { + // first test contracts when soroban processing is enabled + DisabledSoroban = false + runAllSACTests(t) + // now test same contracts when soroban processing is disabled + DisabledSoroban = true + runAllSACTests(t) +} + +func runAllSACTests(t *testing.T) { + tests := []struct { + name string + fn func(*testing.T) + }{ + {"CaseContractMintToAccount", CaseContractMintToAccount}, + {"CaseContractMintToContract", CaseContractMintToContract}, + {"CaseExpirationAndRestoration", CaseExpirationAndRestoration}, + {"CaseContractTransferBetweenAccounts", CaseContractTransferBetweenAccounts}, + {"CaseContractTransferBetweenAccountAndContract", CaseContractTransferBetweenAccountAndContract}, + {"CaseContractTransferBetweenContracts", CaseContractTransferBetweenContracts}, + {"CaseContractBurnFromAccount", CaseContractBurnFromAccount}, + {"CaseContractBurnFromContract", CaseContractBurnFromContract}, + {"CaseContractClawbackFromAccount", CaseContractClawbackFromAccount}, + {"CaseContractClawbackFromContract", CaseContractClawbackFromContract}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("Soroban Processing Disabled = %v. ", DisabledSoroban)+tt.name, func(t *testing.T) { + tt.fn(t) + }) + } +} + // Tests use precompiled wasm bin files that are added to the testdata directory. // Refer to ./services/horizon/internal/integration/contracts/README.md on how to recompile // contract code if needed to new wasm. -func TestContractMintToAccount(t *testing.T) { +func createSAC(itest *integration.Test, asset xdr.Asset) { + invokeHostFunction := &txnbuild.InvokeHostFunction{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeCreateContract, + CreateContract: &xdr.CreateContractArgs{ + ContractIdPreimage: xdr.ContractIdPreimage{ + Type: xdr.ContractIdPreimageTypeContractIdPreimageFromAsset, + FromAsset: &asset, + }, + Executable: xdr.ContractExecutable{ + Type: xdr.ContractExecutableTypeContractExecutableStellarAsset, + WasmHash: nil, + }, + }, + }, + SourceAccount: itest.Master().Address(), + } + _, _, preFlightOp := assertInvokeHostFnSucceeds(itest, itest.Master(), invokeHostFunction) + sourceAccount, extendTTLOp, minFee := itest.PreflightExtendExpiration( + itest.Master().Address(), + preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite, + LongTermTTL, + ) + itest.MustSubmitOperationsWithFee(&sourceAccount, itest.Master(), minFee+txnbuild.MinBaseFee, &extendTTLOp) +} + +func invokeStoreSet( + itest *integration.Test, + storeContractID xdr.Hash, + ledgerEntryData xdr.LedgerEntryData, +) *txnbuild.InvokeHostFunction { + key := ledgerEntryData.MustContractData().Key + val := ledgerEntryData.MustContractData().Val + return &txnbuild.InvokeHostFunction{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, + InvokeContract: &xdr.InvokeContractArgs{ + ContractAddress: contractIDParam(storeContractID), + FunctionName: "set", + Args: xdr.ScVec{ + key, + val, + }, + }, + }, + SourceAccount: itest.Master().Address(), + } +} + +func invokeStoreRemove( + itest *integration.Test, + storeContractID xdr.Hash, + ledgerKey xdr.LedgerKey, +) *txnbuild.InvokeHostFunction { + return &txnbuild.InvokeHostFunction{ + HostFunction: xdr.HostFunction{ + Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, + InvokeContract: &xdr.InvokeContractArgs{ + ContractAddress: contractIDParam(storeContractID), + FunctionName: "remove", + Args: xdr.ScVec{ + ledgerKey.MustContractData().Key, + }, + }, + }, + SourceAccount: itest.Master().Address(), + } +} + +func CaseContractMintToAccount(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, - HorizonEnvironment: map[string]string{"INGEST_DISABLE_STATE_VERIFICATION": "true", "CONNECTION_TIMEOUT": "360000"}, - EnableSorobanRPC: true, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban), + }, + EnableSorobanRPC: true, }) issuer := itest.Master().Address() @@ -72,17 +181,22 @@ func TestContractMintToAccount(t *testing.T) { balanceContracts: big.NewInt(0), contractID: stellarAssetContractID(itest, asset), }) - - fx := getTxEffects(itest, mintTx, asset) - require.Len(t, fx, 1) - creditEffect := assertContainsEffect(t, fx, - effects.EffectAccountCredited)[0].(effects.AccountCredited) - assert.Equal(t, recipientKp.Address(), creditEffect.Account) - assert.Equal(t, issuer, creditEffect.Asset.Issuer) - assert.Equal(t, code, creditEffect.Asset.Code) - assert.Equal(t, "20.0000000", creditEffect.Amount) assertEventPayments(itest, mintTx, asset, "", recipient.GetAccountID(), "mint", "20.0000000") + if !DisabledSoroban { + fx := getTxEffects(itest, mintTx, asset) + require.Len(t, fx, 1) + creditEffect := assertContainsEffect(t, fx, + effects.EffectAccountCredited)[0].(effects.AccountCredited) + assert.Equal(t, recipientKp.Address(), creditEffect.Account) + assert.Equal(t, issuer, creditEffect.Asset.Issuer) + assert.Equal(t, code, creditEffect.Asset.Code) + assert.Equal(t, "20.0000000", creditEffect.Amount) + } else { + fx := getTxEffects(itest, mintTx, asset) + require.Len(t, fx, 0) + } + otherRecipientKp, otherRecipient := itest.CreateAccount("100") itest.MustEstablishTrustline(otherRecipientKp, otherRecipient, txnbuild.MustAssetFromXDR(asset)) @@ -94,12 +208,6 @@ func TestContractMintToAccount(t *testing.T) { ) assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("20")) assertContainsBalance(itest, otherRecipientKp, issuer, code, amount.MustParse("30")) - - fx = getTxEffects(itest, transferTx, asset) - assert.Len(t, fx, 2) - assertContainsEffect(t, fx, - effects.EffectAccountCredited, - effects.EffectAccountDebited) assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -111,41 +219,28 @@ func TestContractMintToAccount(t *testing.T) { balanceContracts: big.NewInt(0), contractID: stellarAssetContractID(itest, asset), }) -} -func createSAC(itest *integration.Test, asset xdr.Asset) { - invokeHostFunction := &txnbuild.InvokeHostFunction{ - HostFunction: xdr.HostFunction{ - Type: xdr.HostFunctionTypeHostFunctionTypeCreateContract, - CreateContract: &xdr.CreateContractArgs{ - ContractIdPreimage: xdr.ContractIdPreimage{ - Type: xdr.ContractIdPreimageTypeContractIdPreimageFromAsset, - FromAsset: &asset, - }, - Executable: xdr.ContractExecutable{ - Type: xdr.ContractExecutableTypeContractExecutableStellarAsset, - WasmHash: nil, - }, - }, - }, - SourceAccount: itest.Master().Address(), + if !DisabledSoroban { + fx := getTxEffects(itest, transferTx, asset) + assert.Len(t, fx, 2) + assertContainsEffect(t, fx, + effects.EffectAccountCredited, + effects.EffectAccountDebited) + } else { + fx := getTxEffects(itest, transferTx, asset) + require.Len(t, fx, 0) } - _, _, preFlightOp := assertInvokeHostFnSucceeds(itest, itest.Master(), invokeHostFunction) - sourceAccount, extendTTLOp, minFee := itest.PreflightExtendExpiration( - itest.Master().Address(), - preFlightOp.Ext.SorobanData.Resources.Footprint.ReadWrite, - LongTermTTL, - ) - itest.MustSubmitOperationsWithFee(&sourceAccount, itest.Master(), minFee+txnbuild.MinBaseFee, &extendTTLOp) } -func TestContractMintToContract(t *testing.T) { +func CaseContractMintToContract(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -170,19 +265,25 @@ func TestContractMintToContract(t *testing.T) { i128Param(int64(mintAmount.Hi), uint64(mintAmount.Lo)), contractAddressParam(recipientContractID)), ) - assertContainsEffect(t, getTxEffects(itest, mintTx, asset), - effects.EffectContractCredited) - balanceAmount, _, _ := assertInvokeHostFnSucceeds( - itest, - itest.Master(), - contractBalance(itest, issuer, asset, recipientContractID), - ) - assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, balanceAmount.Type) - assert.Equal(itest.CurrentTest(), xdr.Uint64(math.MaxUint64-3), (*balanceAmount.I128).Lo) - assert.Equal(itest.CurrentTest(), xdr.Int64(math.MaxInt64), (*balanceAmount.I128).Hi) assertEventPayments(itest, mintTx, asset, "", strkeyRecipientContractID, "mint", amount.String128(mintAmount)) + if !DisabledSoroban { + assertContainsEffect(t, getTxEffects(itest, mintTx, asset), + effects.EffectContractCredited) + + balanceAmount, _, _ := assertInvokeHostFnSucceeds( + itest, + itest.Master(), + contractBalance(itest, issuer, asset, recipientContractID), + ) + assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, balanceAmount.Type) + assert.Equal(itest.CurrentTest(), xdr.Uint64(math.MaxUint64-3), (*balanceAmount.I128).Lo) + assert.Equal(itest.CurrentTest(), xdr.Int64(math.MaxInt64), (*balanceAmount.I128).Hi) + } else { + fx := getTxEffects(itest, mintTx, asset) + require.Len(t, fx, 0) + } // calling transfer from the issuer account will also mint the asset _, transferTx, _ := assertInvokeHostFnSucceeds( itest, @@ -190,19 +291,6 @@ func TestContractMintToContract(t *testing.T) { transferWithAmount(itest, issuer, asset, i128Param(0, 3), contractAddressParam(recipientContractID)), ) - assertContainsEffect(t, getTxEffects(itest, transferTx, asset), - effects.EffectAccountDebited, - effects.EffectContractCredited) - - balanceAmount, _, _ = assertInvokeHostFnSucceeds( - itest, - itest.Master(), - contractBalance(itest, issuer, asset, recipientContractID), - ) - assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, balanceAmount.Type) - assert.Equal(itest.CurrentTest(), xdr.Uint64(math.MaxUint64), (*balanceAmount.I128).Lo) - assert.Equal(itest.CurrentTest(), xdr.Int64(math.MaxInt64), (*balanceAmount.I128).Hi) - // 2^127 - 1 balanceContracts := new(big.Int).Lsh(big.NewInt(1), 127) balanceContracts.Sub(balanceContracts, big.NewInt(1)) @@ -217,9 +305,27 @@ func TestContractMintToContract(t *testing.T) { balanceContracts: balanceContracts, contractID: stellarAssetContractID(itest, asset), }) + + if !DisabledSoroban { + assertContainsEffect(t, getTxEffects(itest, transferTx, asset), + effects.EffectAccountDebited, + effects.EffectContractCredited) + + balanceAmount, _, _ := assertInvokeHostFnSucceeds( + itest, + itest.Master(), + contractBalance(itest, issuer, asset, recipientContractID), + ) + assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, balanceAmount.Type) + assert.Equal(itest.CurrentTest(), xdr.Uint64(math.MaxUint64), (*balanceAmount.I128).Lo) + assert.Equal(itest.CurrentTest(), xdr.Int64(math.MaxInt64), (*balanceAmount.I128).Hi) + } else { + fx := getTxEffects(itest, transferTx, asset) + require.Len(t, fx, 0) + } } -func TestExpirationAndRestoration(t *testing.T) { +func CaseExpirationAndRestoration(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } @@ -232,6 +338,7 @@ func TestExpirationAndRestoration(t *testing.T) { // a fake asset contract in the horizon db and we don't // want state verification to detect this "ingest-disable-state-verification": "true", + "disable-soroban-ingest": fmt.Sprint(DisabledSoroban), }, }) @@ -294,6 +401,7 @@ func TestExpirationAndRestoration(t *testing.T) { LongTermTTL, ) itest.MustSubmitOperationsWithFee(&sourceAccount, itest.Master(), minFee+txnbuild.MinBaseFee, &extendTTLOp) + assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -321,6 +429,16 @@ func TestExpirationAndRestoration(t *testing.T) { balanceToExpire, ), ) + + balanceToExpireLedgerKey := xdr.LedgerKey{ + Type: xdr.LedgerEntryTypeContractData, + ContractData: &xdr.LedgerKeyContractData{ + Contract: balanceToExpire.ContractData.Contract, + Key: balanceToExpire.ContractData.Key, + Durability: balanceToExpire.ContractData.Durability, + }, + } + assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -333,14 +451,6 @@ func TestExpirationAndRestoration(t *testing.T) { contractID: storeContractID, }) - balanceToExpireLedgerKey := xdr.LedgerKey{ - Type: xdr.LedgerEntryTypeContractData, - ContractData: &xdr.LedgerKeyContractData{ - Contract: balanceToExpire.ContractData.Contract, - Key: balanceToExpire.ContractData.Key, - Durability: balanceToExpire.ContractData.Durability, - }, - } // The TESTING_MINIMUM_PERSISTENT_ENTRY_LIFETIME=10 configuration in stellar-core // will ensure that the ledger entry expires after 10 ledgers. // Because ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING is set to true, 10 ledgers @@ -372,6 +482,7 @@ func TestExpirationAndRestoration(t *testing.T) { ), ), ) + assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -390,6 +501,7 @@ func TestExpirationAndRestoration(t *testing.T) { balanceToExpireLedgerKey, ) itest.MustSubmitOperationsWithFee(&sourceAccount, itest.Master(), minFee+txnbuild.MinBaseFee, &restoreFootprint) + assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -419,6 +531,7 @@ func TestExpirationAndRestoration(t *testing.T) { ), ), ) + assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -444,6 +557,7 @@ func TestExpirationAndRestoration(t *testing.T) { ), ), ) + assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -457,56 +571,15 @@ func TestExpirationAndRestoration(t *testing.T) { }) } -func invokeStoreSet( - itest *integration.Test, - storeContractID xdr.Hash, - ledgerEntryData xdr.LedgerEntryData, -) *txnbuild.InvokeHostFunction { - key := ledgerEntryData.MustContractData().Key - val := ledgerEntryData.MustContractData().Val - return &txnbuild.InvokeHostFunction{ - HostFunction: xdr.HostFunction{ - Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, - InvokeContract: &xdr.InvokeContractArgs{ - ContractAddress: contractIDParam(storeContractID), - FunctionName: "set", - Args: xdr.ScVec{ - key, - val, - }, - }, - }, - SourceAccount: itest.Master().Address(), - } -} - -func invokeStoreRemove( - itest *integration.Test, - storeContractID xdr.Hash, - ledgerKey xdr.LedgerKey, -) *txnbuild.InvokeHostFunction { - return &txnbuild.InvokeHostFunction{ - HostFunction: xdr.HostFunction{ - Type: xdr.HostFunctionTypeHostFunctionTypeInvokeContract, - InvokeContract: &xdr.InvokeContractArgs{ - ContractAddress: contractIDParam(storeContractID), - FunctionName: "remove", - Args: xdr.ScVec{ - ledgerKey.MustContractData().Key, - }, - }, - }, - SourceAccount: itest.Master().Address(), - } -} - -func TestContractTransferBetweenAccounts(t *testing.T) { +func CaseContractTransferBetweenAccounts(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -534,6 +607,7 @@ func TestContractTransferBetweenAccounts(t *testing.T) { ) assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("1000")) + assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -557,10 +631,6 @@ func TestContractTransferBetweenAccounts(t *testing.T) { assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("970")) assertContainsBalance(itest, otherRecipientKp, issuer, code, amount.MustParse("30")) - - fx := getTxEffects(itest, transferTx, asset) - assert.NotEmpty(t, fx) - assertContainsEffect(t, fx, effects.EffectAccountCredited, effects.EffectAccountDebited) assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -573,15 +643,26 @@ func TestContractTransferBetweenAccounts(t *testing.T) { contractID: stellarAssetContractID(itest, asset), }) assertEventPayments(itest, transferTx, asset, recipientKp.Address(), otherRecipient.GetAccountID(), "transfer", "30.0000000") + + if !DisabledSoroban { + fx := getTxEffects(itest, transferTx, asset) + assert.NotEmpty(t, fx) + assertContainsEffect(t, fx, effects.EffectAccountCredited, effects.EffectAccountDebited) + } else { + fx := getTxEffects(itest, transferTx, asset) + require.Len(t, fx, 0) + } } -func TestContractTransferBetweenAccountAndContract(t *testing.T) { +func CaseContractTransferBetweenAccountAndContract(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -627,9 +708,6 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) { mint(itest, issuer, asset, "1000", contractAddressParam(recipientContractID)), ) assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("1000")) - assertContainsEffect(t, getTxEffects(itest, mintTx, asset), - effects.EffectContractCredited) - assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -642,6 +720,14 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) { contractID: stellarAssetContractID(itest, asset), }) + if !DisabledSoroban { + assertContainsEffect(t, getTxEffects(itest, mintTx, asset), + effects.EffectContractCredited) + } else { + fx := getTxEffects(itest, mintTx, asset) + require.Len(t, fx, 0) + } + // transfer from account to contract _, transferTx, _ := assertInvokeHostFnSucceeds( itest, @@ -649,8 +735,6 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) { transfer(itest, recipientKp.Address(), asset, "30", contractAddressParam(recipientContractID)), ) assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("970")) - assertContainsEffect(t, getTxEffects(itest, transferTx, asset), - effects.EffectAccountDebited, effects.EffectContractCredited) assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -664,14 +748,19 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) { }) assertEventPayments(itest, transferTx, asset, recipientKp.Address(), strkeyRecipientContractID, "transfer", "30.0000000") + if !DisabledSoroban { + assertContainsEffect(t, getTxEffects(itest, transferTx, asset), + effects.EffectAccountDebited, effects.EffectContractCredited) + } else { + fx := getTxEffects(itest, transferTx, asset) + require.Len(t, fx, 0) + } // transfer from contract to account _, transferTx, _ = assertInvokeHostFnSucceeds( itest, recipientKp, transferFromContract(itest, recipientKp.Address(), asset, recipientContractID, recipientContractHash, "500", accountAddressParam(recipient.GetAccountID())), ) - assertContainsEffect(t, getTxEffects(itest, transferTx, asset), - effects.EffectContractDebited, effects.EffectAccountCredited) assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("1470")) assertAssetStats(itest, assetStats{ code: code, @@ -686,6 +775,13 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) { }) assertEventPayments(itest, transferTx, asset, strkeyRecipientContractID, recipientKp.Address(), "transfer", "500.0000000") + if DisabledSoroban { + fx := getTxEffects(itest, transferTx, asset) + require.Len(t, fx, 0) + return + } + assertContainsEffect(t, getTxEffects(itest, transferTx, asset), + effects.EffectContractDebited, effects.EffectAccountCredited) balanceAmount, _, _ := assertInvokeHostFnSucceeds( itest, itest.Master(), @@ -696,13 +792,15 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) { assert.Equal(itest.CurrentTest(), xdr.Int64(0), (*balanceAmount.I128).Hi) } -func TestContractTransferBetweenContracts(t *testing.T) { +func CaseContractTransferBetweenContracts(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -742,8 +840,28 @@ func TestContractTransferBetweenContracts(t *testing.T) { itest.Master(), transferFromContract(itest, issuer, asset, emitterContractID, emitterContractHash, "10", contractAddressParam(recipientContractID)), ) - assertContainsEffect(t, getTxEffects(itest, transferTx, asset), - effects.EffectContractCredited, effects.EffectContractDebited) + + assertAssetStats(itest, assetStats{ + code: code, + issuer: issuer, + numAccounts: 0, + balanceAccounts: 0, + balanceArchivedContracts: big.NewInt(0), + numArchivedContracts: 0, + numContracts: 2, + balanceContracts: big.NewInt(int64(amount.MustParse("1000"))), + contractID: stellarAssetContractID(itest, asset), + }) + assertEventPayments(itest, transferTx, asset, strkeyEmitterContractID, strkeyRecipientContractID, "transfer", "10.0000000") + + if !DisabledSoroban { + assertContainsEffect(t, getTxEffects(itest, transferTx, asset), + effects.EffectContractCredited, effects.EffectContractDebited) + } else { + fx := getTxEffects(itest, transferTx, asset) + require.Len(t, fx, 0) + return + } // Check balances of emitter and recipient emitterBalanceAmount, _, _ := assertInvokeHostFnSucceeds( @@ -763,28 +881,17 @@ func TestContractTransferBetweenContracts(t *testing.T) { assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, recipientBalanceAmount.Type) assert.Equal(itest.CurrentTest(), xdr.Uint64(100000000), (*recipientBalanceAmount.I128).Lo) assert.Equal(itest.CurrentTest(), xdr.Int64(0), (*recipientBalanceAmount.I128).Hi) - - assertAssetStats(itest, assetStats{ - code: code, - issuer: issuer, - numAccounts: 0, - balanceAccounts: 0, - balanceArchivedContracts: big.NewInt(0), - numArchivedContracts: 0, - numContracts: 2, - balanceContracts: big.NewInt(int64(amount.MustParse("1000"))), - contractID: stellarAssetContractID(itest, asset), - }) - assertEventPayments(itest, transferTx, asset, strkeyEmitterContractID, strkeyRecipientContractID, "transfer", "10.0000000") } -func TestContractBurnFromAccount(t *testing.T) { +func CaseContractBurnFromAccount(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -830,16 +937,6 @@ func TestContractBurnFromAccount(t *testing.T) { burn(itest, recipientKp.Address(), asset, "500"), ) - fx := getTxEffects(itest, burnTx, asset) - require.Len(t, fx, 1) - assetEffects := assertContainsEffect(t, fx, effects.EffectAccountDebited) - require.GreaterOrEqual(t, len(assetEffects), 1) - burnEffect := assetEffects[0].(effects.AccountDebited) - - assert.Equal(t, issuer, burnEffect.Asset.Issuer) - assert.Equal(t, code, burnEffect.Asset.Code) - assert.Equal(t, "500.0000000", burnEffect.Amount) - assert.Equal(t, recipientKp.Address(), burnEffect.Account) assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -852,15 +949,33 @@ func TestContractBurnFromAccount(t *testing.T) { contractID: stellarAssetContractID(itest, asset), }) assertEventPayments(itest, burnTx, asset, recipientKp.Address(), "", "burn", "500.0000000") + + if !DisabledSoroban { + fx := getTxEffects(itest, burnTx, asset) + require.Len(t, fx, 1) + assetEffects := assertContainsEffect(t, fx, effects.EffectAccountDebited) + require.GreaterOrEqual(t, len(assetEffects), 1) + burnEffect := assetEffects[0].(effects.AccountDebited) + + assert.Equal(t, issuer, burnEffect.Asset.Issuer) + assert.Equal(t, code, burnEffect.Asset.Code) + assert.Equal(t, "500.0000000", burnEffect.Amount) + assert.Equal(t, recipientKp.Address(), burnEffect.Account) + } else { + fx := getTxEffects(itest, burnTx, asset) + require.Len(t, fx, 0) + } } -func TestContractBurnFromContract(t *testing.T) { +func CaseContractBurnFromContract(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -895,19 +1010,6 @@ func TestContractBurnFromContract(t *testing.T) { burnSelf(itest, issuer, asset, recipientContractID, recipientContractHash, "10"), ) - balanceAmount, _, _ := assertInvokeHostFnSucceeds( - itest, - itest.Master(), - contractBalance(itest, issuer, asset, recipientContractID), - ) - - assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, balanceAmount.Type) - assert.Equal(itest.CurrentTest(), xdr.Uint64(9900000000), (*balanceAmount.I128).Lo) - assert.Equal(itest.CurrentTest(), xdr.Int64(0), (*balanceAmount.I128).Hi) - - assertContainsEffect(t, getTxEffects(itest, burnTx, asset), - effects.EffectContractDebited) - assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -920,15 +1022,35 @@ func TestContractBurnFromContract(t *testing.T) { contractID: stellarAssetContractID(itest, asset), }) assertEventPayments(itest, burnTx, asset, strkeyRecipientContractID, "", "burn", "10.0000000") + + if !DisabledSoroban { + balanceAmount, _, _ := assertInvokeHostFnSucceeds( + itest, + itest.Master(), + contractBalance(itest, issuer, asset, recipientContractID), + ) + + assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, balanceAmount.Type) + assert.Equal(itest.CurrentTest(), xdr.Uint64(9900000000), (*balanceAmount.I128).Lo) + assert.Equal(itest.CurrentTest(), xdr.Int64(0), (*balanceAmount.I128).Hi) + + assertContainsEffect(t, getTxEffects(itest, burnTx, asset), + effects.EffectContractDebited) + } else { + fx := getTxEffects(itest, burnTx, asset) + require.Len(t, fx, 0) + } } -func TestContractClawbackFromAccount(t *testing.T) { +func CaseContractClawbackFromAccount(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -966,6 +1088,7 @@ func TestContractClawbackFromAccount(t *testing.T) { ) assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("1000")) + assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -983,8 +1106,6 @@ func TestContractClawbackFromAccount(t *testing.T) { itest.Master(), clawback(itest, issuer, asset, "1000", accountAddressParam(recipientKp.Address())), ) - - assertContainsEffect(t, getTxEffects(itest, clawTx, asset), effects.EffectAccountDebited) assertContainsBalance(itest, recipientKp, issuer, code, 0) assertAssetStats(itest, assetStats{ code: code, @@ -998,15 +1119,24 @@ func TestContractClawbackFromAccount(t *testing.T) { contractID: stellarAssetContractID(itest, asset), }) assertEventPayments(itest, clawTx, asset, recipientKp.Address(), "", "clawback", "1000.0000000") + + if !DisabledSoroban { + assertContainsEffect(t, getTxEffects(itest, clawTx, asset), effects.EffectAccountDebited) + } else { + fx := getTxEffects(itest, clawTx, asset) + require.Len(t, fx, 0) + } } -func TestContractClawbackFromContract(t *testing.T) { +func CaseContractClawbackFromContract(t *testing.T) { if integration.GetCoreMaxSupportedProtocol() < 20 { t.Skip("This test run does not support less than Protocol 20") } itest := integration.NewTest(t, integration.Config{ - ProtocolVersion: 20, + ProtocolVersion: 20, + HorizonEnvironment: map[string]string{ + "DISABLE_SOROBAN_INGEST": fmt.Sprint(DisabledSoroban)}, EnableSorobanRPC: true, }) @@ -1044,19 +1174,6 @@ func TestContractClawbackFromContract(t *testing.T) { itest.Master(), clawback(itest, issuer, asset, "10", contractAddressParam(recipientContractID)), ) - - balanceAmount, _, _ := assertInvokeHostFnSucceeds( - itest, - itest.Master(), - contractBalance(itest, issuer, asset, recipientContractID), - ) - assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, balanceAmount.Type) - assert.Equal(itest.CurrentTest(), xdr.Uint64(9900000000), (*balanceAmount.I128).Lo) - assert.Equal(itest.CurrentTest(), xdr.Int64(0), (*balanceAmount.I128).Hi) - - assertContainsEffect(t, getTxEffects(itest, clawTx, asset), - effects.EffectContractDebited) - assertAssetStats(itest, assetStats{ code: code, issuer: issuer, @@ -1069,6 +1186,23 @@ func TestContractClawbackFromContract(t *testing.T) { contractID: stellarAssetContractID(itest, asset), }) assertEventPayments(itest, clawTx, asset, strkeyRecipientContractID, "", "clawback", "10.0000000") + + if !DisabledSoroban { + balanceAmount, _, _ := assertInvokeHostFnSucceeds( + itest, + itest.Master(), + contractBalance(itest, issuer, asset, recipientContractID), + ) + assert.Equal(itest.CurrentTest(), xdr.ScValTypeScvI128, balanceAmount.Type) + assert.Equal(itest.CurrentTest(), xdr.Uint64(9900000000), (*balanceAmount.I128).Lo) + assert.Equal(itest.CurrentTest(), xdr.Int64(0), (*balanceAmount.I128).Hi) + + assertContainsEffect(t, getTxEffects(itest, clawTx, asset), + effects.EffectContractDebited) + } else { + fx := getTxEffects(itest, clawTx, asset) + require.Len(t, fx, 0) + } } func assertContainsBalance(itest *integration.Test, acct *keypair.Full, issuer, code string, amt xdr.Int64) { @@ -1179,6 +1313,12 @@ func assertEventPayments(itest *integration.Test, txHash string, asset xdr.Asset invokeHostFn := ops.Embedded.Records[0].(operations.InvokeHostFunction) assert.Equal(itest.CurrentTest(), invokeHostFn.Function, "HostFunctionTypeHostFunctionTypeInvokeContract") + + if DisabledSoroban { + require.Equal(itest.CurrentTest(), 0, len(invokeHostFn.AssetBalanceChanges)) + return + } + require.Equal(itest.CurrentTest(), 1, len(invokeHostFn.AssetBalanceChanges)) assetBalanceChange := invokeHostFn.AssetBalanceChanges[0] assert.Equal(itest.CurrentTest(), assetBalanceChange.Amount, amount) @@ -1400,10 +1540,6 @@ func assertInvokeHostFnSucceeds(itest *integration.Test, signer *keypair.Full, o err = xdr.SafeUnmarshalBase64(clientTx.ResultXdr, &txResult) require.NoError(itest.CurrentTest(), err) - var txMetaResult xdr.TransactionMeta - err = xdr.SafeUnmarshalBase64(clientTx.ResultMetaXdr, &txMetaResult) - require.NoError(itest.CurrentTest(), err) - opResults, ok := txResult.OperationResults() assert.True(itest.CurrentTest(), ok) assert.Equal(itest.CurrentTest(), len(opResults), 1) @@ -1411,9 +1547,18 @@ func assertInvokeHostFnSucceeds(itest *integration.Test, signer *keypair.Full, o assert.True(itest.CurrentTest(), ok) assert.Equal(itest.CurrentTest(), invokeHostFunctionResult.Code, xdr.InvokeHostFunctionResultCodeInvokeHostFunctionSuccess) - returnValue := txMetaResult.MustV3().SorobanMeta.ReturnValue + var returnValue *xdr.ScVal + + if !DisabledSoroban { + var txMetaResult xdr.TransactionMeta + err = xdr.SafeUnmarshalBase64(clientTx.ResultMetaXdr, &txMetaResult) + require.NoError(itest.CurrentTest(), err) + returnValue = &txMetaResult.MustV3().SorobanMeta.ReturnValue + } else { + verifySorobanMeta(itest.CurrentTest(), clientTx) + } - return &returnValue, clientTx.Hash, &preFlightOp + return returnValue, clientTx.Hash, &preFlightOp } func stellarAssetContractID(itest *integration.Test, asset xdr.Asset) xdr.Hash {