Support 64-byte txn ids (attempt 2) (#2612)

### Description

Adds support to TxOutcome for 512-bit ids and to log meta to support
Solana. Now that we have primitive types in our crate we were able to
create a better solution than before which just stores the EVM 256bit
hashes inside of the 512bit one with leading zeros rather than needing
to over engineer an enum container for it.

### Drive-by changes

- Removed some dead code
- Improved primitive type support for 512bit types
- Improved serialization support for 512bit types

### Related issues

- Fixes #2252 
- Replaces #2256

### Backward compatibility

_Are these changes backward compatible?_

Yes-ish, changes one of the internal database key strings. At standup we
discussed the possibility of migrating the data and concluded that was a
can of worms we did not want to open.

_Are there any infrastructure implications, e.g. changes that would
prohibit deploying older commits using this infra tooling?_

None

### Testing

_What kind of testing have these changes undergone?_

Unit tests
pull/2633/head
Mattie Conover 1 year ago committed by GitHub
parent 5d8a2e568c
commit 8cbfd3cbc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/rust.yml
  2. 2
      rust/Cargo.lock
  3. 4
      rust/agents/relayer/src/msg/pending_message.rs
  4. 37
      rust/agents/scraper/src/chain_scraper/mod.rs
  5. 2
      rust/agents/validator/src/validator.rs
  6. 2
      rust/chains/hyperlane-sealevel/Cargo.toml
  7. 15
      rust/chains/hyperlane-sealevel/src/mailbox.rs
  8. 7
      rust/chains/hyperlane-sealevel/src/validator_announce.rs
  9. 2
      rust/hyperlane-base/src/db/rocks/hyperlane_db.rs
  10. 4
      rust/hyperlane-base/src/db/rocks/test_utils.rs
  11. 22
      rust/hyperlane-core/Cargo.toml
  12. 26
      rust/hyperlane-core/src/chain.rs
  13. 4
      rust/hyperlane-core/src/error.rs
  14. 48
      rust/hyperlane-core/src/traits/encode.rs
  15. 6
      rust/hyperlane-core/src/traits/mod.rs
  16. 10
      rust/hyperlane-core/src/types/log_metadata.rs
  17. 10
      rust/hyperlane-core/src/types/mod.rs
  18. 60
      rust/hyperlane-core/src/types/primitive_types.rs

@ -84,4 +84,4 @@ jobs:
- name: Setup WASM
run: rustup target add wasm32-unknown-unknown
- name: Check WASM
run: cargo check -p hyperlane-core --all-features --target wasm32-unknown-unknown
run: cargo check -p hyperlane-core --features=strum,test-utils --target wasm32-unknown-unknown

2
rust/Cargo.lock generated

@ -3652,7 +3652,6 @@ dependencies = [
"convert_case 0.6.0",
"derive-new",
"derive_more",
"ethers",
"ethers-contract",
"ethers-core",
"ethers-providers",
@ -3668,6 +3667,7 @@ dependencies = [
"serde",
"serde_json",
"sha3 0.10.8",
"solana-sdk",
"strum 0.25.0",
"thiserror",
"tiny-keccak",

@ -242,7 +242,7 @@ impl PendingOperation for PendingMessage {
op_try!(critical: self.ctx.origin_gas_payment_enforcer.record_tx_outcome(&self.message, tx_outcome), "recording tx outcome");
if tx_outcome.executed {
info!(
hash=?tx_outcome.txid,
txid=?tx_outcome.transaction_id,
"Message successfully processed by transaction"
);
self.submitted = true;
@ -251,7 +251,7 @@ impl PendingOperation for PendingMessage {
PendingOperationResult::Success
} else {
info!(
hash=?tx_outcome.txid,
txid=?tx_outcome.transaction_id,
"Transaction attempting to process message reverted"
);
self.on_reprepare()

@ -80,7 +80,14 @@ impl HyperlaneSqlDb {
log_meta: impl Iterator<Item = &LogMeta>,
) -> Result<impl Iterator<Item = TxnWithId>> {
let block_hash_by_txn_hash: HashMap<H256, H256> = log_meta
.map(|meta| (meta.transaction_hash, meta.block_hash))
.map(|meta| {
(
meta.transaction_id
.try_into()
.expect("256-bit transaction ids are the maximum supported at this time"),
meta.block_hash,
)
})
.collect();
// all blocks we care about
@ -265,7 +272,13 @@ impl HyperlaneLogStore<HyperlaneMessage> for HyperlaneSqlDb {
.map(|t| (t.hash, t))
.collect();
let storable = messages.iter().map(|m| {
let txn = txns.get(&m.1.transaction_hash).unwrap();
let txn = txns
.get(
&m.1.transaction_id
.try_into()
.expect("256-bit transaction ids are the maximum supported at this time"),
)
.unwrap();
StorableMessage {
msg: m.0.clone(),
meta: &m.1,
@ -292,7 +305,15 @@ impl HyperlaneLogStore<Delivery> for HyperlaneSqlDb {
.map(|t| (t.hash, t))
.collect();
let storable = deliveries.iter().map(|(message_id, meta)| {
let txn_id = txns.get(&meta.transaction_hash).unwrap().id;
let txn_id = txns
.get(
&meta
.transaction_id
.try_into()
.expect("256-bit transaction ids are the maximum supported at this time"),
)
.unwrap()
.id;
StorableDelivery {
message_id: *message_id,
meta,
@ -320,7 +341,15 @@ impl HyperlaneLogStore<InterchainGasPayment> for HyperlaneSqlDb {
.map(|t| (t.hash, t))
.collect();
let storable = payments.iter().map(|(payment, meta)| {
let txn_id = txns.get(&meta.transaction_hash).unwrap().id;
let txn_id = txns
.get(
&meta
.transaction_id
.try_into()
.expect("256-bit transaction ids are the maximum supported at this time"),
)
.unwrap()
.id;
StorablePayment {
payment,
meta,

@ -209,7 +209,7 @@ impl Validator {
Ok(outcome) => {
if !outcome.executed {
error!(
hash=?outcome.txid,
txid=?outcome.transaction_id,
gas_used=?outcome.gas_used,
gas_price=?outcome.gas_price,
"Transaction attempting to announce validator reverted. Make sure you have enough funds in your account to pay for transaction fees."

@ -23,7 +23,7 @@ tracing.workspace = true
url.workspace = true
account-utils = { path = "../../sealevel/libraries/account-utils" }
hyperlane-core = { path = "../../hyperlane-core" }
hyperlane-core = { path = "../../hyperlane-core", features = ["solana"] }
hyperlane-sealevel-interchain-security-module-interface = { path = "../../sealevel/libraries/interchain-security-module-interface" }
hyperlane-sealevel-mailbox = { path = "../../sealevel/programs/mailbox", features = ["no-entrypoint"] }
hyperlane-sealevel-message-recipient-interface = { path = "../../sealevel/libraries/message-recipient-interface" }

@ -11,7 +11,7 @@ use hyperlane_core::{
accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint,
ContractLocator, Decode as _, Encode as _, HyperlaneAbi, HyperlaneChain, HyperlaneContract,
HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexer, LogMeta, Mailbox,
MessageIndexer, SequenceIndexer, TxCostEstimate, TxOutcome, H256, U256,
MessageIndexer, SequenceIndexer, TxCostEstimate, TxOutcome, H256, H512, U256,
};
use hyperlane_sealevel_interchain_security_module_interface::{
InterchainSecurityModuleInstruction, VerifyInstruction,
@ -60,13 +60,6 @@ use crate::{
const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111";
const SPL_NOOP: &str = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV";
// FIXME solana uses the first 64 byte signature of a transaction to uniquely identify the
// transaction rather than a 32 byte transaction hash like ethereum. Hash it here to reduce
// size - requires more thought to ensure this makes sense to do...
fn signature_to_txn_hash(signature: &Signature) -> H256 {
H256::from(solana_sdk::hash::hash(signature.as_ref()).to_bytes())
}
// The max amount of compute units for a transaction.
// TODO: consider a more sane value and/or use IGP gas payments instead.
const PROCESS_COMPUTE_UNITS: u32 = 1_400_000;
@ -524,10 +517,10 @@ impl Mailbox for SealevelMailbox {
.map_err(|err| warn!("Failed to confirm inbox process transaction: {}", err))
.map(|ctx| ctx.value)
.unwrap_or(false);
let txid = signature_to_txn_hash(&signature);
let txid = signature.into();
Ok(TxOutcome {
txid,
transaction_id: txid,
executed,
// TODO use correct data upon integrating IGP support
gas_price: U256::zero(),
@ -680,7 +673,7 @@ impl SealevelMailboxIndexer {
// TODO: get these when building out scraper support.
// It's inconvenient to get these :|
block_hash: H256::zero(),
transaction_hash: H256::zero(),
transaction_id: H512::zero(),
transaction_index: 0,
log_index: U256::zero(),
},

@ -3,7 +3,8 @@ use tracing::{info, instrument, warn};
use hyperlane_core::{
Announcement, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain,
HyperlaneContract, HyperlaneDomain, SignedType, TxOutcome, ValidatorAnnounce, H160, H256, U256,
HyperlaneContract, HyperlaneDomain, SignedType, TxOutcome, ValidatorAnnounce, H160, H256, H512,
U256,
};
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
@ -117,10 +118,10 @@ impl ValidatorAnnounce for SealevelValidatorAnnounce {
_tx_gas_limit: Option<U256>,
) -> ChainResult<TxOutcome> {
warn!(
"Announcing validator storage locations within the agents is not supported on Sealevel"
"Announcing validator storage locations within the agents is not supported on Sealevel"
);
Ok(TxOutcome {
txid: H256::zero(),
transaction_id: H512::zero(),
executed: false,
gas_used: U256::zero(),
gas_price: U256::zero(),

@ -26,7 +26,7 @@ const MESSAGE_DISPATCHED_BLOCK_NUMBER: &str = "message_dispatched_block_number_"
const MESSAGE: &str = "message_";
const NONCE_PROCESSED: &str = "nonce_processed_";
const GAS_PAYMENT_FOR_MESSAGE_ID: &str = "gas_payment_for_message_id_v2_";
const GAS_PAYMENT_META_PROCESSED: &str = "gas_payment_meta_processed_v2_";
const GAS_PAYMENT_META_PROCESSED: &str = "gas_payment_meta_processed_v3_";
const GAS_EXPENDITURE_FOR_MESSAGE_ID: &str = "gas_expenditure_for_message_id_v2_";
const PENDING_MESSAGE_RETRY_COUNT_FOR_MESSAGE_ID: &str =
"pending_message_retry_count_for_message_id_";

@ -32,7 +32,7 @@ where
mod test {
use hyperlane_core::{
HyperlaneDomain, HyperlaneLogStore, HyperlaneMessage, LogMeta, RawHyperlaneMessage, H256,
U256,
H512, U256,
};
use crate::db::HyperlaneRocksDB;
@ -60,7 +60,7 @@ mod test {
address: H256::from_low_u64_be(1),
block_number: 1,
block_hash: H256::from_low_u64_be(1),
transaction_hash: H256::from_low_u64_be(1),
transaction_id: H512::from_low_u64_be(1),
transaction_index: 0,
log_index: U256::from(0),
};

@ -15,31 +15,30 @@ auto_impl.workspace = true
borsh.workspace = true
bs58.workspace = true
bytes = { workspace = true, features = ["serde"] }
config = { workspace = true, optional = true }
convert_case.workspace = true
derive-new.workspace = true
derive_more.workspace = true
ethers-contract = { workspace = true, optional = true }
ethers-core = { workspace = true, optional = true }
ethers-providers = { workspace = true, optional = true }
eyre.workspace = true
fixed-hash.workspace = true
getrandom.workspace = true
hex.workspace = true
itertools.workspace = true
num = { workspace = true, features = ["serde"] }
num-derive.workspace = true
num-traits.workspace = true
primitive-types = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true
sha3.workspace = true
solana-sdk = { workspace = true, optional = true }
strum = { workspace = true, optional = true, features = ["derive"] }
thiserror.workspace = true
uint.workspace = true
fixed-hash.workspace = true
tiny-keccak = { workspace = true, features = ["keccak"]}
config = { workspace = true, optional = true }
ethers = { workspace = true, optional = true }
ethers-core = { workspace = true, optional = true }
ethers-contract = { workspace = true, optional = true }
ethers-providers = { workspace = true, optional = true }
strum = { workspace = true, optional = true, features = ["derive"] }
primitive-types = { workspace = true, optional = true }
uint.workspace = true
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "time"] }
@ -49,4 +48,5 @@ default = []
test-utils = ["dep:config"]
agent = ["ethers", "strum"]
strum = ["dep:strum"]
ethers = ["dep:ethers-core", "dep:ethers-contract", "dep:ethers-providers", "dep:primitive-types", "dep:ethers"]
ethers = ["dep:ethers-core", "dep:ethers-contract", "dep:ethers-providers", "dep:primitive-types"]
solana = ["dep:solana-sdk"]

@ -9,7 +9,7 @@ use num_traits::FromPrimitive;
use strum::{EnumIter, EnumString, IntoStaticStr};
use crate::utils::many_to_one;
use crate::{ChainResult, HyperlaneProtocolError, IndexMode, H160, H256};
use crate::{HyperlaneProtocolError, IndexMode, H160, H256};
#[derive(Debug, Clone)]
pub struct Address(pub bytes::Bytes);
@ -36,30 +36,6 @@ impl<'a> std::fmt::Display for ContractLocator<'a> {
}
}
#[async_trait::async_trait]
pub trait Chain {
/// Query the balance on a chain
async fn query_balance(&self, addr: Address) -> ChainResult<Balance>;
}
impl From<Address> for H160 {
fn from(addr: Address) -> Self {
H160::from_slice(addr.0.as_ref())
}
}
impl From<H160> for Address {
fn from(addr: H160) -> Self {
Address(addr.as_bytes().to_owned().into())
}
}
impl From<&'_ Address> for H160 {
fn from(addr: &Address) -> Self {
H160::from_slice(addr.0.as_ref())
}
}
/// All domains supported by Hyperlane.
#[derive(FromPrimitive, PartialEq, Eq, Debug, Clone, Copy, Hash)]
#[cfg_attr(

@ -152,8 +152,8 @@ impl<T: ethers_providers::Middleware + 'static> From<ethers_contract::ContractEr
}
#[cfg(feature = "ethers")]
impl From<ethers::providers::ProviderError> for ChainCommunicationError {
fn from(err: ethers::providers::ProviderError) -> Self {
impl From<ethers_providers::ProviderError> for ChainCommunicationError {
fn from(err: ethers_providers::ProviderError) -> Self {
Self::ContractError(HyperlaneCustomErrorWrapper(Box::new(err)))
}
}

@ -1,6 +1,6 @@
use std::io::{Error, ErrorKind};
use crate::{HyperlaneProtocolError, H256, U256};
use crate::{HyperlaneProtocolError, H160, H256, H512, U256};
/// Simple trait for types with a canonical encoding
pub trait Encode {
@ -53,28 +53,36 @@ impl Decode for ethers_core::types::Signature {
}
}
impl Encode for H256 {
fn write_to<W>(&self, writer: &mut W) -> std::io::Result<usize>
where
W: std::io::Write,
{
writer.write_all(self.as_ref())?;
Ok(32)
}
}
macro_rules! impl_encode_for_primitive_hash {
($t:ty) => {
impl Encode for $t {
fn write_to<W>(&self, writer: &mut W) -> std::io::Result<usize>
where
W: std::io::Write,
{
writer.write_all(&self.0)?;
Ok(<$t>::len_bytes())
}
}
impl Decode for H256 {
fn read_from<R>(reader: &mut R) -> Result<Self, HyperlaneProtocolError>
where
R: std::io::Read,
Self: Sized,
{
let mut digest = H256::default();
reader.read_exact(digest.as_mut())?;
Ok(digest)
}
impl Decode for $t {
fn read_from<R>(reader: &mut R) -> Result<Self, HyperlaneProtocolError>
where
R: std::io::Read,
Self: Sized,
{
let mut h = Self::zero();
reader.read_exact(&mut h.0)?;
Ok(h)
}
}
};
}
impl_encode_for_primitive_hash!(H160);
impl_encode_for_primitive_hash!(H256);
impl_encode_for_primitive_hash!(H512);
impl Encode for U256 {
fn write_to<W>(&self, writer: &mut W) -> std::io::Result<usize>
where

@ -33,8 +33,8 @@ mod validator_announce;
/// The result of a transaction
#[derive(Debug, Clone, Copy)]
pub struct TxOutcome {
/// The txid
pub txid: crate::H256,
/// The transaction identifier/hash
pub transaction_id: crate::H512,
/// True if executed, false otherwise (reverted, etc.)
pub executed: bool,
/// Amount of gas used on this transaction.
@ -48,7 +48,7 @@ pub struct TxOutcome {
impl From<ethers_core::types::TransactionReceipt> for TxOutcome {
fn from(t: ethers_core::types::TransactionReceipt) -> Self {
Self {
txid: t.transaction_hash.into(),
transaction_id: t.transaction_hash.into(),
executed: t.status.unwrap().low_u32() == 1,
gas_used: t.gas_used.map(Into::into).unwrap_or(crate::U256::zero()),
gas_price: t

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "ethers")]
use ethers_contract::LogMeta as EthersLogMeta;
use crate::{H256, U256};
use crate::{H256, H512, U256};
/// A close clone of the Ethereum `LogMeta`, this is designed to be a more
/// generic metadata that we can use for other blockchains later. Some changes
@ -21,8 +21,8 @@ pub struct LogMeta {
/// The block hash in which the log was emitted
pub block_hash: H256,
/// The transaction hash in which the log was emitted
pub transaction_hash: H256,
/// The transaction identifier/hash in which the log was emitted
pub transaction_id: H512,
/// Transactions index position log was created from
pub transaction_index: u64,
@ -42,10 +42,10 @@ impl From<EthersLogMeta> for LogMeta {
impl From<&EthersLogMeta> for LogMeta {
fn from(v: &EthersLogMeta) -> Self {
Self {
address: crate::H160::from(v.address).into(),
address: v.address.into(),
block_number: v.block_number.as_u64(),
block_hash: v.block_hash.into(),
transaction_hash: v.transaction_hash.into(),
transaction_id: v.transaction_hash.into(),
transaction_index: v.transaction_index.as_u64(),
log_index: v.log_index.into(),
}

@ -160,8 +160,8 @@ impl Add for InterchainGasExpenditure {
/// Uniquely identifying metadata for an InterchainGasPayment
#[derive(Debug)]
pub struct InterchainGasPaymentMeta {
/// The transaction hash in which the GasPayment log was emitted
pub transaction_hash: H256,
/// The transaction id/hash in which the GasPayment log was emitted
pub transaction_id: H512,
/// The index of the GasPayment log within the transaction's logs
pub log_index: u64,
}
@ -172,7 +172,7 @@ impl Encode for InterchainGasPaymentMeta {
W: Write,
{
let mut written = 0;
written += self.transaction_hash.write_to(writer)?;
written += self.transaction_id.write_to(writer)?;
written += self.log_index.write_to(writer)?;
Ok(written)
}
@ -185,7 +185,7 @@ impl Decode for InterchainGasPaymentMeta {
Self: Sized,
{
Ok(Self {
transaction_hash: H256::read_from(reader)?,
transaction_id: H512::read_from(reader)?,
log_index: u64::read_from(reader)?,
})
}
@ -194,7 +194,7 @@ impl Decode for InterchainGasPaymentMeta {
impl From<&LogMeta> for InterchainGasPaymentMeta {
fn from(meta: &LogMeta) -> Self {
Self {
transaction_hash: meta.transaction_hash,
transaction_id: meta.transaction_id,
log_index: meta.log_index.as_u64(),
}
}

@ -3,12 +3,9 @@
#![allow(clippy::assign_op_pattern)]
#![allow(clippy::reversed_empty_ranges)]
use std::fmt::Formatter;
use crate::types::serialize;
use borsh::{BorshDeserialize, BorshSerialize};
use fixed_hash::{construct_fixed_hash, impl_fixed_hash_conversions};
use serde::de::Visitor;
use uint::construct_uint;
/// Error type for conversion.
@ -59,32 +56,25 @@ construct_fixed_hash! {
pub struct H512(64);
}
struct H512Visitor;
impl<'de> Visitor<'de> for H512Visitor {
type Value = H512;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a 512-bit hash")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.try_into()
.map_err(|_| E::invalid_length(v.len(), &self))
.map(H512)
}
}
#[cfg(feature = "ethers")]
type EthersH160 = ethers_core::types::H160;
#[cfg(feature = "ethers")]
type EthersH256 = ethers_core::types::H256;
#[cfg(feature = "ethers")]
type EthersH512 = ethers_core::types::H512;
#[cfg(feature = "ethers")]
impl_fixed_hash_conversions!(H256, EthersH160);
#[cfg(feature = "ethers")]
impl_fixed_hash_conversions!(EthersH256, H160);
#[cfg(feature = "ethers")]
impl_fixed_hash_conversions!(EthersH512, H160);
#[cfg(feature = "ethers")]
impl_fixed_hash_conversions!(EthersH512, H256);
#[cfg(feature = "ethers")]
impl_fixed_hash_conversions!(H512, EthersH160);
#[cfg(feature = "ethers")]
impl_fixed_hash_conversions!(H512, EthersH256);
impl_fixed_hash_conversions!(H256, H160);
impl_fixed_hash_conversions!(H512, H256);
@ -139,11 +129,19 @@ macro_rules! impl_fixed_uint_conversions {
};
}
#[cfg(feature = "ethers")]
impl_fixed_uint_conversions!(U256, ethers_core::types::U128);
impl_fixed_uint_conversions!(U256, U128);
impl_fixed_uint_conversions!(U512, U128);
impl_fixed_uint_conversions!(U512, U256);
#[cfg(feature = "ethers")]
impl_fixed_uint_conversions!(U256, ethers_core::types::U128);
#[cfg(feature = "ethers")]
impl_fixed_uint_conversions!(U512, ethers_core::types::U128);
#[cfg(feature = "ethers")]
impl_fixed_uint_conversions!(U512, ethers_core::types::U256);
#[cfg(feature = "ethers")]
impl_fixed_uint_conversions!(ethers_core::types::U512, U256);
#[cfg(feature = "ethers")]
impl_fixed_uint_conversions!(ethers_core::types::U512, U128);
macro_rules! impl_f64_conversions {
($ty:ty) => {
@ -308,3 +306,19 @@ impl_fixed_hash_serde!(H128, 16);
impl_fixed_hash_serde!(H160, 20);
impl_fixed_hash_serde!(H256, 32);
impl_fixed_hash_serde!(H512, 64);
#[cfg(feature = "solana")]
impl From<solana_sdk::hash::Hash> for H256 {
fn from(hash: solana_sdk::hash::Hash) -> Self {
H256(hash.to_bytes())
}
}
// Solana uses the first 64 byte signature of a transaction to uniquely identify the
// transaction.
#[cfg(feature = "solana")]
impl From<solana_sdk::signature::Signature> for H512 {
fn from(sig: solana_sdk::signature::Signature) -> Self {
H512(sig.into())
}
}

Loading…
Cancel
Save