diff --git a/.github/workflows/horizon.yml b/.github/workflows/horizon.yml index 5bb9c443..957bb34c 100644 --- a/.github/workflows/horizon.yml +++ b/.github/workflows/horizon.yml @@ -35,13 +35,13 @@ jobs: PROTOCOL_27_CORE_DOCKER_IMG: stellar/stellar-core:27.0.0-3288.7696c069d.jammy PROTOCOL_27_CORE_DEBIAN_PKG_VERSION: 27.0.0-3288.7696c069d.jammy~buildtests PROTOCOL_27_STELLAR_RPC_DOCKER_IMG: stellar/stellar-rpc:27.0.0-preview-190 - # Protocol 28 (CAP-0083) runs against the vnext (next-protocol-enabled) core build. - # Pinned to 3367 (3589a696b), which includes stellar-core#5329 — the fix - # for the QueryServer.cpp:124 contiguous-snapshot assertion that aborted - # captive-core catchup on builds 3313..3366. - PROTOCOL_28_CORE_DOCKER_IMG: stellar/unsafe-stellar-core:27.0.1-3367.3589a696b.jammy-vnext - PROTOCOL_28_CORE_DEBIAN_PKG_VERSION: 27.0.1-3367.3589a696b.jammy~vnext~buildtests - # No Protocol 28 stellar-rpc image is published yet (tracked: stellar-rpc#789). + # Protocol 28 (CAP-0084) runs against the vnext (next-protocol-enabled) core build. + # Pinned to build 3395 (4cdd2fc9e) — the -vnext image/deb matching CAP-0084 + # core (stellar-core#5337, HEAD 4cdd2fc9e). Variant preserved from the p27 leg: + # plain -vnext docker + ~vnext~buildtests deb. + PROTOCOL_28_CORE_DOCKER_IMG: stellar/unsafe-stellar-core:27.1.1-3395.4cdd2fc9e.jammy-vnext + PROTOCOL_28_CORE_DEBIAN_PKG_VERSION: 27.1.1-3395.4cdd2fc9e.jammy~vnext~buildtests + # No Protocol 28 stellar-rpc image is published yet (tracked: stellar-rpc#837). # Leave EMPTY on purpose: the P27 RPC image bundles a P27 captive-core that # cannot ingest a protocol-28 network, so bringing it up makes every # EnableStellarRPC test hang in waitForStellarRPC() ("DB is empty") until the diff --git a/CHANGELOG.md b/CHANGELOG.md index 903e4e88..049aa0a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ file. This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added +- Added Protocol 28 ingestion support for CAP-0084 (Muxed Contract Addresses): + SAC transfer/mint events to a muxed-contract destination surface the base + contract in the `to` topic and the mux id via `to_muxed_id`, and bumped + `MaxSupportedProtocolVersion` to 28 ([#203](https://github.com/stellar/stellar-horizon/pull/203)). + ## 27.0.0 **This release adds support for Protocol 27** diff --git a/go.mod b/go.mod index 152ffc76..07222c46 100644 --- a/go.mod +++ b/go.mod @@ -170,3 +170,5 @@ require ( google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/stellar/go-stellar-sdk => github.com/sisuresh/go v0.0.0-20260701230520-eeca7aa4a3bc diff --git a/go.sum b/go.sum index 75a8c147..a28227b7 100644 --- a/go.sum +++ b/go.sum @@ -413,6 +413,8 @@ github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+Yg github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sisuresh/go v0.0.0-20260701230520-eeca7aa4a3bc h1:WVa7+1/bWNhqwKTBIZ+yHdANMwmXDb4VOym4bn2YpME= +github.com/sisuresh/go v0.0.0-20260701230520-eeca7aa4a3bc/go.mod h1:IkcqcrE9UQi7n/1y+MxKB+7qzdjG1T2kGOD7Ss8dqjw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -429,8 +431,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/stellar/go-stellar-sdk v0.6.1-0.20260616165505-26fec7c0e891 h1:Y/PtLm4L4ZHLDI1dXIflRjI5pY+m+KD7FUVFM07tV+E= -github.com/stellar/go-stellar-sdk v0.6.1-0.20260616165505-26fec7c0e891/go.mod h1:IkcqcrE9UQi7n/1y+MxKB+7qzdjG1T2kGOD7Ss8dqjw= github.com/stellar/go-xdr v0.0.0-20260529210834-0bf8f4956364 h1:gOKrfuWdZ92LFlv0TAwgZ7OsWKeBsOMDlGLyFgduI1w= github.com/stellar/go-xdr v0.0.0-20260529210834-0bf8f4956364/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible h1:jMXXAcz6xTarGDQ4VtVbtERogcmDQw4RaE85Cr9CgoQ= diff --git a/internal/ingest/contractevents/events.go b/internal/ingest/contractevents/events.go index a1b31940..15e6a1e2 100644 --- a/internal/ingest/contractevents/events.go +++ b/internal/ingest/contractevents/events.go @@ -317,10 +317,15 @@ 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 -// valid for SAC events (those are only used for classic transaction memo mappings -// per CAP-67, which are processed through a different code path). +// For SAC events, to_muxed_id is always a uint64. It carries either the muxed +// account ID from a MuxedAddressObject (CAP-67) or, under CAP-0084, the muxed +// contract ID from a MUXED_CONTRACT ScAddress. In both cases the SAC host +// de-muxes the destination before emitting the event: the `to` topic holds the +// base (account or contract) address and the mux id is surfaced separately as a +// uint64, so the two are byte-identical in shape and indistinguishable from the +// event alone. This holds for both transfer and mint. 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) { var foundAmount, foundMuxedId bool var amount xdr.Int128Parts @@ -346,9 +351,11 @@ 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). - // ScvBytes/ScvString are NOT valid here - those are only for classic - // transaction memo mappings which use a different code path. + // SAC events only emit uint64 for to_muxed_id (muxed account ID under + // CAP-67, or muxed contract ID under CAP-0084 - both de-muxed by the + // host to a uint64). ScvBytes/ScvString are NOT valid here - those are + // only for classic transaction memo mappings which use a different + // code path. switch entry.Val.Type { case xdr.ScValTypeScvU64: if val, ok := entry.Val.GetU64(); ok { diff --git a/internal/ingest/contractevents/events_test.go b/internal/ingest/contractevents/events_test.go index e5118b98..fd6b44da 100644 --- a/internal/ingest/contractevents/events_test.go +++ b/internal/ingest/contractevents/events_test.go @@ -21,6 +21,11 @@ var ( randomAccount = keypair.MustRandom().Address() zeroContractHash = xdr.Hash([32]byte{}) zeroContract = strkey.MustEncode(strkey.VersionByteContract, zeroContractHash[:]) + // muxedContractHash stands in for the base contract a CAP-0084 + // MUXED_CONTRACT address de-muxes to. The host emits this base contract in + // the `to` topic and surfaces the mux id separately as a uint64. + muxedContractHash = xdr.Hash([32]byte{0xCA, 0xFE, 0xBA, 0xBE}) + muxedContract = strkey.MustEncode(strkey.VersionByteContract, muxedContractHash[:]) ) // Test fixture structure @@ -242,6 +247,55 @@ func TestStellarAssetContractEventParsing(t *testing.T) { DestinationMemo: xdr.MemoID(12345), }, }, + { + // CAP-0084: a transfer to a muxed contract destination. The host + // de-muxes the MUXED_CONTRACT address, emitting the base contract in + // the `to` topic and the mux id as a uint64 to_muxed_id - byte-identical + // in shape to the CAP-67 muxed-account case above, by design. + name: "V4 transfer to muxed contract (CAP-0084)", + txMetaVersion: 4, + eventType: EventTypeTransfer, + topics: []xdr.ScVal{ + makeSymbol("transfer"), + makeAddress(randomAccount), + makeAddress(muxedContract), + makeAsset(randomAsset), + }, + data: makeV4MapData(big.NewInt(1000), xdr.MemoID(67890)), + asset: randomAsset, + contractID: mustGetContractID(randomAsset), + expectedResult: &StellarAssetContractEvent{ + Type: EventTypeTransfer, + Asset: randomAsset, + From: randomAccount, + To: muxedContract, + Amount: xdr.Int128Parts{Lo: 1000, Hi: 0}, + DestinationMemo: xdr.MemoID(67890), + }, + }, + { + // CAP-0084: a mint to a muxed contract destination. The mint path + // de-muxes the destination identically to transfer, so the muxed + // contract id surfaces as a uint64 to_muxed_id as well. + name: "V4 mint to muxed contract (CAP-0084)", + txMetaVersion: 4, + eventType: EventTypeMint, + topics: []xdr.ScVal{ + makeSymbol("mint"), + makeAddress(muxedContract), // to (no admin in V4) + makeAsset(randomAsset), + }, + data: makeV4MapData(big.NewInt(2000), xdr.MemoID(67890)), + asset: randomAsset, + contractID: mustGetContractID(randomAsset), + expectedResult: &StellarAssetContractEvent{ + Type: EventTypeMint, + Asset: randomAsset, + To: muxedContract, + Amount: xdr.Int128Parts{Lo: 2000, Hi: 0}, + DestinationMemo: xdr.MemoID(67890), + }, + }, { name: "V4 SAC event rejects ScvString for to_muxed_id", txMetaVersion: 4, diff --git a/internal/ingest/testdata/test-lcms/InvokeHostFunctionTests/221f1cad327fb891.xdr b/internal/ingest/testdata/test-lcms/InvokeHostFunctionTests/221f1cad327fb891.xdr index fffe8d03..0680d2ec 100644 Binary files a/internal/ingest/testdata/test-lcms/InvokeHostFunctionTests/221f1cad327fb891.xdr and b/internal/ingest/testdata/test-lcms/InvokeHostFunctionTests/221f1cad327fb891.xdr differ diff --git a/internal/ingest/testdata/test-lcms/InvokeHostFunctionTests/efd47464023e9e55.xdr b/internal/ingest/testdata/test-lcms/InvokeHostFunctionTests/efd47464023e9e55.xdr index 961bd702..047bf34f 100644 Binary files a/internal/ingest/testdata/test-lcms/InvokeHostFunctionTests/efd47464023e9e55.xdr and b/internal/ingest/testdata/test-lcms/InvokeHostFunctionTests/efd47464023e9e55.xdr differ