Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fcd4aed
Add Protocol 30 (CAP-0084) support
sisuresh Sep 23, 2026
236b6eb
Repin go-stellar-sdk to go-stellar-sdk#6015 (73e446de)
sisuresh Sep 29, 2026
16d3e1d
Ingest stellar-core#5472 CAP-0084 LCM vectors
sisuresh Sep 29, 2026
5647d8d
CI: test protocols 29 and 30, drop 28
sisuresh Sep 29, 2026
de18137
CI: run the P30 leg on stellar-core 73f2bdd1's vnext packages
sisuresh Sep 29, 2026
1a58631
effects: record muxed contract destinations on contract_credited
sisuresh Oct 5, 2026
185126b
Merge origin/main into p30-cap-0084-validate
tamirms Oct 8, 2026
7cbd08e
Repin go-stellar-sdk to go-stellar-sdk#6015 (663a89a1)
tamirms Oct 8, 2026
216905d
ingest: give TestGetState_Read a HAS that matches its bucket
tamirms Oct 8, 2026
9da6d51
effects: test contract_muxed on contract_credited end to end
tamirms Oct 8, 2026
c420bfa
ingest: test ledgers with CAP-0088 millisecond close times
tamirms Oct 8, 2026
8d1b94a
CI: run the P30 leg on stellar-core master's 88d2431d7 vnext build
tamirms Oct 8, 2026
4042796
CHANGELOG: describe the Protocol 30 changes
tamirms Oct 8, 2026
fad3ef0
CI: run the P30 leg on stellar-core master after protocol 30 landed
tamirms Oct 9, 2026
bfb260f
Require the merged go-stellar-sdk Protocol 30 commit
tamirms Oct 14, 2026
a81a6eb
Run the P30 leg on core 3640, which includes CAP-0088
tamirms Oct 14, 2026
8025f43
Run the P30 leg on the stellar-core 30.0.0 release candidate
tamirms Oct 14, 2026
0f057e4
Trim comments and the CHANGELOG, drop the CAP-0088 tests
tamirms Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions .github/workflows/horizon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ on:
pull_request:

jobs:
integration-p28-pkg:
name: Integration tests (P28)
integration-p29-pkg:
name: Integration tests (P29)
uses: ./.github/workflows/integration-tests.yml
with:
protocol_version: '28'
# The P28 leg runs the NEW core and rpc builds at protocol 28 — the point
# is verifying the updated binaries still serve the previous protocol
# (the real configuration during the upgrade window), not re-testing
# already-shipped versions against each other.
protocol_version: '29'
# Until a Protocol 30 stellar-core release is published, this leg runs the
# P29 release that networks run today. Once one is, point it at the new
# core and rpc builds at protocol 29: the point is verifying the updated
# binaries still serve the previous protocol (the real configuration
# during the upgrade window).
core_deb_version: '29.0.0-3589.4eb833373.jammy~buildtests'
core_docker_img: 'stellar/stellar-core:29.0.0-3589.4eb833373.jammy'
stellar_rpc_docker_img: 'stellar/stellar-rpc:29.0.0'
Expand All @@ -23,23 +24,21 @@ jobs:
# latest-go/latest-pg job.
full_matrix: ${{ github.event_name == 'push' }}

integration-p29-pkg:
name: Integration tests (P29)
integration-p30-pkg:
name: Integration tests (P30)
uses: ./.github/workflows/integration-tests.yml
with:
protocol_version: '29'
core_deb_version: '29.0.0-3589.4eb833373.jammy~buildtests'
core_docker_img: 'stellar/stellar-core:29.0.0-3589.4eb833373.jammy'
stellar_rpc_docker_img: 'stellar/stellar-rpc:29.0.0'
full_matrix: ${{ github.event_name == 'push' }}

# integration-p29-src:
# name: Integration tests (P29, core from source)
# uses: ./.github/workflows/integration-tests.yml
# with:
# protocol_version: '29'
# core_git_ref: 'master'
# stellar_rpc_docker_img: 'stellar/stellar-rpc:29.0.0'
protocol_version: '30'
# The stellar-core 30.0.0 release candidate (0e49751aa), where protocol 30
# is the current protocol with CAP-0084 and CAP-0087
# (stellar/stellar-core#5478) and CAP-0088 (#5423), so every ledger after
# the upgrade uses a millisecond close-time StellarValue arm. It requires
# the 95 contract cost params in
# internal/integration/testdata/unlimited-config-v30.xdr.
core_deb_version: '30.0.0-3642.0e49751aa.noble~buildtests'
core_docker_img: 'stellar/unsafe-stellar-core:30.0.0-3642.0e49751aa.noble'
Comment on lines +29 to +30
# No protocol-30 stellar-rpc image yet: NewTest() AMBER-skips RPC tests.
stellar_rpc_docker_img: ''

verify-range:
name: Test (and push) verify-range image
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ on:
required: false
type: string
stellar_rpc_docker_img:
description: 'Full docker image reference for Stellar RPC (e.g. stellar/stellar-rpc:27.0.0)'
required: true
description: 'Full docker image reference for Stellar RPC (e.g. stellar/stellar-rpc:27.0.0). Empty skips the stellar-rpc tests.'
required: false
type: string
full_matrix:
description: 'Run the full go/postgres version matrix. Defaults to false: a single job on the latest go and postgres, which is what external callers (e.g. stellar-core CI) want.'
Expand Down Expand Up @@ -189,6 +189,7 @@ jobs:
echo "Using stellar core version $(stellar-core version)"

- name: Pull and set Stellar RPC image
if: env.STELLAR_RPC_DOCKER_IMG != ''
run: |
docker pull "$STELLAR_RPC_DOCKER_IMG"
echo HORIZON_INTEGRATION_TESTS_STELLAR_RPC_DOCKER_IMG="$STELLAR_RPC_DOCKER_IMG" >> $GITHUB_ENV
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ file. This project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### Added
- Added Protocol 30 support and bumped `MaxSupportedProtocolVersion` to 30 ([#237](https://github.com/stellar/stellar-horizon/pull/237)). The regenerated `go-stellar-sdk` XDR ([go-stellar-sdk#6015](https://github.com/stellar/go-stellar-sdk/pull/6015)) decodes all three Protocol 30 CAPs:
- CAP-0084 (muxed contract addresses): a Stellar Asset Contract `transfer` or `mint` to a muxed contract (`W...`) produces a `contract_credited` effect with new `contract_muxed` and `contract_muxed_id` fields holding the muxed address and id, as `account_muxed` and `account_muxed_id` do for muxed accounts. `contract` stays the base `C...` address. The operation's `asset_balance_changes` entry has the base contract in `to` and the id in `destination_muxed_id`, as for muxed accounts.
- CAP-0088 (millisecond close times): every ledger after the one that applies the Protocol 30 upgrade uses one of the new `STELLAR_VALUE_SIGNED_MS` or `STELLAR_VALUE_EMPTY_TX_SET_MS` `StellarValue` arms. The close times Horizon serves, such as a ledger's `closed_at`, stay whole seconds, but consecutive ledgers can now share the same `closed_at`. A ledger's `header_xdr` carries the new arms, so clients that decode it need Protocol 30 XDR.
- CAP-0087 (ML-DSA host functions) needs no Horizon changes.

### Fixed
- The asset ingestion filter now matches rules on the inner operations of fee-bump transactions. Earlier versions collected operations only from TxV0 and Tx envelopes, so a fee-bump transaction was not ingested even when its inner operations referenced a whitelisted asset.

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ require (
require (
github.com/creachadair/jrpc2 v1.2.0
github.com/fsouza/fake-gcs-server v1.49.2
github.com/stellar/go-stellar-sdk v0.7.2
github.com/stellar/go-stellar-sdk v0.7.4-0.20261014050248-990270d32ebe
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,8 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
github.com/stellar/go-stellar-sdk v0.7.2 h1:LTO860ulPKRmsKgxGTAf8APL2nFtVvRKClhw2unGhyY=
github.com/stellar/go-stellar-sdk v0.7.2/go.mod h1:YTYl7/zFt7oIX5Y4WRgL+3d2upWQDLywlHunZ4fOxvc=
github.com/stellar/go-stellar-sdk v0.7.4-0.20261014050248-990270d32ebe h1:LeZAWquMEbuR/yF6n+JRD24zZyfm0apTFSwsHCcuJME=
github.com/stellar/go-stellar-sdk v0.7.4-0.20261014050248-990270d32ebe/go.mod h1:TE/oIrVngAujvcalNBggneqRLI3wSlUZb2pWYyqU3QA=
github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs=
github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80=
github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible h1:jMXXAcz6xTarGDQ4VtVbtERogcmDQw4RaE85Cr9CgoQ=
Expand Down
52 changes: 52 additions & 0 deletions internal/db2/history/ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"time"

"github.com/guregu/null"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/stellar/go-stellar-sdk/toid"
"github.com/stellar/go-stellar-sdk/xdr"
Expand Down Expand Up @@ -150,6 +152,56 @@ func TestInsertLedger(t *testing.T) {
tt.Assert.Equal(expectedLedger, ledgerFromDB)
}

// TestLedgerHeaderToMapMsCloseTime checks that a ledger whose StellarValue uses
// a CAP-0088 millisecond close-time arm is stored with the whole-second
// CloseTime as closed_at, and with the arm intact in ledger_header.
func TestLedgerHeaderToMapMsCloseTime(t *testing.T) {
const closeTimeMs = 1_760_000_000_789
signature := xdr.LedgerCloseValueSignature{
NodeId: xdr.NodeId(xdr.MustAddress("GAUJETIZVEP2NRYLUESJ3LS66NVCEGMON4UDCBCSBEVPIID773P2W6AY")),
Signature: xdr.Signature{1, 2, 3},
}
for _, ext := range []xdr.StellarValueExt{
{
V: xdr.StellarValueTypeStellarValueSignedMs,
SignedMsValue: &xdr.StellarValueSignedMsValue{
CloseTimeMs: closeTimeMs,
LcValueSignature: signature,
},
},
{
V: xdr.StellarValueTypeStellarValueEmptyTxSetMs,
ProposedMsValue: &xdr.StellarValueProposedMsValue{
CloseTimeMs: closeTimeMs,
TxSetHash: xdr.Hash{4},
PreviousLedgerHash: xdr.Hash{5},
PreviousLedgerVersion: 30,
LcValueSignature: signature,
},
},
} {
t.Run(ext.V.String(), func(t *testing.T) {
entry := xdr.LedgerHeaderHistoryEntry{
Header: xdr.LedgerHeader{
LedgerVersion: 30,
LedgerSeq: 100,
ScpValue: xdr.StellarValue{
CloseTime: closeTimeMs / 1000,
Ext: ext,
},
},
}
row, err := ledgerHeaderToMap(entry, 0, 0, 0, 0, 1)
require.NoError(t, err)
assert.Equal(t, time.Unix(closeTimeMs/1000, 0).UTC(), row["closed_at"])

var header xdr.LedgerHeader
require.NoError(t, xdr.SafeUnmarshalBase64(row["ledger_header"].(string), &header))
assert.Equal(t, ext, header.ScpValue.Ext)
})
}
}

func insertLedgerWithSequence(tt *test.T, q *Q, seq uint32) {
// generate random hashes to avoid insert clashes due to UNIQUE constraints
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
Expand Down
6 changes: 3 additions & 3 deletions internal/ingest/contractevents/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,8 @@ func parseSacEventFromTxMetaV4(event *xdr.ContractEvent, networkPassphrase strin
}

// parseSacEventMap parses the ScMap data format used in V4 SAC events.
// For SAC events, to_muxed_id represents the muxed account ID from
// MuxedAddressObject - which is always a uint64. ScvBytes and ScvString are NOT
// For SAC events, to_muxed_id represents the muxed account or muxed contract
// ID from MuxedAddressObject - which is always a uint64. ScvBytes and ScvString are NOT
// valid for SAC events (those are only used for classic transaction memo mappings
// per CAP-67, which are processed through a different code path).
func parseSacEventMap(mapData xdr.ScMap) (xdr.Int128Parts, xdr.Memo, error) {
Expand Down Expand Up @@ -346,7 +346,7 @@ func parseSacEventMap(mapData xdr.ScMap) (xdr.Int128Parts, xdr.Memo, error) {

case "to_muxed_id":
foundMuxedId = true
// SAC events only emit uint64 for to_muxed_id (muxed account ID).
// SAC events only emit uint64 for to_muxed_id (muxed account or contract ID).
// ScvBytes/ScvString are NOT valid here - those are only for classic
// transaction memo mappings which use a different code path.
switch entry.Val.Type {
Expand Down
19 changes: 19 additions & 0 deletions internal/ingest/contractevents/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,25 @@ func TestStellarAssetContractEventParsing(t *testing.T) {
}
}

func TestParseAddressMuxedContract(t *testing.T) {
address := xdr.ScAddress{
Type: xdr.ScAddressTypeScAddressTypeMuxedContract,
MuxedContract: &xdr.MuxedContract{
Id: 12345,
ContractId: xdr.ContractId(zeroContractHash),
},
}
parsed, err := parseAddress(xdr.ScVal{Type: xdr.ScValTypeScvAddress, Address: &address})
require.NoError(t, err)

muxed, err := strkey.DecodeMuxedContract(parsed)
require.NoError(t, err)
assert.Equal(t, uint64(12345), muxed.ID())
contractID, err := muxed.ContractID()
require.NoError(t, err)
assert.Equal(t, zeroContract, contractID)
}

// Test helper functions
func someLedgerTransaction(version int32) ingest.LedgerTransaction {
return ingest.LedgerTransaction{
Expand Down
Loading
Loading