diff --git a/.github/workflows/horizon.yml b/.github/workflows/horizon.yml index a8fd3ab977..4e012ff323 100644 --- a/.github/workflows/horizon.yml +++ b/.github/workflows/horizon.yml @@ -15,7 +15,7 @@ jobs: go: [1.17, 1.18] pg: [9.6.5] ingestion-backend: [db, captive-core, captive-core-remote-storage] - captive-core: [18.5.0-873.rc1.d387c6a71.focal] + protocol-version: [18, 19] runs-on: ${{ matrix.os }} services: postgres: @@ -32,7 +32,12 @@ jobs: ports: - 5432:5432 env: - HORIZON_INTEGRATION_TESTS: "true" + HORIZON_INTEGRATION_TESTS_ENABLED: true + HORIZON_INTEGRATION_TESTS_CORE_MAX_SUPPORTED_PROTOCOL: ${{ matrix.protocol-version }} + PROTOCOL_19_CORE_DEBIAN_PKG_VERSION: 18.4.1-875.95d896a49.focal~v19unsafe + PROTOCOL_19_CORE_DOCKER_IMG: stellar/stellar-core:18.4.1-875.95d896a49.focal-v19unsafe + PROTOCOL_18_CORE_DEBIAN_PKG_VERSION: 18.5.0-873.rc1.d387c6a71.focal + PROTOCOL_18_CORE_DOCKER_IMG: stellar/stellar-core:18.5.0-873.rc1.d387c6a71.focal PGHOST: localhost PGPORT: 5432 PGUSER: postgres @@ -61,8 +66,11 @@ jobs: with: go-version: ${{ matrix.go }} - - name: Pull latest Stellar Core image - run: docker pull stellar/stellar-core + - name: Pull and set Stellar Core image + shell: bash + run: | + docker pull "$PROTOCOL_${{ matrix.protocol-version }}_CORE_DOCKER_IMG" + echo HORIZON_INTEGRATION_TESTS_DOCKER_IMG="$PROTOCOL_${{ matrix.protocol-version }}_CORE_DOCKER_IMG" >> $GITHUB_ENV - if: ${{ startsWith(matrix.ingestion-backend, 'captive-core') }} name: Install and enable Captive Core @@ -74,14 +82,14 @@ jobs: sudo wget -qO - https://apt.stellar.org/SDF.asc | APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=true sudo apt-key add - sudo bash -c 'echo "deb https://apt.stellar.org focal unstable" > /etc/apt/sources.list.d/SDF-unstable.list' - sudo apt-get update && sudo apt-get install -y stellar-core=${{ matrix.captive-core }} + sudo apt-get update && sudo apt-get install -y stellar-core="$PROTOCOL_${{ matrix.protocol-version }}_CORE_DEBIAN_PKG_VERSION" echo "Using stellar core version $(stellar-core version)" - echo 'HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE=true' >> $GITHUB_ENV - echo 'CAPTIVE_CORE_BIN=/usr/bin/stellar-core' >> $GITHUB_ENV + echo 'HORIZON_INTEGRATION_TESTS_ENABLE_CAPTIVE_CORE=true' >> $GITHUB_ENV + echo 'HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN=/usr/bin/stellar-core' >> $GITHUB_ENV - if: ${{ matrix.ingestion-backend == 'captive-core-remote-storage' }} name: Setup Captive Core Remote Storage - run: echo 'HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE_USE_DB=true' >> $GITHUB_ENV + run: echo 'HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_USE_DB=true' >> $GITHUB_ENV - run: go test -race -timeout 25m -v ./services/horizon/internal/integration/... diff --git a/integration.sh b/integration.sh index 1bd78a5c16..3d98ec5de6 100755 --- a/integration.sh +++ b/integration.sh @@ -3,11 +3,10 @@ set -e cd "$(dirname "${BASH_SOURCE[0]}")" -export HORIZON_INTEGRATION_TESTS=true -export HORIZON_INTEGRATION_ENABLE_CAP_35=${HORIZON_INTEGRATION_ENABLE_CAP_35:-} -export HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE=${HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE:-} -export HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE_USE_DB=${HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE_USE_DB:-} -export CAPTIVE_CORE_BIN=${CAPTIVE_CORE_BIN:-/usr/bin/stellar-core} +export HORIZON_INTEGRATION_TESTS_ENABLED=true +export HORIZON_INTEGRATION_TESTS_ENABLE_CAPTIVE_CORE=${HORIZON_INTEGRATION_TESTS_ENABLE_CAPTIVE_CORE:-} +export HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_USE_DB=${HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_USE_DB:-} +export HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN=${HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN:-/usr/bin/stellar-core} export TRACY_NO_INVARIANT_CHECK=1 # This fails on my dev vm. - Paul # launch postgres if it's not already. diff --git a/services/horizon/TESTING_README.md b/services/horizon/TESTING_README.md index 7061c77541..c7747bccd3 100644 --- a/services/horizon/TESTING_README.md +++ b/services/horizon/TESTING_README.md @@ -25,7 +25,7 @@ Authoring tests to assert coverage is key importance, to facilitate best experie * For multi-table db seeding as part of test setup, use the newer notion of 'fixtures' for sql batch datasets. A 'fixture' is just a helper function that programatically loads DB from a hardcoded set of seed data and uses the session interface to do so. Refer to `services/horizon/internal/db2/history/trade_scenario.go` for example of a 'fixture' dataset. -* For integration tests, they should be located in services/horizon/integration package. Tests located in this package will only run when `HORIZON_INTEGRATION_TESTS=true` is present in environment. +* For integration tests, they should be located in services/horizon/integration package. Tests located in this package will only run when `HORIZON_INTEGRATION_TESTS_ENABLED=true` is present in environment. ## Leverage Scaffolding for Test Cases * Mocked DB unit tests that avoid needing a live db connection: @@ -48,7 +48,7 @@ Authoring tests to assert coverage is key importance, to facilitate best experie * `services/horizon/internal/integration/clawback_test.go` is good example of integration test that uses the scaffolding. - * integration tests only execute when `HORIZON_INTEGRATION_TESTS=true` is present as environment variable. + * integration tests only execute when `HORIZON_INTEGRATION_TESTS_ENABLED=true` is present as environment variable. diff --git a/services/horizon/internal/docs/notes_for_developers.md b/services/horizon/internal/docs/notes_for_developers.md index 0164feedf6..c5696a6f57 100644 --- a/services/horizon/internal/docs/notes_for_developers.md +++ b/services/horizon/internal/docs/notes_for_developers.md @@ -70,12 +70,12 @@ go test github.com/stellar/go/services/horizon/... To run the integration tests, move to top folder of working copy of `go` repo to run all integration tests or /services/horizon to run just Horizon integration tests: ``` -HORIZON_INTEGRATION_TESTS=true go test -race -timeout 25m -v ./... +HORIZON_INTEGRATION_TESTS_ENABLED=true go test -race -timeout 25m -v ./... ``` To run just one specific integration test, e.g. like `TestTxSub`: ``` -HORIZON_INTEGRATION_TESTS=true go test -run TestTxsub -race -timeout 25m -v ./... +HORIZON_INTEGRATION_TESTS_ENABLED=true go test -run TestTxsub -race -timeout 25m -v ./... ``` ## Logging diff --git a/services/horizon/internal/ingest/main.go b/services/horizon/internal/ingest/main.go index 365937940d..26f66bddb0 100644 --- a/services/horizon/internal/ingest/main.go +++ b/services/horizon/internal/ingest/main.go @@ -25,7 +25,7 @@ import ( const ( // MaxSupportedProtocolVersion defines the maximum supported version of // the Stellar protocol. - MaxSupportedProtocolVersion uint32 = 18 + MaxSupportedProtocolVersion uint32 = 19 // CurrentVersion reflects the latest version of the ingestion // algorithm. This value is stored in KV store and is used to decide diff --git a/services/horizon/internal/ingest/processor_runner_test.go b/services/horizon/internal/ingest/processor_runner_test.go index ae7383f7e1..16c9b25e19 100644 --- a/services/horizon/internal/ingest/processor_runner_test.go +++ b/services/horizon/internal/ingest/processor_runner_test.go @@ -2,6 +2,7 @@ package ingest import ( "context" + "fmt" "io" "reflect" "testing" @@ -157,7 +158,12 @@ func TestProcessorRunnerRunHistoryArchiveIngestionProtocolVersionNotSupported(t } _, err := runner.RunHistoryArchiveIngestion(100, 200, xdr.Hash{}) - assert.EqualError(t, err, "Error while checking for supported protocol version: This Horizon version does not support protocol version 200. The latest supported protocol version is 18. Please upgrade to the latest Horizon version.") + assert.EqualError(t, err, + fmt.Sprintf( + "Error while checking for supported protocol version: This Horizon version does not support protocol version 200. The latest supported protocol version is %d. Please upgrade to the latest Horizon version.", + MaxSupportedProtocolVersion, + ), + ) } func TestProcessorRunnerBuildChangeProcessor(t *testing.T) { @@ -342,5 +348,10 @@ func TestProcessorRunnerRunAllProcessorsOnLedgerProtocolVersionNotSupported(t *t } _, err := runner.RunAllProcessorsOnLedger(ledger) - assert.EqualError(t, err, "Error while checking for supported protocol version: This Horizon version does not support protocol version 200. The latest supported protocol version is 18. Please upgrade to the latest Horizon version.") + assert.EqualError(t, err, + fmt.Sprintf( + "Error while checking for supported protocol version: This Horizon version does not support protocol version 200. The latest supported protocol version is %d. Please upgrade to the latest Horizon version.", + MaxSupportedProtocolVersion, + ), + ) } diff --git a/services/horizon/internal/ingest/verify.go b/services/horizon/internal/ingest/verify.go index 2538cf7268..ebfee11def 100644 --- a/services/horizon/internal/ingest/verify.go +++ b/services/horizon/internal/ingest/verify.go @@ -422,6 +422,13 @@ func addAccountsToStateVerifier(ctx context.Context, verifier *verify.StateVerif NumSponsored: xdr.Uint32(row.NumSponsored), NumSponsoring: xdr.Uint32(row.NumSponsoring), SignerSponsoringIDs: signerSponsoringIDs, + Ext: xdr.AccountEntryExtensionV2Ext{ + V: 3, + V3: &xdr.AccountEntryExtensionV3{ + SeqLedger: xdr.Uint32(row.SequenceLedger), + SeqTime: xdr.TimePoint(row.SequenceTime.Unix()), + }, + }, }, }, }, diff --git a/services/horizon/internal/ingest/verify_range_state_test.go b/services/horizon/internal/ingest/verify_range_state_test.go index 6cba5dca6d..9866cc4a50 100644 --- a/services/horizon/internal/ingest/verify_range_state_test.go +++ b/services/horizon/internal/ingest/verify_range_state_test.go @@ -331,6 +331,12 @@ func (s *VerifyRangeStateTestSuite) TestSuccessWithVerify() { xdr.MustAddressPtr(mockAccountID), xdr.MustAddressPtr(sponsor), }, + Ext: xdr.AccountEntryExtensionV2Ext{ + V: 3, + V3: &xdr.AccountEntryExtensionV3{ + SeqTime: xdr.TimePoint(18446744011573954816), + }, + }, }, }, }, diff --git a/services/horizon/internal/integration/claimable_balance_test.go b/services/horizon/internal/integration/claimable_balance_test.go index 84bb8be04d..70f4d5b52c 100644 --- a/services/horizon/internal/integration/claimable_balance_test.go +++ b/services/horizon/internal/integration/claimable_balance_test.go @@ -24,7 +24,7 @@ func TestClaimableBalanceBasics(t *testing.T) { // Ensure predicting claimable balances works. t.Run("BalanceIDs", func(t *testing.T) { - tx, err := itest.CreateSignedTransaction( + tx, err := itest.CreateSignedTransactionFromOps( itest.MasterAccount(), []*keypair.Full{master}, &txnbuild.CreateClaimableBalance{ diff --git a/services/horizon/internal/integration/liquidity_pool_test.go b/services/horizon/internal/integration/liquidity_pool_test.go index 60a5e99f02..3b8770ed2b 100644 --- a/services/horizon/internal/integration/liquidity_pool_test.go +++ b/services/horizon/internal/integration/liquidity_pool_test.go @@ -662,7 +662,7 @@ func TestLiquidityPoolFailedDepositAndWithdraw(t *testing.T) { nonExistentPoolID := [32]byte{0xca, 0xfe} // Failing deposit - tx, err := itest.CreateSignedTransaction(shareAccount, []*keypair.Full{shareKeys}, + tx, err := itest.CreateSignedTransactionFromOps(shareAccount, []*keypair.Full{shareKeys}, &txnbuild.LiquidityPoolDeposit{ LiquidityPoolID: nonExistentPoolID, MaxAmountA: "400", @@ -696,7 +696,7 @@ func TestLiquidityPoolFailedDepositAndWithdraw(t *testing.T) { tt.Equal("0.0000000", deposit.SharesReceived) // Failing withdrawal - tx, err = itest.CreateSignedTransaction(shareAccount, []*keypair.Full{shareKeys}, + tx, err = itest.CreateSignedTransactionFromOps(shareAccount, []*keypair.Full{shareKeys}, &txnbuild.LiquidityPoolWithdraw{ LiquidityPoolID: nonExistentPoolID, Amount: amount.StringFromInt64(int64(10)), diff --git a/services/horizon/internal/integration/transaction_preconditions_test.go b/services/horizon/internal/integration/transaction_preconditions_test.go new file mode 100644 index 0000000000..8efdf1240d --- /dev/null +++ b/services/horizon/internal/integration/transaction_preconditions_test.go @@ -0,0 +1,88 @@ +package integration + +import ( + "testing" + "time" + + "github.com/stellar/go/keypair" + "github.com/stellar/go/services/horizon/internal/test/integration" + "github.com/stellar/go/txnbuild" + "github.com/stretchr/testify/assert" +) + +func TestTransactionPreconditionsMinSeq(t *testing.T) { + if integration.GetCoreMaxSupportedProtocol() < 19 { + t.Skip("Can't run with protocol < 19") + } + tt := assert.New(t) + itest := integration.NewTest(t, integration.Config{}) + master := itest.Master() + masterAccount := itest.MasterAccount() + currentAccountSeq, err := masterAccount.GetSequenceNumber() + tt.NoError(err) + + // Ensure that the minSequence of the transaction is enough + // but the sequence isn't + txParams := buildTXParams(master, masterAccount, currentAccountSeq, currentAccountSeq+100) + + // this errors because the tx.seqNum is more than +1 from sourceAccoubnt.seqNum + _, err = itest.SubmitTransaction(master, txParams) + tt.Error(err) + + // Now the transaction should be submitted without problems + txParams.Preconditions.MinSequenceNumber = ¤tAccountSeq + itest.MustSubmitTransaction(master, txParams) +} + +func TestTransactionPreconditionsTimeBounds(t *testing.T) { + if integration.GetCoreMaxSupportedProtocol() < 19 { + t.Skip("Can't run with protocol < 19") + } + tt := assert.New(t) + itest := integration.NewTest(t, integration.Config{}) + master := itest.Master() + masterAccount := itest.MasterAccount() + currentAccountSeq, err := masterAccount.GetSequenceNumber() + tt.NoError(err) + txParams := buildTXParams(master, masterAccount, currentAccountSeq, currentAccountSeq+1) + + // this errors because the min time is > current tx submit time + txParams.Preconditions.TimeBounds.MinTime = time.Now().Unix() + 3600 + txParams.Preconditions.TimeBounds.MaxTime = time.Now().Unix() + 7200 + _, err = itest.SubmitTransaction(master, txParams) + tt.Error(err) + + // this errors because the max time is < current tx submit time + txParams.Preconditions.TimeBounds.MinTime = 0 + txParams.Preconditions.TimeBounds.MaxTime = time.Now().Unix() - 3600 + _, err = itest.SubmitTransaction(master, txParams) + tt.Error(err) + + // Now the transaction should be submitted without problems, min < current tx submit time < max + txParams.Preconditions.TimeBounds.MinTime = time.Now().Unix() - 3600 + txParams.Preconditions.TimeBounds.MaxTime = time.Now().Unix() + 3600 + itest.MustSubmitTransaction(master, txParams) +} + +func buildTXParams(master *keypair.Full, masterAccount txnbuild.Account, sourceAccountSeq int64, txSequence int64) txnbuild.TransactionParams { + + ops := []txnbuild.Operation{ + &txnbuild.BumpSequence{ + BumpTo: sourceAccountSeq + 10, + }, + } + + return txnbuild.TransactionParams{ + SourceAccount: &txnbuild.SimpleAccount{ + AccountID: masterAccount.GetAccountID(), + Sequence: txSequence, + }, + // Phony operation to run + Operations: ops, + BaseFee: txnbuild.MinBaseFee, + Memo: nil, + Preconditions: txnbuild.Preconditions{ + TimeBounds: txnbuild.NewInfiniteTimeout(), + }, + } +} diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index d307301e7a..fd032e0112 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -41,8 +41,8 @@ const ( ) var ( - RunWithCaptiveCore = os.Getenv("HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE") != "" - RunWithCaptiveCoreUseDB = os.Getenv("HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE_USE_DB") != "" + RunWithCaptiveCore = os.Getenv("HORIZON_INTEGRATION_TESTS_ENABLE_CAPTIVE_CORE") != "" + RunWithCaptiveCoreUseDB = os.Getenv("HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_USE_DB") != "" ) type Config struct { @@ -111,13 +111,20 @@ func NewTestForRemoteHorizon(t *testing.T, horizonURL string, passPhrase string, // // WARNING: This requires Docker Compose installed. func NewTest(t *testing.T, config Config) *Test { - if os.Getenv("HORIZON_INTEGRATION_TESTS") == "" { - t.Skip("skipping integration test: HORIZON_INTEGRATION_TESTS not set") + if os.Getenv("HORIZON_INTEGRATION_TESTS_ENABLED") == "" { + t.Skip("skipping integration test: HORIZON_INTEGRATION_TESTS_ENABLED not set") } // If not specific explicitly, set the protocol to the maximum supported version if config.ProtocolVersion == 0 { + // Default to the maximum supported protocol version config.ProtocolVersion = ingest.MaxSupportedProtocolVersion + // If the environment tells us that Core only supports up to certain version, + // use that. + maxSupportedCoreProtocolFromEnv := GetCoreMaxSupportedProtocol() + if maxSupportedCoreProtocolFromEnv != 0 && maxSupportedCoreProtocolFromEnv < ingest.MaxSupportedProtocolVersion { + config.ProtocolVersion = maxSupportedCoreProtocolFromEnv + } } composePath := findDockerComposePath() @@ -153,7 +160,7 @@ func (i *Test) configureCaptiveCore() { // custom Horizon parameters. if RunWithCaptiveCore { composePath := findDockerComposePath() - i.coreConfig.binaryPath = os.Getenv("CAPTIVE_CORE_BIN") + i.coreConfig.binaryPath = os.Getenv("HORIZON_INTEGRATION_TESTS_CAPTIVE_CORE_BIN") i.coreConfig.configPath = filepath.Join(composePath, "captive-core-integration-tests.cfg") if RunWithCaptiveCoreUseDB { i.coreConfig.useDB = true @@ -190,13 +197,18 @@ func (i *Test) runComposeCommand(args ...string) { cmdline := append([]string{"-f", integrationYaml}, args...) cmd := exec.Command("docker-compose", cmdline...) + coreImageOverride := "" if i.config.CoreDockerImage != "" { + coreImageOverride = i.config.CoreDockerImage + } else if img := os.Getenv("HORIZON_INTEGRATION_TESTS_DOCKER_IMG"); img != "" { + coreImageOverride = img + } + if coreImageOverride != "" { cmd.Env = append( os.Environ(), - fmt.Sprintf("CORE_IMAGE=%s", i.config.CoreDockerImage), + fmt.Sprintf("CORE_IMAGE=%s", coreImageOverride), ) } - i.t.Log("Running", cmd.Env, cmd.Args) out, innerErr := cmd.Output() if exitErr, ok := innerErr.(*exec.ExitError); ok { @@ -672,7 +684,7 @@ func (i *Test) SubmitOperations( func (i *Test) SubmitMultiSigOperations( source txnbuild.Account, signers []*keypair.Full, ops ...txnbuild.Operation, ) (proto.Transaction, error) { - tx, err := i.CreateSignedTransaction(source, signers, ops...) + tx, err := i.CreateSignedTransactionFromOps(source, signers, ops...) if err != nil { return proto.Transaction{}, err } @@ -687,17 +699,39 @@ func (i *Test) MustSubmitMultiSigOperations( return tx } -func (i *Test) CreateSignedTransaction( - source txnbuild.Account, signers []*keypair.Full, ops ...txnbuild.Operation, -) (*txnbuild.Transaction, error) { - txParams := txnbuild.TransactionParams{ - SourceAccount: source, - Operations: ops, - BaseFee: txnbuild.MinBaseFee, - Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, - IncrementSequenceNum: true, +func (i *Test) MustSubmitTransaction(signer *keypair.Full, txParams txnbuild.TransactionParams, +) proto.Transaction { + tx, err := i.SubmitTransaction(signer, txParams) + panicIf(err) + return tx +} + +func (i *Test) SubmitTransaction( + signer *keypair.Full, txParams txnbuild.TransactionParams, +) (proto.Transaction, error) { + return i.SubmitMultiSigTransaction([]*keypair.Full{signer}, txParams) +} + +func (i *Test) SubmitMultiSigTransaction( + signers []*keypair.Full, txParams txnbuild.TransactionParams, +) (proto.Transaction, error) { + tx, err := i.CreateSignedTransaction(signers, txParams) + if err != nil { + return proto.Transaction{}, err } + return i.Client().SubmitTransaction(tx) +} +func (i *Test) MustSubmitMultiSigTransaction( + signers []*keypair.Full, txParams txnbuild.TransactionParams, +) proto.Transaction { + tx, err := i.SubmitMultiSigTransaction(signers, txParams) + panicIf(err) + return tx +} + +func (i *Test) CreateSignedTransaction(signers []*keypair.Full, txParams txnbuild.TransactionParams, +) (*txnbuild.Transaction, error) { tx, err := txnbuild.NewTransaction(txParams) if err != nil { return nil, err @@ -713,6 +747,20 @@ func (i *Test) CreateSignedTransaction( return tx, nil } +func (i *Test) CreateSignedTransactionFromOps( + source txnbuild.Account, signers []*keypair.Full, ops ...txnbuild.Operation, +) (*txnbuild.Transaction, error) { + txParams := txnbuild.TransactionParams{ + SourceAccount: source, + Operations: ops, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, + IncrementSequenceNum: true, + } + + return i.CreateSignedTransaction(signers, txParams) +} + func (i *Test) GetCurrentCoreLedgerSequence() (int, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -826,3 +874,15 @@ func mapToFlags(params map[string]string) []string { } return args } + +func GetCoreMaxSupportedProtocol() uint32 { + str := os.Getenv("HORIZON_INTEGRATION_TESTS_CORE_MAX_SUPPORTED_PROTOCOL") + if str == "" { + return 0 + } + version, err := strconv.ParseUint(str, 10, 32) + if err != nil { + return 0 + } + return uint32(version) +}