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

Initial Rust implementation #174

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open

Initial Rust implementation #174

wants to merge 18 commits into from

Conversation

sellout
Copy link
Collaborator

@sellout sellout commented Sep 26, 2024

This provides two more implementations of the ZcashScript trait. One is a pure Rust implementation and the other runs both the C++ and Rust implementations, returning the C++ result and logging a warning if they differ.

This follows the C++ implementation extremely closely for ease of initial review. Future changes will move from this to a Rustier approach.

E.g.,

  • early returns
  • asserts
  • bounds checking followed by accesses1
  • if instead of match
  • mutable refs
  • etc.

It differs from the C++ in some places, as well. Numeric types and casting/From are changed in some places to avoid partiality. Also, the Opcodes are partitioned into multiple types because we eventually want to be able to enforce push_only at the type level, and PushValue operations carry data (although not yet in the structure), which makes them distinct from operations (PushdataBytelength, in particular).

Footnotes

  1. accesses generally return in Result, so ? ensures they’re safe, but we keep the bounds checking so that the stack state matches the C++ version (e.g., without the bounds checking, an op that pops two elements off the stack could fail after popping one, which would leave the stack in a different state than the C++ impl which checks the length of the stack before popping either).

This was referenced Sep 26, 2024
@sellout sellout marked this pull request as draft September 26, 2024 18:33
@sellout
Copy link
Collaborator Author

sellout commented Sep 26, 2024

The files are intended to be compared side-by-side with the C++ implementation. E.g., script.rs first contains definitions from script.h, followed by definitions from script.cpp.

@sellout sellout force-pushed the sellout/rust-interpreter branch from 629eed7 to e95972b Compare September 29, 2024 20:04
@sellout sellout marked this pull request as ready for review September 29, 2024 20:26
@sellout sellout requested a review from nuttycom September 30, 2024 19:02
@sellout
Copy link
Collaborator Author

sellout commented Sep 30, 2024

@daira, @str4d, @ebfull – can’t officially add you as reviewers, but here it is.

The only changes here other than moving chunks of code around are
- moving `evaluate` out of `impl Script`, which required changing
 `&self` to `script: &Script`; and
- unifying `ExecutionOptions` with `VerificationFlags`.
For easier side-by-side comparison.
@sellout sellout force-pushed the sellout/rust-interpreter branch 3 times, most recently from 848ebaa to 09ac96b Compare October 17, 2024 19:54
The C++ impl uses exceptions for `ScriptNum`, but catches them.
These tests run both the C++ and Rust impls. One uses completely
arbitrary inputs, and the other limits script_sig to data pushes.
For now, the underlying errors are discarded when comparing against the
C++ results, but there are corresponding changes on the C++ side in a
separate branch.
@sellout sellout force-pushed the sellout/rust-interpreter branch from 09ac96b to 4c34b3e Compare October 21, 2024 19:35
@sellout
Copy link
Collaborator Author

sellout commented Oct 21, 2024

I don’t know why the test suite is failing on the C++ side now.

  • There are no changes to the C++ in this PR.
  • “It works for me” – locally running cargo test behaves as expected, running both the C++ and Rust tests.

My only guess is that it is somehow caused by the secp256k1(_sys) dependency maybe forcing a different version of the underlying libsecp256k1 to be linked.

Since there’s a lot of noise in the logs, here’s the actual error:

  = note: /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/target/debug/build/zcash_script-13bd32f862a45584/out/libzcash_script.a(484cc929e4b13849-pubkey.o): in function `(anonymous namespace)::Secp256k1SelfTester::Secp256k1SelfTester()':
          /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:17: undefined reference to `secp256k1_selftest'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/target/debug/build/zcash_script-13bd32f862a45584/out/libzcash_script.a(484cc929e4b13849-pubkey.o): in function `CPubKey::Verify(uint256 const&, std::vector<unsigned char, std::allocator<unsigned char> > const&) const':
          /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:28: undefined reference to `secp256k1_context_static'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:28: undefined reference to `secp256k1_ec_pubkey_parse'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:35: undefined reference to `secp256k1_context_static'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:35: undefined reference to `secp256k1_ecdsa_signature_parse_der'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:40: undefined reference to `secp256k1_context_static'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:40: undefined reference to `secp256k1_ecdsa_signature_normalize'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:41: undefined reference to `secp256k1_context_static'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:41: undefined reference to `secp256k1_ecdsa_verify'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/target/debug/build/zcash_script-13bd32f862a45584/out/libzcash_script.a(484cc929e4b13849-pubkey.o): in function `CPubKey::CheckLowS(std::vector<unsigned char, std::allocator<unsigned char> > const&)':
          /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:149: undefined reference to `secp256k1_context_static'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:149: undefined reference to `secp256k1_ecdsa_signature_parse_der'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:152: undefined reference to `secp256k1_context_static'
          /usr/bin/ld: /home/runner/work/zcash_script/zcash_script/depend/zcash/src/pubkey.cpp:152: undefined reference to `secp256k1_ecdsa_signature_normalize'
          collect2: error: ld returned 1 exit status

Copy link
Collaborator

@nuttycom nuttycom left a comment

Choose a reason for hiding this comment

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

Flushing comments; reviewed up to 336d0fe

// Data is being pushed to the stack here; we may need to check
// that the minimal script size was used to do so if our caller
// requires it.
if options.contains(VerificationFlags::MinimalData)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nonblocking: This is not a pure move; ideally this change would be split out into a separate commit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you mean because it’s not a const with bitwise ops?

I can push this to a later PR and swing VerificationFlags back to a bunch of consts. I think I just wrote this bit before I decided to stick as close to the C++ as possible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, I see. This is because of the commit it’s in, which is intended to just rearrange @ebfull’s implementation a bit.

// of a standard tx rule, for example) they can
// enable `discourage_upgradable_nops` to turn
// these opcodes into errors.
if options.contains(VerificationFlags::DiscourageUpgradableNOPs) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Comment on lines 197 to 198
if !options.contains(VerificationFlags::CHECKLOCKTIMEVERIFY) {
if options.contains(VerificationFlags::DiscourageUpgradableNOPs) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

src/external/mod.rs Outdated Show resolved Hide resolved
src/external/uint256.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
pub enum ScriptError {
Ok = 0,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure how big of a deal this is, but it might be better to be explicit about the exact binding to the C++ enum reprs for all of the cases; a reordering could accidentally cause these to get out of sync.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The only reason for having the repr is for the C++/Rust comparisons, where the C++ errors get mapped to the Rust ones. If they get out of sync, the tests should fail.

src/script_error.rs Outdated Show resolved Hide resolved
src/zcash_script.rs Outdated Show resolved Hide resolved
src/interpreter.rs Outdated Show resolved Hide resolved
@conradoplg
Copy link
Contributor

  • “It works for me” – locally running cargo test behaves as expected, running both the C++ and Rust tests.

It works on Rust 1.81 but it doesn't on 1.82. Which is super weird, but should give you a direction where to investigate

- remove `uint256` module
- create a specific type for the C++/Rust comparison implementation
- rename some identifiers
- rephrase some comments
@mpguerra
Copy link

mpguerra commented Dec 3, 2024

Thank you for your work on this so far @sellout! Are you planning to continue working on this one? We were hoping to tag a new release of zcash_script including this work once it is ready.

@sellout sellout force-pushed the sellout/rust-interpreter branch from 7e16a5f to 3b81cf5 Compare December 5, 2024 23:32
- Switch from `log` to `tracing` for `warn!`,
- Export `SignedOutputs`, which is needed to create a `HashType`, and
- Upgrade zcash_primitives to the same version used by zebrad.
@sellout sellout force-pushed the sellout/rust-interpreter branch from 3b81cf5 to f215757 Compare December 6, 2024 01:26
@sellout
Copy link
Collaborator Author

sellout commented Dec 6, 2024

Yes, sorry – until I saw your comment, I didn’t realize this branch wasn’t up-to-date with my current work (I had accidentally been pushing to my fork).

I’ve updated it, fixing the merge conflicts, etc., but it still seems to have the test-building issue on Ubuntu. I’ll dig into that tomorrow.

Also, this PR still needs some review. If anyone on the ZF side (@conradoplg, @arya2, whoever) has time to give it a go, I’m happy to facilitate that however I can.

sellout added a commit to sellout/zcash_script that referenced this pull request Dec 6, 2024
Matches the one in Zebra, although I think pinning to a specific release
(e.g., `channel = "1.82"`) would help with consistency, perhaps avoiding
issues like
ZcashFoundation#174 (comment)
@sellout
Copy link
Collaborator Author

sellout commented Dec 6, 2024

Ok, the current failing checks are failing on master (because of Rust stable toolchain changes that happened since those commits were merged), so #176 is trying to address those independently of this massive PR.

@mpguerra mpguerra requested a review from conradoplg December 12, 2024 09:27
mpguerra pushed a commit that referenced this pull request Dec 16, 2024
* Add a basic rust-toolchain.toml

Matches the one in Zebra, although I think pinning to a specific release
(e.g., `channel = "1.82"`) would help with consistency, perhaps avoiding
issues like
#174 (comment)

* Remove redundant toolchain info from GH workflow

[actions-rs/toolchain doesn’t support TOML-formatted
rust-toolchain files](actions-rs/toolchain#126), but it’s unnecessary anyway.

- actions-rs/cargo will pick up the rust-toolchain.toml, so we usually
  don’t need to mention the toolchain at all;
- the Windows build just runs `rustup target add x86_64-pc-windows-msvc`
  directly; and
- where we want to build with multiple toolchains (in a matrix), there
  are some slightly-awkward conditionals.

This also makes some other changes:

- `fail-fast` is disabled because it hides useful & distinct build
  results; and
-  `rustup component add` for clippy and rustfmt are removed because
  they’re in the rust-toolchain.toml toolchain, and we want to make sure
  they are, so that they’re available to developers.

* Pin rustup channel to "1.81"

Newer versions until 1.85 (current nightly) have some breakage wrt C++
linking.

* Have bindgen target correct rustc version

It should match the version in rust-toolchain.toml. Unfortunately, it’s
not possible to reference that directly, so this adds comments to remind
contributors to update them together.
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.

5 participants