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

SPL errors from hashes #5169

Merged
merged 5 commits into from
Sep 1, 2023

Conversation

buffalojoec
Copy link
Contributor

@buffalojoec buffalojoec commented Aug 30, 2023

This PR adds unique, custom error codes to every error in SPL libraries - including the two interfaces transfer-hook and token-metadata.

The ability to derive unique u32 error codes from hash inputs is powered by these changes to the spl-program-error crate, which simply hashes the following combination of inputs and takes bytes 13 through 16 as the little-endian u32:

"spl_program_error:" + <enum name> + ":" + <variant name>

This creates unique error codes for each error, as requested in this comment

Closes #5042
Solves discussions in #5168

@buffalojoec buffalojoec marked this pull request as ready for review August 30, 2023 02:38
Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

The implementation looks perfect! I did just realize though... we'll need a way for someone at the top-level to get more information about the error, since they're essentially random numbers 😓

Ok, hear me out, what if instead of giving each error a random number, we give the first error a random hashed number of namespace:error_name, and everything counts up from there? That way someone just needs to find out that first number, which we force people to add to the source code somewhere to make it easier to discover, similar to solana-frozen-abi, ie:

#[spl_program_error(hash_error_codes = true, error_start = 42)]
pub enum MyError {
    OhNoes,
}

Where 42 is hash('spl_program_error:MyError')[13..17]. If it's incorrect, the macro tells you what number to set as error_start.

We could maybe condense those two into one arg, but I can't come up with any good names. Maybe just have expected_error_start? Or expose a new macro, like spl_program_error_with_unique_error_codes which does the hashing and also takes in the expected start number? These names all stink, so I'll noodle on something better.

token/transfer-hook-interface/src/error.rs Outdated Show resolved Hide resolved
libraries/program-error/derive/src/macro_impl.rs Outdated Show resolved Hide resolved
@buffalojoec buffalojoec mentioned this pull request Aug 30, 2023
@buffalojoec
Copy link
Contributor Author

Ok, hear me out, what if instead of giving each error a random number, we give the first error a random hashed number of namespace:error_name, and everything counts up from there?

This was a concern of mine on the approach as well. I felt like a unique error per variant was overkill, and posed a higher collision risk.
Also, derive(FromPrimitive) handles this very neatly, as you might have noticed in #5168. You just set the first one and the rest increment. So, I'm game.

On the topic of collisions, I felt like it was probably also overkill, but I have one particular concern about a hash randomly resulting in, say [1, 0, 0, 0] and colliding with (literally any) program errors. For this reason I suggest we lean toward a fixed 9 on the MSB and take only bytes 13, 14, and 15 from the hash. wdyt?

Where 42 is hash('spl_program_error:MyError')[13..17]. If it's incorrect, the macro tells you what number to set as error_start.

Ha, this is cool and would totally work! It smells a bit like a macro anti-pattern to me, but I guess it really doesn't do any harm, and it will help with readability for sure! I'm in for this change as well.

@joncinque
Copy link
Contributor

On the topic of collisions, I felt like it was probably also overkill, but I have one particular concern about a hash randomly resulting in, say [1, 0, 0, 0] and colliding with (literally any) program errors. For this reason I suggest we lean toward a fixed 9 on the MSB and take only bytes 13, 14, and 15 from the hash. wdyt?

Ah right, that's a good point. Rather than remove a whole byte from the randomness, how about we just make sure that the number is >= 256 to be totally safe? and if it happens to be 0 <= x < 256, just shift over 1 byte until you get to a valid set of bytes.

It smells a bit like a macro anti-pattern to me

If you have a better way of surfacing that number easily for people looking at the source, I'm game! But I couldn't come up with anything else

@buffalojoec
Copy link
Contributor Author

buffalojoec commented Aug 30, 2023

On the topic of collisions, I felt like it was probably also overkill, but I have one particular concern about a hash randomly resulting in, say [1, 0, 0, 0] and colliding with (literally any) program errors. For this reason I suggest we lean toward a fixed 9 on the MSB and take only bytes 13, 14, and 15 from the hash. wdyt?

smh, I meant 256 not 9

Ah right, that's a good point. Rather than remove a whole byte from the randomness, how about we just make sure that the number is >= 256 to be totally safe? and if it happens to be 0 <= x < 256, just shift over 1 byte until you get to a valid set of bytes.

Which number are you referring to? The entire u32 integer itself? In my opinion we want to clear at least 5_000 for Anchor errors, and likely even go a little further just to be safe for people who like to categorize with leading digits. Say, 10_000 or 100_000?

It smells a bit like a macro anti-pattern to me

If you have a better way of surfacing that number easily for people looking at the source, I'm game! But I couldn't come up with anything else

Nah I think it's okay. Unless I find some Rustaceans barking about it online, it's going in

@buffalojoec
Copy link
Contributor Author

For us VS Code userz 🎉

Screenshot 2023-08-30 at 9 22 59 AM

@joncinque
Copy link
Contributor

Which number are you referring to? The entire u32 integer itself? In my opinion we want to clear at least 5_000 for Anchor errors, and likely even go a little further just to be safe for people who like to categorize with leading digits. Say, 10_000 or 100_000?

Sorry, yeah I mean the whole 4 bytes interpreted as a little-endian u32. Does Anchor start at 5000 for all programs? That seems very odd. If that's the case, we can just do 6000 or something, right? And that's already being plenty generous I'd say. If people use their own start, that's similar to just grinding a bunch of error names until you get something you like.

@buffalojoec
Copy link
Contributor Author

buffalojoec commented Aug 31, 2023

Which number are you referring to? The entire u32 integer itself? In my opinion we want to clear at least 5_000 for Anchor errors, and likely even go a little further just to be safe for people who like to categorize with leading digits. Say, 10_000 or 100_000?

Sorry, yeah I mean the whole 4 bytes interpreted as a little-endian u32. Does Anchor start at 5000 for all programs? That seems very odd. If that's the case, we can just do 6000 or something, right? And that's already being plenty generous I'd say. If people use their own start, that's similar to just grinding a bunch of error names until you get something you like.

They use the first digit as a category https://docs.rs/anchor-lang/latest/anchor_lang/error/enum.ErrorCode.html
and then users start at 6000 https://docs.rs/anchor-lang/latest/anchor_lang/error/constant.ERROR_CODE_OFFSET.html

I have 10,000 in my latest commit, I guess we could get away with 7,000 ?

Besides our offset, this is ready for another look.

Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

Just few tiny last little questions, this is extremely close!

libraries/program-error/derive/src/macro_impl.rs Outdated Show resolved Hide resolved
libraries/program-error/derive/src/macro_impl.rs Outdated Show resolved Hide resolved
libraries/program-error/derive/src/macro_impl.rs Outdated Show resolved Hide resolved
libraries/program-error/derive/src/lib.rs Show resolved Hide resolved
@buffalojoec
Copy link
Contributor Author

Comments resolved!

This is a breaking change for anything using these libraries downstream. I'm thinking:

  • spl-program-error: 1.0.0 or 0.3.0 if we want to keep it in development
  • Any downstream crates: Bump minor version

wdyt?

@@ -11,7 +11,7 @@ use {
};

const SPL_ERROR_HASH_NAMESPACE: &str = "spl_program_error";
const SPL_ERROR_HASH_MIN_VALUE: u32 = 10_0000;
const SPL_ERROR_HASH_MIN_VALUE: u32 = 7_0000;
Copy link
Contributor

Choose a reason for hiding this comment

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

Haha I just realized that this has an extra 0, should be 7_000. Good thing we have multiple rounds of reviews

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah! my bad

@joncinque
Copy link
Contributor

This is a breaking change for anything using these libraries downstream

Sorry, maybe I'm missing something, but how is this change breaking? It passes CI without any changes to other libraries that are using it currently

@buffalojoec
Copy link
Contributor Author

This is a breaking change for anything using these libraries downstream

Sorry, maybe I'm missing something, but how is this change breaking? It passes CI without any changes to other libraries that are using it currently

Is it not technically breaking since the underlying error codes are changing?

CI is passing because I included changes to the tests where the expected error changed.

For example, anyone expecting TransferHookError::IncorrectAccount is now getting AccountResolutionError::IncorrectAccount, and furthermore anyone expecting some u32 like 1 is now getting 13498579 or something wild.

@joncinque
Copy link
Contributor

Is it not technically breaking since the underlying error codes are changing?

But that's only if you use hash_error_code_start, right?

@buffalojoec
Copy link
Contributor Author

Is it not technically breaking since the underlying error codes are changing?

But that's only if you use hash_error_code_start, right?

I'm thinking about if I'm working with spl_token_2022 and I'm expecting certain codes from, say transfer hook helpers. Also spl_token_client for that matter

@joncinque
Copy link
Contributor

I'm thinking about if I'm working with spl_token_2022 and I'm expecting certain codes from, say transfer hook helpers. Also spl_token_client for that matter

Ah yes, that's a breaking change in those libraries for sure. I'm planning on cutting a new minor release for all of these anyway. I'll do it once this lands

Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

Looks good!

hash

@buffalojoec
Copy link
Contributor Author

Lmao Lennon would approve

@buffalojoec buffalojoec merged commit cfaabb5 into solana-labs:master Sep 1, 2023
31 checks passed
thlorenz added a commit to ironforge-cloud/solana-program-library that referenced this pull request Sep 4, 2023
* master: (719 commits)
  release: Bump token-2022 and all dependencies (solana-labs#5189)
  SPL errors from hashes (solana-labs#5169)
  stake-pool: Add comments about unnecessary ownership checks (HAL-01) (solana-labs#5084)
  stake-pool: Enforce that pool mint uses 9 decimal places (HAL-03) (solana-labs#5085)
  build(deps-dev): bump tsx from 3.12.7 to 3.12.8 in /single-pool/js (solana-labs#5188)
  account-compression: Fixup sdk doc deployment (solana-labs#5187)
  token-js: renamed `getExtraAccountMetaAccount` to `getExtraAccountMetaAddress` (solana-labs#5186)
  token-js: added an e2e test for transferring using a mint with a transfer hook extension (solana-labs#5138)
  Serde optional dependencies clean-up (solana-labs#5181)
  build(deps): bump chrono from 0.4.27 to 0.4.28 (solana-labs#5180)
  stake-pool: Use unaligned types for safe pointer cast (solana-labs#5179)
  spl-pod: make code docs more explicit (solana-labs#5178)
  token-js: added extra account resolution for transfer hook extension (solana-labs#5112)
  Fix incorrect code doc (solana-labs#5177)
  Move Pod types to separate library (solana-labs#5119)
  build(deps-dev): bump @typescript-eslint/eslint-plugin from 6.4.1 to 6.5.0 in /memo/js (solana-labs#5176)
  build(deps): bump chrono from 0.4.26 to 0.4.27 (solana-labs#5171)
  build(deps-dev): bump prettier from 3.0.2 to 3.0.3 in /token-swap/js (solana-labs#5174)
  build(deps-dev): bump prettier from 3.0.2 to 3.0.3 in /token/js (solana-labs#5172)
  build(deps-dev): bump prettier from 3.0.2 to 3.0.3 in /token-lending/js (solana-labs#5173)
  ...
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.

[Token-2022] Transfer hook interface error reporting
2 participants