-
Notifications
You must be signed in to change notification settings - Fork 1.1k
docs: Clarify that callback can be called more than once #1727
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
Open
real-or-random
wants to merge
2
commits into
bitcoin-core:master
Choose a base branch
from
real-or-random:202508-illegal-twice
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when I initially read the comments on master, I assumed "API not crashing guarantee" is only for debug stage (no
abort
call) since they were in 1 para. but after seeing this diff just realised that crashing in a callback is acceptable and still a stable API regardless of debug/production build. so I think the comments made it clearer for me!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, paragraph breaks matter. :)
Though I think it is relatively clear even on
master
. When the callback callsabort
, then the entire program will crash immediately. And from that point on, any guarantee you get from the API is vacuous.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok that makes sense. but now I'm confused. It's just a line break difference but I interpreted the meaning before and after as:
API function called is guaranteed not to cause a crash in debug stage (in tests where it's mostly the callback function
counting_callback_fn
that just counts and no abort)API function called is guaranteed not to cause a crash in any stage (tests or IRL)
=> because it's the callback doing the crash + handling invalid user inputs + user responsibility and so API is stable?
do we want 1 or 2?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, I think the guarantee we want to provide is:
"An API function called is guaranteed not to crash after the callback returns."
This guarantee holds no matter what the callback function is set to, which corresponds to what you called "in any "stage". Now, of course, this guarantee doesn't say anything if the callback itself causes a crash (and the default callback does) because then the callback never returns, so there's no "after the callback returns".
Now that I've written this, I'm not sure what this actually means. In simple cases, the only thing the API function does after the callback returns is to return 0. This shouldn't crash. But there are more complex cases:
secp256k1_ec_pubkey_cmp
tries to read the memory atpubkey1
even after an invalidpubkey0
triggered the callback. This may crash ifpubkey1
points to an invalid memory region.Perhaps what we should want to say here is something like this: "An API function called is guaranteed not to crash after the callback returns (unless the crash is unrelated to the violation that triggered the callback)". But this is imprecise and ugly.
Perhaps we should drop this sentence entirely. It creates more confusion than it resolves. What about this?
"Should the callback return, the return value and output arguments of the API function call are undefined. Moreover, the same API call may trigger the callback again in this case."
Note that all of this is about API guarantees, so the audience is API users. As a result, "debug stage" means "debug stage of a program using libsecp256k1". (And as a side remark, note that the callbacks can be set at runtime, so strictly speaking, this is not (necessarily) about debug vs. release builds.) So if you develop a program (an application or another library) that uses the libsecp256k1 API, you may want to set a callback that does not crash for debugging purposes. I'm not entirely sure how useful this functionality is to API users, but this is what the docs here describe.
In practice, where this is useful is exactly in our internal tests, as you correctly point out. But what I'm trying to say is that these are our internal tests, where we may do everything because we control the implementation of the API functions. So we could also have an internal way of setting a counting callback that is not exposed through the public API.
If you ask me, the main purpose of setting callback functions is that you can control how the library crashes in production code. Maybe in your application, you don't want
abort()
but a more graceful termination. Or you can't use the default callback because it writes an error message tostdout
but you don't havestdout
because you're developing for a hardware wallet. (In the latter case, setting callbacks at runtime won't help you because the program won't compile in the first place. We've added compile-time overrides for this case.)And on a last note, I don't think all of this is optimally designed. I believe we'd do some things differently if we had a chance to start from scratch.