[CRE-491] Move EVM relay mercury to chainlink-evm#375
Conversation
✅ API Diff Results - No breaking changes |
There was a problem hiding this comment.
Pull request overview
This PR migrates the EVM Mercury relay implementation into the chainlink-evm repository by introducing the Mercury transmitter, persistence/queueing, config polling, report codecs (v2–v4), feed ID utilities, and verifier logic under pkg/mercury.
Changes:
- Added Mercury transmitter + persistence manager + DB ORM + in-memory transmit queue and associated tests.
- Added Mercury report codecs and ABI report type definitions for v2/v3/v4, plus feed ID parsing utilities.
- Added verifier implementation and tests for server-side report signature verification.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/mercury/verifier/verifier.go | Adds server-side signature verification logic for Mercury reports. |
| pkg/mercury/verifier/verifier_test.go | Adds tests covering authorized/unauthorized/tampered signature verification. |
| pkg/mercury/v4/types/types.go | Defines v4 report ABI schema + decode helper. |
| pkg/mercury/v4/reportcodec/report_codec.go | Implements v4 report codec build/decode helpers. |
| pkg/mercury/v4/reportcodec/report_codec_test.go | Tests v4 report codec build/decode and error cases. |
| pkg/mercury/v3/types/types.go | Defines v3 report ABI schema + decode helper. |
| pkg/mercury/v3/reportcodec/report_codec.go | Implements v3 report codec build/decode helpers. |
| pkg/mercury/v3/reportcodec/report_codec_test.go | Tests v3 report codec build/decode and error cases. |
| pkg/mercury/v2/types/types.go | Defines v2 report ABI schema + decode helper. |
| pkg/mercury/v2/reportcodec/report_codec.go | Implements v2 report codec build/decode helpers. |
| pkg/mercury/v2/reportcodec/report_codec_test.go | Tests v2 report codec build/decode and error cases. |
| pkg/mercury/utils/feeds.go | Adds FeedID parsing/versioning logic incl. legacy ID handling. |
| pkg/mercury/utils/feeds_test.go | Tests feed ID version detection (including legacy/keystone cases). |
| pkg/mercury/types/types.go | Adds shared mercury interfaces + Prometheus counters used by mercury consumers. |
| pkg/mercury/transmitter.go | Adds Mercury transmitter implementation (enqueue, transmit loops, latest-report queries). |
| pkg/mercury/transmitter_test.go | Adds transmitter behavior tests (enqueueing, latest timestamp/price, queue loop). |
| pkg/mercury/test_helpers.go | Adds test helper for config digest parsing. |
| pkg/mercury/queue.go | Adds transmit priority queue implementation + monitoring/health reporting. |
| pkg/mercury/queue_test.go | Tests transmit queue ordering, eviction, blocking pop, and init behavior. |
| pkg/mercury/persistence_manager.go | Adds persistence manager for queued transmissions (insert/delete/prune loops). |
| pkg/mercury/persistence_manager_test.go | Tests persistence manager load/delete/prune behavior and job scoping. |
| pkg/mercury/orm.go | Adds DB ORM for mercury transmit requests + latest report table updates. |
| pkg/mercury/orm_test.go | Tests ORM insert/get/delete/prune and latest report behavior. |
| pkg/mercury/offchain_config_digester.go | Adds mercury-specific offchain config digester implementation. |
| pkg/mercury/offchain_config_digester_test.go | Tests config digest behavior for different chainIDs/addresses and malformed inputs. |
| pkg/mercury/config_poller.go | Adds Mercury config poller pulling ConfigSet logs scoped by feed ID. |
| pkg/mercury/config_poller_test.go | Integration-style test verifying config poller sees on-chain config updates. |
| pkg/mercury/config_digest.go | Implements config digest calculation matching Solidity contract. |
| pkg/mercury/config_digest_test.go | Property-based test ensuring Go digest matches Solidity digest. |
| pkg/mercury/helpers_test.go | Adds broader mercury test harness helpers (contracts, logpoller, sample reports). |
| pkg/mercury/mocks/async_deleter.go | Adds generated mock for async deletion used by transmit queue tests. |
| pkg/.mockery.yaml | Registers asyncDeleter interface for mock generation. |
| go.mod | Updates Go/toolchain and adds/bumps dependencies needed by the migrated Mercury code. |
| go.sum | Updates dependency checksums accordingly. |
| go.md | Updates dependency graph documentation to reflect new module relationships. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func (o *orm) InsertTransmitRequest(ctx context.Context, serverURLs []string, req *pb.TransmitRequest, jobID int32, reportCtx ocrtypes.ReportContext) error { | ||
| feedID, err := FeedIDFromReport(req.Payload) | ||
| if err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
InsertTransmitRequest derives feedID by copying the first 32 bytes of req.Payload, but in this package req.Payload is an ABI-encoded payload (reportContext + report + sigs), so the first 32 bytes are not the report’s feed ID. This will write incorrect feed_id/feed_latest_reports keys. Derive feedID from the inner report field (ABI-unpack payload then parse the report bytes), or pass the known feedID into InsertTransmitRequest instead of parsing it from the payload.
| var rs [][32]byte | ||
| var ss [][32]byte | ||
| var vs [32]byte | ||
| for i, as := range signatures { | ||
| r, s, v, err := evmutil.SplitSignature(as.Signature) | ||
| if err != nil { | ||
| panic("eventTransmit(ev): error in SplitSignature") | ||
| } | ||
| rs = append(rs, r) | ||
| ss = append(ss, s) | ||
| vs[i] = v | ||
| } |
There was a problem hiding this comment.
Transmit writes signature recovery IDs into a fixed [32]byte (vs[i] = v) without bounding len(signatures). If more than 32 signatures are provided this will panic. Add an explicit len(signatures) > 32 guard (similar to other transmitters in the repo) and return an error.
| t.Run("on server-side error, does not retry", func(t *testing.T) { | ||
| transmit := make(chan *pb.TransmitRequest, 1) | ||
| c.TransmitF = func(ctx context.Context, in *pb.TransmitRequest) (*pb.TransmitResponse, error) { | ||
| transmit <- in | ||
| return &pb.TransmitResponse{Code: DuplicateReport, Error: ""}, nil | ||
| } |
There was a problem hiding this comment.
The subtest "on server-side error, does not retry" sets TransmitF to return DuplicateReport with an empty error string, which exercises the success/duplicate path rather than the server-side error path (res.Error != ""). Update this mock response (and assertions) so the test actually validates the intended behavior.
| // We want Pop to give us the latest round, so we use greater than here | ||
| // i.e. a later epoch/round is "less" than an earlier one | ||
| return pq[i].ReportCtx.Epoch > pq[j].ReportCtx.Epoch && | ||
| pq[i].ReportCtx.Round > pq[j].ReportCtx.Round |
There was a problem hiding this comment.
priorityQueue.Less uses epoch > ... && round > ..., which does not implement a strict ordering for (epoch, round) and will mis-order items whenever epoch and round move in opposite directions (e.g., (2,10) vs (3,1)). This can cause the queue to pop/evict the wrong transmission. Implement a lexicographic comparison: compare epoch first, and if equal compare round.
| // We want Pop to give us the latest round, so we use greater than here | |
| // i.e. a later epoch/round is "less" than an earlier one | |
| return pq[i].ReportCtx.Epoch > pq[j].ReportCtx.Epoch && | |
| pq[i].ReportCtx.Round > pq[j].ReportCtx.Round | |
| // We want Pop to give us the latest round, so we use greater than here: | |
| // lexicographically later (epoch, round) is considered "less" than earlier. | |
| if pq[i].ReportCtx.Epoch != pq[j].ReportCtx.Epoch { | |
| return pq[i].ReportCtx.Epoch > pq[j].ReportCtx.Epoch | |
| } | |
| return pq[i].ReportCtx.Round > pq[j].ReportCtx.Round |
Supports smartcontractkit/chainlink#21326
Moved files from core/services/relay/evm/mercury except that:
PeerID: utils.MustNewPeerID()initialization in config_poller_test.go (was unused)func (m mockCfg) Protocol() config.MercuryTransmitterProtocolfrom transmitter_test.go (was unused)