Skip to content

fix: stop decoding UMP signals as XCM - #420

Merged
TarikGul merged 2 commits into
mainfrom
tg-ump-signals
Sep 30, 2026
Merged

TarikGul merged 2 commits into
mainfrom
tg-ump-signals

Conversation

@TarikGul

Copy link
Copy Markdown
Member

Closes: #298

Description

Since elastic scaling, a candidate's upward_messages is the XCM it sent, then UMP_SEPARATOR (an empty message), then UMP signals:

[ ..XCM.., [], UMPSignal::SelectCore(..), UMPSignal::<..>(..) ]

We decoded every entry as VersionedXcm, so the signals came back as raw hex with no way to tell them from an XCM message that genuinely failed to decode, which is what #298 asks for.

They are not corrupt XCM. They are well formed SCALE that simply is not XCM:

0x00 <u8> <u8>          3 bytes   SelectCore(CoreSelector, ClaimQueueOffset)
0x01 0x98 <38 bytes>    40 bytes  variant 1, compact length 38, carrying
                                  0x0024 0801 1220 <32 bytes>
                                  identity multihash plus protobuf Ed25519 key, a libp2p PeerId

Every parachain emits them, not just SORA as the issue supposed. On relay block 33216511 the senders were 1000, 1001, 1002, 1004, 1005, 1010, 2006, 2030, 3367, 3377, 3388, 3417 and 3428.

Changes

upward_xcm_messages reads the list only up to the separator. Everything from the separator onwards is dropped, so a raw hex data in the response now means exactly one thing: XCM we could not decode. A list with no separator is treated as all XCM, which is what pre signal runtimes emit.

The separator was already invisible to us. It arrives as an empty JSON array and the old msg.as_str() check skipped it, which is why the signals looked like part of the same list.

Verified

Against live Polkadot relay, this branch versus the deployed build, counting messages in decodedXcmMsgs:

block deployed this branch
33216511 46 0 all signals
30219740 18 0 all signals, the block from #272
28490504 1 1 real XCM, kept

That last one is the control. It is a genuine V5 message from para 1000 that still decodes to unpaidExecution, transact and setTopic, so XCM ahead of the separator is untouched.

Four unit tests cover the shape seen on chain, XCM before the separator, a list with no separator, and the separator arriving as an empty hex string rather than an empty array.

Note

This drops the signals rather than surfacing them. They are not XCM and the field is decodedXcmMsgs, so that seemed right, but returning them under their own key would be an easy follow up if anyone wants them.

Since elastic scaling, a candidate's upward message list is the XCM it
sent, then UMP_SEPARATOR (an empty message), then UMP signals. We decoded
every entry as XCM, so the signals came back as raw hex, indistinguishable
from an XCM message that genuinely failed to decode.

They are not corrupt XCM, they are well formed SCALE that is not XCM:
a 3 byte SelectCore(CoreSelector, ClaimQueueOffset), and a 40 byte variant
carrying a libp2p PeerId. Every parachain emits them, Asset Hub included,
so on a recent relay block 46 of 46 upward messages were signals and none
were XCM.

Reads only up to the separator now. A raw hex `data` in the response
therefore means one thing: XCM we could not decode.

The separator was already invisible: it arrives as an empty JSON array and
the old `msg.as_str()` check skipped it, which is why the signals looked
like part of the list.
bee344
bee344 previously approved these changes Sep 30, 2026

@bee344 bee344 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just two nits

Comment thread crates/server/src/handlers/blocks/decode/xcm.rs Outdated
Comment thread crates/server/src/handlers/blocks/decode/xcm.rs
Review nits on #420.

The doc comment for decode_xcm_message ended up above upward_xcm_messages
when that helper was inserted, so it read as part of the wrong function
and decode_xcm_message had none. Moved back.

is_ump_separator now says why Value::Null is not a separator, with a test.
Null means we failed to read that entry, not that the runtime marked a
boundary, so treating it as one would truncate the list and drop the XCM
after it. Anything that is not a string is skipped by the caller instead.
@TarikGul
TarikGul merged commit 4839b36 into main Sep 30, 2026
13 checks passed
@TarikGul
TarikGul deleted the tg-ump-signals branch September 30, 2026 14:32
@TarikGul TarikGul mentioned this pull request Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Non-XCM Bridge Messages in UMP: Decoding Gap

2 participants