Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for solana message id's #32

Closed
wants to merge 19 commits into from

Conversation

eloylp
Copy link
Member

@eloylp eloylp commented Jul 8, 2024

Description

At Eiger we are developing the Solana integration. During tests, we realised the current Base58TxDigestAndEventIndex does not support the specific Solana transaction ids (a.k.a as Signatures), which have a length of 88 characters on it's base58 encoded form.

This PR aims to add support for the Solana message id type's by adding a new enum variant on the existing codebase.

Todos

  • Unit tests
  • Manual tests
  • Documentation
  • Connect epics/issues

Steps to Test

Expected Behaviour

Other Notes

We are unsure about the non direct implications of this change nor it's impact into Axelar's future plans. We appreciate your feedback and suggestions.

@eloylp eloylp self-assigned this Jul 8, 2024
Base58SolanaTxDigestAndEventIndex::new_from_b58_encoded_signature_and_index(
id, index,
)
.unwrap()
Copy link
Member Author

@eloylp eloylp Jul 8, 2024

Choose a reason for hiding this comment

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

I don't like to unwrap, but functions signature does not allow returning errors.

Copy link
Member

Choose a reason for hiding this comment

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

same, but looks like the other implementations also use unwraps freely here.

@@ -137,6 +138,12 @@ fn parse_message_id(

Ok((id.tx_hash_as_hex(), id.event_index))
}
MessageIdFormat::Base58SolanaTxDigestAndEventIndex => {
let id = Base58SolanaTxDigestAndEventIndex::from_str(&message_id)
.map_err(|_| ContractError::InvalidMessageID(message_id.into()))?;
Copy link
Member Author

Choose a reason for hiding this comment

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

I wonder about providing more error info. But maybe it doesn't worth it, as we wouldn't get much more information anyways.

Copy link
Member

Choose a reason for hiding this comment

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

I agree, would be nice, but lets keep it as is, because it fits with the rest of the code.

}
}

pub fn new_from_b58_encoded_signature_and_index(
Copy link
Member Author

@eloylp eloylp Jul 8, 2024

Choose a reason for hiding this comment

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

Added this extra constructor for convenience, and for not spreading the decoding code everywhere.

}
}

fn decode_b58_signature(signature: &str) -> Result<RawSignature, Report<Error>> {
Copy link
Member Author

Choose a reason for hiding this comment

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

Added this helper func for not repeating code.


use super::*;

fn random_bytes() -> [u8; 64] {
Copy link
Member Author

Choose a reason for hiding this comment

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

Increased this from [u8;32]

Copy link
Member

Choose a reason for hiding this comment

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

You can use the type alias that you defined at the top of the file RawSignature

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed at 3ce8e17

#[test]
fn should_parse_msg_id() {
let res = Base58SolanaTxDigestAndEventIndex::from_str(
"4hHzKKdpXH2QMB5Jm11YR48cLqUJb9Cwq2YL3tveVTPeFkZaLP8cdcH5UphVPJ7kYwCUCRLnywd3xkUhb4ZYWtf5-0",
Copy link
Member Author

Choose a reason for hiding this comment

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

Adapted to a solana signature here

let event_index = random_event_index();

// too long
let msg_id = format!("{}{}-{}", tx_digest, tx_digest, event_index);
Copy link
Member Author

Choose a reason for hiding this comment

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

Basically the test doubles the length here.

assert!(res.is_err());

// too short
let msg_id = format!("{}-{}", &tx_digest[0..30], event_index);
Copy link
Member Author

Choose a reason for hiding this comment

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

Here we keep the same lower bound

));
assert!(res.is_err());
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I think here, as in other places, another option would be to test the REGEX directly with something like rstest.

Copy link
Member

Choose a reason for hiding this comment

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

Generally speaking, I would opt for using proptest and do some property-based testing that generates the values for us. But let's not overcomplicate this PR. I'm just mentioning this as something cool that can be expanded upon.

let decoded = bs58::decode(&b58[trim..]).into_vec().unwrap();
assert_ne!(bytes.to_vec(), decoded);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I think this kind of tests are more "exploratory" ones of the b58 library. Tests that provide insights about a library expected behaviour. If i am right, i would not put them here, but in a dedicated integration test suite, so not related with any specific part of the code.

@eloylp eloylp requested a review from roberts-pumpurs July 8, 2024 14:36
Copy link
Member

@roberts-pumpurs roberts-pumpurs left a comment

Choose a reason for hiding this comment

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

overall lgtm, left some adjustments

Base58SolanaTxDigestAndEventIndex::new_from_b58_encoded_signature_and_index(
id, index,
)
.unwrap()
Copy link
Member

Choose a reason for hiding this comment

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

same, but looks like the other implementations also use unwraps freely here.

@@ -137,6 +138,12 @@ fn parse_message_id(

Ok((id.tx_hash_as_hex(), id.event_index))
}
MessageIdFormat::Base58SolanaTxDigestAndEventIndex => {
let id = Base58SolanaTxDigestAndEventIndex::from_str(&message_id)
.map_err(|_| ContractError::InvalidMessageID(message_id.into()))?;
Copy link
Member

Choose a reason for hiding this comment

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

I agree, would be nice, but lets keep it as is, because it fits with the rest of the code.

@@ -1 +1 @@
pub type Hash = [u8; 32];
pub type Hash = [u8; 32];
Copy link
Member

Choose a reason for hiding this comment

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

looks like the newline character got deleted on this file, and this diff is unnecessary. You can probably git reset this file

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed at 5e6e475

Comment on lines 51 to 56
.map_err(|e: TryFromSliceError| {
Error::InvalidTxDigest(format!(
"Signature - {}: {}",
signature,
e
))
Copy link
Member

Choose a reason for hiding this comment

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

Let's change the error message so it would be used the same way as in the other msg_id files (only populate it with the message id - signature in our case), without the extra format! call:

        .map_err(|e: TryFromSliceError| {
            Error::InvalidTxDigest(
                signature.to_owned()
            ))

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed at c370f4d


#[test]
fn should_not_parse_msg_id_with_correct_length_base58_but_wrong_length_hex() {
// this is 88 chars and valid base58, but will encode to 33 bytes
Copy link
Member

Choose a reason for hiding this comment

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

This commend is invalid, right?

but will encode to 33 bytes

Copy link
Member Author

@eloylp eloylp Jul 9, 2024

Choose a reason for hiding this comment

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

Nice catch ! fixed at c8d6344

Copy link
Member Author

Choose a reason for hiding this comment

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

Also updated this lower bound check to 64 99bd04d

})?)
}

const PATTERN: &str = "^([1-9A-HJ-NP-Za-km-z]{32,88})-(0|[1-9][0-9]*)$";
Copy link
Member

Choose a reason for hiding this comment

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

Based on my reading, the Solana signatures are 86-88 chars long.
But a zeroized byte array [0, 0, ..n] (n=64) will result in base58 string that's 64 chars long:

import base58

# Create 64 bytes all containing 0
data = bytes([0] * 64)

# Encode the data using Base58
encoded = base58.b58encode(data)

# Print the encoded string
print(f"Base58 Encoded String: {encoded}")
print(f"Length of Encoded String: {len(encoded)}")

# Base58 Encoded String: b'1111111111111111111111111111111111111111111111111111111111111111'
# Length of Encoded String: 64

Im not sure if there are other edge cases like this 🤔 , but I think its safe to adjust the regex pattern to ..{64,88}..

Copy link
Member Author

Choose a reason for hiding this comment

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

Based on the signature's code , i think it's suggesting at least we are going to fill the whole byte slice [u8; 64] , thus the minimum of 64 you are commmenting makes sense and seems safe to adopt.

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed at 002caa6


use super::*;

fn random_bytes() -> [u8; 64] {
Copy link
Member

Choose a reason for hiding this comment

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

You can use the type alias that you defined at the top of the file RawSignature

let res = Base58SolanaTxDigestAndEventIndex::from_str(
"4hHzKKdpXH2QMB5Jm11YR48cLqUJb9Cwq2YL3tveVTPeFkZaLP8cdcH5UphVPJ7kYwCUCRLnywd3xkUhb4ZYWtf5-0",
);
res.unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

it would be more idiomatic to assert the Result type rather than unwrap and not use the result:
assert!(res.is_ok());

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed at edb54bd . I changed it previously to debug a test. IMO, this is a bad sympton of fragile tests. As the negative can come from different underlying reasons. but let's keep aligned with current test structure.

Comment on lines 188 to 191
// this is 44 chars and valid base 58, but will encode to 33 bytes
// (z is the largest base58 digit, and so this will overflow 2^256)
let tx_digest = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
assert_eq!(tx_digest.len(), 44);
Copy link
Member

Choose a reason for hiding this comment

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

This should be adjusted to test the upper 88 char limit rather than 44

Copy link
Member Author

Choose a reason for hiding this comment

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

Also fixed at c8d6344

));
assert!(res.is_err());
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Generally speaking, I would opt for using proptest and do some property-based testing that generates the values for us. But let's not overcomplicate this PR. I'm just mentioning this as something cool that can be expanded upon.

@eloylp eloylp force-pushed the add-solana-msg-id-support branch from 02fb8f9 to c8d6344 Compare July 9, 2024 09:17
@eloylp eloylp requested a review from roberts-pumpurs July 9, 2024 09:58
@eloylp
Copy link
Member Author

eloylp commented Jul 9, 2024

@roberts-pumpurs thanks for your review, addressed all suggestions. I will start crafting a new PR based on this outcome for upstream.

@eloylp
Copy link
Member Author

eloylp commented Jul 9, 2024

Upstream PR is ready for review: axelarnetwork#494

cc @roberts-pumpurs

@roberts-pumpurs
Copy link
Member

Closing this PR because it has been merged in upstream

@eloylp eloylp deleted the add-solana-msg-id-support branch September 4, 2024 09:27
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.

6 participants