Skip to content

Commit 19b4f3e

Browse files
authored
fix: skip scraper lookup for txHash (#8517)
1 parent f2813ec commit 19b4f3e

9 files changed

Lines changed: 171 additions & 19 deletions

File tree

.changeset/odd-turtles-rhyme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperlane-xyz/ccip-server": patch
3+
---
4+
5+
Added origin tx hash to skip scraper lookup for tx hash

rust/main/agents/relayer/src/msg/metadata/base_builder.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use hyperlane_base::{
1919
use hyperlane_core::{
2020
accumulator::merkle::Proof, AggregationIsm, CcipReadIsm, Checkpoint, HyperlaneDomain,
2121
HyperlaneMessage, InterchainSecurityModule, ModuleType, MultisigIsm, RoutingIsm,
22-
ValidatorAnnounce, H160, H256,
22+
ValidatorAnnounce, H160, H256, H512,
2323
};
2424

2525
use crate::msg::metadata::base_builder::validator_announced_storages::fetch_storage_locations_helper;
@@ -85,6 +85,10 @@ pub trait BuildsBaseMetadata: Send + Sync + Debug {
8585
async fn highest_known_leaf_index(&self) -> Option<u32>;
8686
async fn get_merkle_leaf_id_by_message_id(&self, message_id: H256)
8787
-> eyre::Result<Option<u32>>;
88+
async fn retrieve_origin_tx_hash_by_message_id(
89+
&self,
90+
message_id: H256,
91+
) -> eyre::Result<Option<H512>>;
8892
async fn build_ism(&self, address: H256) -> eyre::Result<Box<dyn InterchainSecurityModule>>;
8993
async fn build_routing_ism(&self, address: H256) -> eyre::Result<Box<dyn RoutingIsm>>;
9094
async fn build_multisig_ism(&self, address: H256) -> eyre::Result<Box<dyn MultisigIsm>>;
@@ -172,6 +176,15 @@ impl BuildsBaseMetadata for BaseMetadataBuilder {
172176
Ok(merkle_leaf)
173177
}
174178

179+
async fn retrieve_origin_tx_hash_by_message_id(
180+
&self,
181+
message_id: H256,
182+
) -> eyre::Result<Option<H512>> {
183+
Ok(self
184+
.db
185+
.retrieve_dispatched_tx_hash_by_message_id(&message_id)?)
186+
}
187+
175188
async fn build_ism(&self, address: H256) -> eyre::Result<Box<dyn InterchainSecurityModule>> {
176189
self.destination_chain_setup
177190
.build_ism(address, &self.metrics)

rust/main/agents/relayer/src/msg/metadata/ccip_read/mod.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use sha3::{digest::Update, Digest, Keccak256};
1515
use tracing::{info, instrument, warn};
1616

1717
use hyperlane_core::{
18-
utils::bytes_to_hex, CcipReadIsm, HyperlaneMessage, HyperlaneSignerExt, Metadata, ModuleType,
19-
RawHyperlaneMessage, Signable, H160, H256,
18+
h512_to_bytes, utils::bytes_to_hex, CcipReadIsm, HyperlaneMessage, HyperlaneSignerExt,
19+
Metadata, ModuleType, RawHyperlaneMessage, Signable, H160, H256,
2020
};
2121
use hyperlane_ethereum::{OffchainLookup, Signers};
2222

@@ -37,6 +37,7 @@ struct OffchainLookupRequestBody {
3737
pub data: String,
3838
pub sender: String,
3939
pub signature: Option<String>,
40+
pub origin_tx_hash: Option<String>,
4041
}
4142

4243
#[derive(Serialize, Deserialize)]
@@ -223,6 +224,24 @@ async fn metadata_build(
223224
.call_get_offchain_verify_info(ism, message)
224225
.await?;
225226

227+
let origin_tx_hash = ism_builder
228+
.base
229+
.base_builder()
230+
.retrieve_origin_tx_hash_by_message_id(message.id())
231+
.await
232+
.map_err(|err| {
233+
warn!(error = %err, "Error retrieving origin tx hash for message {:?}", message.id());
234+
})
235+
.ok()
236+
.flatten()
237+
.map(|h| bytes_to_hex(&h512_to_bytes(&h)));
238+
tracing::debug!(
239+
message_id = ?message.id(),
240+
origin_tx_hash = ?origin_tx_hash,
241+
found_in_db = origin_tx_hash.is_some(),
242+
"Origin tx hash lookup result",
243+
);
244+
226245
let ccip_url_regex = create_ccip_url_regex();
227246

228247
for url in info.urls.iter() {
@@ -232,7 +251,7 @@ async fn metadata_build(
232251
}
233252

234253
// if we fail, we want to try the other urls
235-
match fetch_offchain_data(ism_builder, &info, url).await {
254+
match fetch_offchain_data(ism_builder, &info, url, origin_tx_hash.clone()).await {
236255
Ok(data) => return Ok(data),
237256
Err(err) => {
238257
tracing::warn!(?ism_address, url, ?err, "Failed to fetch offchain data");
@@ -250,6 +269,7 @@ async fn fetch_offchain_data(
250269
ism_builder: &CcipReadIsmMetadataBuilder,
251270
info: &OffchainLookup,
252271
url: &str,
272+
origin_tx_hash: Option<String>,
253273
) -> Result<Metadata, MetadataBuildError> {
254274
// Compute relayer authentication signature via EIP-191
255275
let maybe_signature_hex = if let Some(signer) = ism_builder.base.base_builder().get_signer() {
@@ -271,7 +291,13 @@ async fn fetch_offchain_data(
271291
sender: sender_as_bytes,
272292
data: data_as_bytes,
273293
signature: maybe_signature_hex,
294+
origin_tx_hash,
274295
};
296+
tracing::debug!(
297+
url = interpolated_url,
298+
?body,
299+
"Sending POST request to offchain lookup server"
300+
);
275301
Client::new()
276302
.request(Method::POST, interpolated_url)
277303
.header(CONTENT_TYPE, "application/json")
@@ -301,6 +327,10 @@ async fn fetch_offchain_data(
301327
let error_msg = format!("Failed to read offchain lookup server response: ({err})");
302328
MetadataBuildError::FailedToBuild(error_msg)
303329
})?;
330+
tracing::debug!(
331+
response = response_body,
332+
"Received response from offchain lookup server"
333+
);
304334
let json: OffchainResponse = serde_json::from_str(&response_body).map_err(|err| {
305335
let error_msg = format!(
306336
"Failed to parse offchain lookup server json response: ({err}) ({response_body})"

rust/main/agents/relayer/src/test_utils/mock_base_builder.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use hyperlane_base::{
1010
};
1111
use hyperlane_core::{
1212
accumulator::merkle::Proof, AggregationIsm, CcipReadIsm, Checkpoint, HyperlaneDomain,
13-
HyperlaneMessage, InterchainSecurityModule, Mailbox, MultisigIsm, RoutingIsm, H256,
13+
HyperlaneMessage, InterchainSecurityModule, Mailbox, MultisigIsm, RoutingIsm, H256, H512,
1414
};
1515
use hyperlane_ethereum::Signers;
1616
use hyperlane_test::mocks::MockMailboxContract;
@@ -191,6 +191,12 @@ impl BuildsBaseMetadata for MockBaseMetadataBuilder {
191191
.pop_front()
192192
.expect("No mock get_merkle_leaf_id_by_message_id response set")
193193
}
194+
async fn retrieve_origin_tx_hash_by_message_id(
195+
&self,
196+
_message_id: H256,
197+
) -> eyre::Result<Option<H512>> {
198+
Ok(None)
199+
}
194200
async fn build_ism(&self, address: H256) -> eyre::Result<Box<dyn InterchainSecurityModule>> {
195201
self.responses
196202
.build_ism

rust/main/hyperlane-base/src/db/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub use rocks::*;
55
use hyperlane_core::{
66
identifiers::UniqueIdentifier, GasPaymentKey, HyperlaneDomain, HyperlaneMessage,
77
InterchainGasPayment, InterchainGasPaymentMeta, MerkleTreeInsertion, PendingOperationStatus,
8-
H256,
8+
H256, H512,
99
};
1010

1111
mod error;
@@ -161,6 +161,19 @@ pub trait HyperlaneDb: Send + Sync {
161161
/// Retrieve the nonce of the highest processed message we're aware of
162162
fn retrieve_highest_seen_message_nonce_number(&self) -> DbResult<Option<u32>>;
163163

164+
/// Store the origin transaction hash for a dispatched message by its message id
165+
fn store_dispatched_tx_hash_by_message_id(
166+
&self,
167+
message_id: &H256,
168+
tx_hash: &H512,
169+
) -> DbResult<()>;
170+
171+
/// Retrieve the origin transaction hash for a dispatched message by its message id
172+
fn retrieve_dispatched_tx_hash_by_message_id(
173+
&self,
174+
message_id: &H256,
175+
) -> DbResult<Option<H512>>;
176+
164177
/// Store payload uuid by message id
165178
fn store_payload_uuids_by_message_id(
166179
&self,

rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use hyperlane_core::{
88
identifiers::UniqueIdentifier, Decode, Encode, GasPaymentKey, HyperlaneDomain,
99
HyperlaneLogStore, HyperlaneMessage, HyperlaneSequenceAwareIndexerStoreReader,
1010
HyperlaneWatermarkedLogStore, Indexed, InterchainGasExpenditure, InterchainGasPayment,
11-
InterchainGasPaymentMeta, LogMeta, MerkleTreeInsertion, PendingOperationStatus, H256,
11+
InterchainGasPaymentMeta, LogMeta, MerkleTreeInsertion, PendingOperationStatus, H256, H512,
1212
};
1313

1414
use crate::db::{
@@ -40,6 +40,7 @@ const MERKLE_TREE_INSERTION_BLOCK_NUMBER_BY_LEAF_INDEX: &str =
4040
"merkle_tree_insertion_block_number_by_leaf_index_";
4141
const LATEST_INDEXED_GAS_PAYMENT_BLOCK: &str = "latest_indexed_gas_payment_block";
4242
const PAYLOAD_UUIDS_BY_MESSAGE_ID: &str = "payload_uuids_by_message_id_";
43+
const MESSAGE_DISPATCHED_TX_HASH_BY_MESSAGE_ID: &str = "message_dispatched_tx_hash_by_message_id_";
4344

4445
/// Rocks DB result type
4546
pub type DbResult<T> = std::result::Result<T, DbError>;
@@ -319,6 +320,10 @@ impl HyperlaneLogStore<HyperlaneMessage> for HyperlaneRocksDB {
319320
if stored_message {
320321
stored = stored.saturating_add(1);
321322
}
323+
self.store_dispatched_tx_hash_by_message_id(
324+
&message.inner().id(),
325+
&meta.transaction_id,
326+
)?;
322327
}
323328
if stored > 0 {
324329
debug!(messages = stored, "Wrote new messages to database");
@@ -698,6 +703,25 @@ impl HyperlaneDb for HyperlaneRocksDB {
698703
) -> DbResult<Option<Vec<UniqueIdentifier>>> {
699704
self.retrieve_value_by_key(PAYLOAD_UUIDS_BY_MESSAGE_ID, message_id)
700705
}
706+
707+
fn store_dispatched_tx_hash_by_message_id(
708+
&self,
709+
message_id: &H256,
710+
tx_hash: &H512,
711+
) -> DbResult<()> {
712+
self.store_value_by_key(
713+
MESSAGE_DISPATCHED_TX_HASH_BY_MESSAGE_ID,
714+
message_id,
715+
tx_hash,
716+
)
717+
}
718+
719+
fn retrieve_dispatched_tx_hash_by_message_id(
720+
&self,
721+
message_id: &H256,
722+
) -> DbResult<Option<H512>> {
723+
self.retrieve_value_by_key(MESSAGE_DISPATCHED_TX_HASH_BY_MESSAGE_ID, message_id)
724+
}
701725
}
702726

703727
impl HyperlaneRocksDB {

rust/main/hyperlane-base/src/db/rocks/test_utils.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ mod test {
4141
RawHyperlaneMessage, H256, H512, U256,
4242
};
4343

44-
use crate::db::HyperlaneRocksDB;
44+
use crate::db::{HyperlaneDb, HyperlaneRocksDB};
4545

4646
use super::*;
4747

@@ -83,4 +83,44 @@ mod test {
8383
})
8484
.await;
8585
}
86+
87+
#[tokio::test]
88+
async fn db_stores_and_retrieves_dispatched_tx_hash() {
89+
run_test_db(|db| async move {
90+
let db = HyperlaneRocksDB::new(
91+
&HyperlaneDomain::new_test_domain("db_stores_and_retrieves_dispatched_tx_hash"),
92+
db,
93+
);
94+
95+
let m = HyperlaneMessage {
96+
nonce: 42,
97+
version: 3,
98+
origin: 10,
99+
sender: H256::from_low_u64_be(4),
100+
destination: 12,
101+
recipient: H256::from_low_u64_be(5),
102+
body: vec![],
103+
};
104+
let tx_hash = H512::from_low_u64_be(0xdeadbeef);
105+
let meta = LogMeta {
106+
address: H256::from_low_u64_be(1),
107+
block_number: 1,
108+
block_hash: H256::from_low_u64_be(1),
109+
transaction_id: tx_hash,
110+
transaction_index: 0,
111+
log_index: U256::from(0),
112+
};
113+
114+
db.store_logs(&vec![(Indexed::new(m.clone()), meta)])
115+
.await
116+
.unwrap();
117+
118+
let retrieved = db
119+
.retrieve_dispatched_tx_hash_by_message_id(&m.id())
120+
.unwrap()
121+
.unwrap();
122+
assert_eq!(retrieved, tx_hash);
123+
})
124+
.await;
125+
}
86126
}

rust/main/hyperlane-base/src/tests/mock_hyperlane_db.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::db::{DbResult, HyperlaneDb, InterchainGasExpenditureData, InterchainG
44
use hyperlane_core::{
55
identifiers::UniqueIdentifier, GasPaymentKey, HyperlaneDomain, HyperlaneMessage,
66
HyperlaneProvider, InterchainGasPayment, InterchainGasPaymentMeta, MerkleTreeInsertion,
7-
PendingOperationStatus, H256,
7+
PendingOperationStatus, H256, H512,
88
};
99

1010
mockall::mock! {
@@ -123,5 +123,7 @@ mockall::mock! {
123123
fn retrieve_highest_seen_message_nonce_number(&self) -> DbResult<Option<u32>>;
124124
fn store_payload_uuids_by_message_id(&self, message_id: &H256, payload_uuids: Vec<UniqueIdentifier>) -> DbResult<()>;
125125
fn retrieve_payload_uuids_by_message_id(&self, message_id: &H256) -> DbResult<Option<Vec<UniqueIdentifier>>>;
126+
fn store_dispatched_tx_hash_by_message_id(&self, message_id: &H256, tx_hash: &H512) -> DbResult<()>;
127+
fn retrieve_dispatched_tx_hash_by_message_id(&self, message_id: &H256) -> DbResult<Option<H512>>;
126128
}
127129
}

typescript/ccip-server/src/services/CCTPService.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,25 @@ class CCTPService extends BaseService {
6969
createAbiHandler(
7070
CctpService__factory,
7171
'getCCTPAttestation',
72-
this.getCCTPAttestation.bind(this),
72+
(message: string, logger: Logger) =>
73+
this.getCCTPAttestation(message, undefined, logger),
7374
),
7475
);
7576

7677
// CCIP-read spec: POST /getCctpAttestation
77-
this.router.post(
78-
'/getCctpAttestation',
79-
createAbiHandler(
78+
this.router.post('/getCctpAttestation', async (req, res) => {
79+
const rawTxHash = req.body?.origin_tx_hash;
80+
const originTxHash =
81+
typeof rawTxHash === 'string' && ethers.utils.isHexString(rawTxHash, 32)
82+
? rawTxHash
83+
: undefined;
84+
return createAbiHandler(
8085
CctpService__factory,
8186
'getCCTPAttestation',
82-
this.getCCTPAttestation.bind(this),
83-
),
84-
);
87+
(message: string, logger: Logger) =>
88+
this.getCCTPAttestation(message, originTxHash, logger),
89+
)(req, res);
90+
});
8591
}
8692

8793
async getCCTPMessageFromReceipt(
@@ -130,7 +136,11 @@ class CCTPService extends BaseService {
130136
throw new Error('Unable to find MessageSent event in logs');
131137
}
132138

133-
async getCCTPAttestation(message: string, logger: Logger) {
139+
async getCCTPAttestation(
140+
message: string,
141+
originTxHash: string | undefined,
142+
logger: Logger,
143+
) {
134144
const log = this.addLoggerServiceContext(logger);
135145

136146
log.info(
@@ -141,11 +151,20 @@ class CCTPService extends BaseService {
141151
const messageId: string = ethers.utils.keccak256(message);
142152
log.info({ messageId, hyperlaneMessage: message }, 'Generated message ID');
143153

144-
const txHash =
145-
await this.hyperlaneService.getOriginTransactionHashByMessageId(
154+
let txHash: string | undefined = originTxHash;
155+
156+
if (txHash) {
157+
log.info({ txHash, messageId }, 'Using tx hash provided by relayer');
158+
} else {
159+
log.info(
160+
{ messageId },
161+
'No tx hash from relayer, falling back to scraper lookup',
162+
);
163+
txHash = await this.hyperlaneService.getOriginTransactionHashByMessageId(
146164
messageId,
147165
log,
148166
);
167+
}
149168

150169
if (!txHash) {
151170
throw new Error(`Invalid transaction hash: ${txHash}`);

0 commit comments

Comments
 (0)