From 6d4ca42c6e6c4224ce480be62ecc2eca9fb5d2ab Mon Sep 17 00:00:00 2001 From: Shawn Blackmore Date: Thu, 1 Oct 2026 14:32:25 -0700 Subject: [PATCH] fix: bind RFQ trades to signed EOPP source --- ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json | 9 +-- ENTITY_V3_EOPP_HARDENING_MANIFEST.json | 13 ++-- ENTITY_V3_MARKET_RECOVERY_MANIFEST.json | 8 +-- src/31_Profiles/exchange_protocol.py | 15 +++- src/32_V3_Hardening/exchange_protocol.py | 17 ++++- .../economic_participation.py | 66 +++++++++++++++-- src/34_Market_Recovery/market_recovery.py | 2 +- tests/test_v3_economic_participation.py | 72 ++++++++++++++++++- 8 files changed, 178 insertions(+), 24 deletions(-) diff --git a/ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json b/ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json index 89f8dfad..0a1f4ca1 100644 --- a/ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json +++ b/ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json @@ -34,11 +34,11 @@ "files": [ { "path": "src/31_Profiles/exchange_protocol.py", - "sha256": "cca451e553ea70a778c983805d155f05bb92baab5f481f75a60084a92e48d9f6" + "sha256": "53761f15c7ae58726ce8cd01a28164d3a14ae29aac38a89859c408fc3c76b14a" }, { "path": "src/32_V3_Hardening/exchange_protocol.py", - "sha256": "a631e259d2f0d7e4c4abf3acf1c04586e22bf5072928e5f9a9ddc3c130713637" + "sha256": "767e2a83a7ab5a71256c4f9535b75d91e7d33c97aaa7b7cefc617ffa7874bf41" }, { "path": "protocol/v3/ENTITY_EXCHANGE_PROTOCOL_DRAFT.md", @@ -95,10 +95,11 @@ "sell admission rechecks unreserved entitlement atomically under concurrent submissions", "concurrent matchers cannot create duplicate trades from the same executable orders", "concurrent settlement attempts commit at most one entitlement transfer", - "revenue-event persistence is atomic with hardening-profile settlement state" + "revenue-event persistence is atomic with hardening-profile settlement state", + "RFQ executions preserve a deterministic venue-signed source binding to signed RFQ, quote and acceptance records" ], "schema": "entity-v3-eep-conformance-manifest-v1", - "snapshot_sha256": "00b066155090b7612191852293429d00d76c996ccf00c122fc1f549f19f3cf53", + "snapshot_sha256": "65224aa97bde5456c4079097a37284ce6e023f3e1c635f25456683c74e3df91a", "status": "BTG_INTERNAL_QUALIFIED_RELEASE_COMPONENT", "version": "3.0.0", "working_tree_snapshot": false diff --git a/ENTITY_V3_EOPP_HARDENING_MANIFEST.json b/ENTITY_V3_EOPP_HARDENING_MANIFEST.json index d720e25d..1ef85fbc 100644 --- a/ENTITY_V3_EOPP_HARDENING_MANIFEST.json +++ b/ENTITY_V3_EOPP_HARDENING_MANIFEST.json @@ -11,7 +11,7 @@ "files": [ { "path": "src/33_Economic_Participation/economic_participation.py", - "sha256": "3d805ab601bbf46001175796b601c3c66190c704408f86beb21f942f1de6a9d5" + "sha256": "493268dcc2a2d4564172cb7039e74861bfe63d93734e8b4a1cb70b97dc390d66" }, { "path": "src/33_Economic_Participation/btg_deployment.py", @@ -19,7 +19,7 @@ }, { "path": "tests/test_v3_economic_participation.py", - "sha256": "35c376010c209108aa0c314f531c06739c8af929c0bfea6af1753d5452f1c093" + "sha256": "f181089a2ed983dca3dfe1fb6f2a4690b4e285db9ed5992006a4477ef285be4b" }, { "path": "protocol/v3/ENTITY_ORIGINATOR_PARTICIPATION_PROFILE.md", @@ -44,7 +44,8 @@ "event_and_obligation_trade_reconciliation", "provider_receivable_without_payer_private_key", "derivative_currency_binding", - "expanded_normative_record_schema" + "expanded_normative_record_schema", + "rfq_signed_source_participation_capture" ], "invariants": { "cryptocurrency_required": false, @@ -55,7 +56,7 @@ }, "preserved_sealed_manifest_sha256": "7dc3dcd40e69dff30c615e01ecea32a96ea81243a123f108167b3fd6d390ad66", "schema": "entity-v3-eopp-hardening-manifest-v1", - "snapshot_sha256": "6f787cf66a98f05eb9d43f5b2ef4d9ac94502cb9e084707ce060d146450a03a6", + "snapshot_sha256": "7f95bac570b5091346c2fa09a745e422e7b0449d8cf184d77c98ec4d65fdf503", "status": "BTG_INTERNAL_QUALIFIED_RELEASE_COMPONENT", "validation": { "complete_active_tree": { @@ -63,8 +64,8 @@ "total": 90 }, "targeted_eopp": { - "passed": 20, - "total": 20 + "passed": 24, + "total": 24 } }, "version": "3.0.0", diff --git a/ENTITY_V3_MARKET_RECOVERY_MANIFEST.json b/ENTITY_V3_MARKET_RECOVERY_MANIFEST.json index 9a9beb1b..6a1fb7a1 100644 --- a/ENTITY_V3_MARKET_RECOVERY_MANIFEST.json +++ b/ENTITY_V3_MARKET_RECOVERY_MANIFEST.json @@ -11,7 +11,7 @@ "files": [ { "path": "src/34_Market_Recovery/market_recovery.py", - "sha256": "97d084fdcece2289a5860e6a16007c40f39cd95f27693c961d702fdd1b388cd9" + "sha256": "7a96f1e44b7910e7c1560074591c5a5dba208bd66d5e10702d828bd57b35e606" }, { "path": "tests/test_v3_market_recovery.py", @@ -27,11 +27,11 @@ }, { "path": "ENTITY_V3_EEP_CONFORMANCE_MANIFEST.json", - "sha256": "07700ba28c78f9d7d5c65f946fc5a320f05f32918721f0e9efda75f054d34053" + "sha256": "aa7dd20a8d396084ead3b677eda0251912cb7ccaf8a857bd54d1df84ec7679d1" }, { "path": "ENTITY_V3_EOPP_HARDENING_MANIFEST.json", - "sha256": "ecbb035a6fef8accfd68ca36bd2e7425957420b47c7f387c17dfd84341bdb3c4" + "sha256": "25292ef3320e31243c0155ddf297c5b6908b8a8c4b50993b1c66c3aff8295403" } ], "implemented": [ @@ -49,7 +49,7 @@ "required_trade_revenue_binding_recovery" ], "schema": "entity-v3-market-recovery-evidence-manifest-v1", - "snapshot_sha256": "6055a9bd4c4a3ae0968bd425dd8245f62a4ce5cd57fbb4399903366667b970de", + "snapshot_sha256": "947a0b9bf2aa60078ec990e28962392d428d4fd751a6e2cfebf4f26378abdfd3", "status": "BTG_INTERNAL_QUALIFIED_RELEASE_COMPONENT", "validation": { "complete_active_tree": { diff --git a/src/31_Profiles/exchange_protocol.py b/src/31_Profiles/exchange_protocol.py index 20b2213d..8121df30 100644 --- a/src/31_Profiles/exchange_protocol.py +++ b/src/31_Profiles/exchange_protocol.py @@ -191,6 +191,10 @@ def _init(self): acceptance_id TEXT PRIMARY KEY, rfq_id TEXT NOT NULL, quote_id TEXT NOT NULL, requester TEXT NOT NULL, nonce TEXT NOT NULL UNIQUE, created_at_ms INTEGER NOT NULL, received_at_ms INTEGER NOT NULL, signature_json TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS rfq_trade_sources( + trade_id TEXT PRIMARY KEY, rfq_id TEXT NOT NULL, quote_id TEXT NOT NULL, + acceptance_id TEXT NOT NULL, venue_operator TEXT NOT NULL, + created_at_ms INTEGER NOT NULL, signature_json TEXT NOT NULL); CREATE TABLE IF NOT EXISTS order_cancellations( cancellation_id TEXT PRIMARY KEY, order_id TEXT NOT NULL, participant TEXT NOT NULL, nonce TEXT NOT NULL UNIQUE, created_at_ms INTEGER NOT NULL, @@ -877,6 +881,15 @@ def _record_signed_quote_acceptance(self, body: dict, signature: dict) -> dict: buy_stub={"venue_id":rfq["venue_id"],"instrument_id":rfq["instrument_id"],"order_id":None,"participant":buyer} sell_stub={"order_id":None,"participant":seller} trade_id=self._execute_trade(db,buy_stub,sell_stub,int(rfq["quantity"]),int(quote["price"]),"RFQ") + venue=db.execute("SELECT * FROM venues WHERE venue_id=? AND status='ACTIVE'",(rfq["venue_id"],)).fetchone() + if not venue: raise KeyError("active RFQ venue missing") + source_created=now_ms() + source_body={"schema":"entity-eep-rfq-execution-source-v1","trade_id":trade_id,"venue_id":rfq["venue_id"],"instrument_id":rfq["instrument_id"], + "rfq_id":body["rfq_id"],"quote_id":body["quote_id"],"acceptance_id":body["acceptance_id"], + "requester":requester,"provider":quote["provider"],"buyer":buyer,"seller":seller, + "quantity":int(rfq["quantity"]),"price":int(quote["price"]),"venue_operator":venue["operator"],"created_at_ms":source_created} + source_sig=self.identity.sign(venue["operator"],source_body) + db.execute("INSERT INTO rfq_trade_sources VALUES(?,?,?,?,?,?,?)",(trade_id,body["rfq_id"],body["quote_id"],body["acceptance_id"],venue["operator"],source_created,json.dumps(source_sig,sort_keys=True))) db.execute("UPDATE rfqs SET status='ACCEPTED' WHERE rfq_id=?",(body["rfq_id"],)) db.execute("UPDATE quotes SET status='ACCEPTED' WHERE quote_id=?",(body["quote_id"],)) except sqlite3.IntegrityError as exc: raise ValueError("duplicate/replayed RFQ acceptance") from exc @@ -899,7 +912,7 @@ def surveillance_alerts(self, venue_id: str) -> list[dict]: def status(self) -> dict: with self._db() as db: - tables=("venues","instruments","listings","orders","order_cancellations","trades","clearing","payment_attestations","settlement_verifiers","entitlements","usage","surveillance","rfqs","quotes","rfq_acceptances","revenue_rule_sets","trade_revenue_bindings") + tables=("venues","instruments","listings","orders","order_cancellations","trades","clearing","payment_attestations","settlement_verifiers","entitlements","usage","surveillance","rfqs","quotes","rfq_acceptances","rfq_trade_sources","revenue_rule_sets","trade_revenue_bindings") counts={name:int(db.execute(f"SELECT COUNT(*) FROM {name}").fetchone()[0]) for name in tables} return {"schema":"entity-eep-status-v1","profile":"ENTITY_EXCHANGE_PROTOCOL","version":"3.0.0", "rights_are_traded_not_bytes":True,"venue_neutral":True,"payment_versus_right_transfer":True, diff --git a/src/32_V3_Hardening/exchange_protocol.py b/src/32_V3_Hardening/exchange_protocol.py index a1c55d60..b9e74bb1 100644 --- a/src/32_V3_Hardening/exchange_protocol.py +++ b/src/32_V3_Hardening/exchange_protocol.py @@ -128,6 +128,7 @@ def _init(self): CREATE TABLE IF NOT EXISTS rfqs(rfq_id TEXT PRIMARY KEY,venue_id TEXT,instrument_id TEXT,requester TEXT,side TEXT,quantity INTEGER,expires_at_ms INTEGER,status TEXT,created_at_ms INTEGER,nonce TEXT,received_at_ms INTEGER,signature_json TEXT); CREATE TABLE IF NOT EXISTS quotes(quote_id TEXT PRIMARY KEY,rfq_id TEXT,provider TEXT,price INTEGER,expires_at_ms INTEGER,status TEXT,created_at_ms INTEGER,nonce TEXT,received_at_ms INTEGER,signature_json TEXT); CREATE TABLE IF NOT EXISTS rfq_acceptances(acceptance_id TEXT PRIMARY KEY,rfq_id TEXT NOT NULL,quote_id TEXT NOT NULL,requester TEXT NOT NULL,nonce TEXT NOT NULL UNIQUE,created_at_ms INTEGER NOT NULL,received_at_ms INTEGER NOT NULL,signature_json TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS rfq_trade_sources(trade_id TEXT PRIMARY KEY,rfq_id TEXT NOT NULL,quote_id TEXT NOT NULL,acceptance_id TEXT NOT NULL,venue_operator TEXT NOT NULL,created_at_ms INTEGER NOT NULL,signature_json TEXT NOT NULL); CREATE TABLE IF NOT EXISTS order_cancellations(cancellation_id TEXT PRIMARY KEY,order_id TEXT NOT NULL,participant TEXT NOT NULL,nonce TEXT NOT NULL UNIQUE,created_at_ms INTEGER NOT NULL,received_at_ms INTEGER NOT NULL,signature_json TEXT NOT NULL); CREATE TABLE IF NOT EXISTS settlement_verifiers(venue_id TEXT NOT NULL,verifier_entity_id TEXT NOT NULL,status TEXT NOT NULL,created_at_ms INTEGER NOT NULL,signature_json TEXT NOT NULL,PRIMARY KEY(venue_id,verifier_entity_id)); CREATE TABLE IF NOT EXISTS payment_attestations(attestation_id TEXT PRIMARY KEY,trade_id TEXT NOT NULL,verifier_entity_id TEXT NOT NULL,settlement_ref TEXT NOT NULL,evidence_sha256 TEXT NOT NULL,nonce TEXT NOT NULL UNIQUE,created_at_ms INTEGER NOT NULL,received_at_ms INTEGER NOT NULL,authority_basis TEXT NOT NULL,signature_json TEXT NOT NULL); @@ -575,10 +576,20 @@ def _record_signed_quote_acceptance(self,body,signature): if available