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

Hooks Amendment #4225

Merged
merged 3 commits into from
Feb 22, 2024
Merged

Hooks Amendment #4225

merged 3 commits into from
Feb 22, 2024

Conversation

RichardAH
Copy link
Collaborator

@RichardAH RichardAH commented Jul 8, 2022

xrpl hooks
The Hooks Amendment adds smart contract-like functionality to the XRP Ledger. With this amendment users may choose to install small, efficient web assembly binaries onto their XRPL Accounts using the new SetHook transactor. These binaries allow for custom logic about which transactions are accepted (or rejected) and can also emit new transactions and manage custom information called Hook State.

These early blogs describe the premise of Hooks:


Getting Started

Hooks Builder

A developer-centric Hooks IDE for your browser, connected to the testnet. (No download is required.)

Hooks Transaction Explorer

A custom technical explorer for the testnet.

Hooks V2 Staging Testnet

Consists of 9 nodes, peering information: https://xumm.notion.site/Hooks-V2-staging-net-info-XLS20-518fa261c5cd49d2bcb89a5b9e7bef05.

Hooks Documentation

Is not present on github, lives exclusively on redme.io.

Test Plan

  1. The codebase for Hooks is being tested live on the community-run Hooks V2 Staging Testnet.
  2. Javascript test cases are being actively developed here:
    https:/XRPL-Labs/xrpld-hooks/tree/develop/hook
  3. Unit tests are planned but have not yet been started.

Notes

  1. This PR is a transfer of the current Hooks codebase to XRPLF/rippled onto its own branch.
  2. It is not production ready and should not be used in production.
  3. This PR is squashed from XRPL-Labs/xrpld-hooks.

Future Tasks

  • Port javascript tests to unit tests.
  • Improved APIs for accessing Hook state, namespace and other ledger objects.
  • Clear backlog of existing issues on https:/XRPL-Labs/xrpld-hooks/issues.
  • Fix WASMEDGE in build system to allow for static builds (currently only non-static builds work).
  • Small refactors and some small rewrites needed in wasm binary parsing in SetHook transactor.
  • Find and clean up all TODOs in code.
  • Security review.

Copy link
Contributor

@nbougalis nbougalis left a comment

Choose a reason for hiding this comment

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

This is a huge and hugely complicated PR, so it will take a while. This is my first run through this code and it's causing my browser a lot of grief, given its size.

Expect follow-up reviews, but there are a couple of issues that need to be addressed already and I figured this was a good place to stop.

Builds/CMake/RippledSettings.cmake Outdated Show resolved Hide resolved
src/ripple/app/hook/Enum.h Show resolved Hide resolved
enum hook_log_code : uint16_t
{
/* HookSet log-codes */
AMENDMENT_DISABLED = 1, // attempt to HookSet when amendment is not yet enabled.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggesting avoiding explicitly numbering these. Number the first as 1 and let the compiler do the tedious work; just leave a note to never remove entries from this enum.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree with you, but these are used for external tools (such as the Hooks Builder) and being able to look up the number pre-populated in the source is very useful for those tools and their developers. It's also a strong reminder to developers not to insert new codes out of order which might change existing codes. (Although as you say a comment would work too.)

}

enum hook_return_code : int64_t
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Existing serializers don't have support for signed 64-bit integers, so you'll end up having to cast things. Why not change this to std::uint64_t and just treat it the way Microsoft does HRESULT? Reserve some of the high bits (e.g. the top four) to indicate different classes of errors. Anything that has one of those four bits set is an error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This enum is only used by the Hook API itself, so these are values returned by a host function to the running web assembly (analogous to the way a system call returns a value to a userspace program, where in this case rippled is the kernel and the hook is the userspace program). The result is never serialized to metadata or anywhere else, unless the hook developer serializes into hook state. Using int64_t allows for a very simple way for hook developers to test for an error, namely if (hook_api_func() < 0) //error logic here. Changing it to a uint64_t is perfectly possible, as you point out, but then hook developers would be writing if (hook_api_func() >> 63U) //error logic here this is possible but I think developing hooks is already difficult enough without making them jump through bit shifting hoops just to test for errors. Can revisit if you think important.

src/ripple/app/hook/Enum.h Outdated Show resolved Hide resolved
src/ripple/app/tx/impl/SetHook.cpp Outdated Show resolved Hide resolved
@@ -456,6 +593,11 @@ Transactor::apply()
NotTEC
Transactor::checkSign(PreclaimContext const& ctx)
{
// hook emitted transactions do not have signatures
if (ctx.view.rules().enabled(featureHooks) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a really scary bit of code here. We need to be 1000% sure that a transaction that passes this if can NEVER be generated from something other than a hook under ANY circumstances.

It might actually be nice to have a test here that verifies that the account has authorized the hook that issued the transaction to issue transactions on its behalf.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Definitely agree that this is a dangerous and pivotal piece of code.

The isEmittedTxn function tests for the presence of the sfEmitDetails block in a transaction. Provided there's no way to put that validly into a non-emitted transaction then this condition will never pass. This is already blocked at the netcode in 5 different locations... however given how cheap it is to check, the more checks the better. (Ultimately it could actually be checked in TxQ. Unless it's added from the emitDir it should never have sfEmitDetails.)

It is possible to check if the Hook that emitted the txn was authorized by the emitting account, but there's an annoying corner case here: since the emitted txn is "from the past" i.e. created by a past execution of a hook, the current state of the ledger might not be the same as it was then. That is: the emitted txn was authorized when it was emitted but at the time the signature is being checked (at insertion into the next ledger, or the one after that) it might not be. And a very real case of this is when a Hook emits a txn that deletes itself. The "one and done" self deleting hook is actually a very useful construct, and not something we want to accidentally prevent.

We could just check the historic ledger. But if we do this then we need to ensure a validator resyncing has at least that much history before this code ever executes. (Even though it's only 5 ledgers, I'm not sure if that is necessarily the case at the moment?)

src/ripple/app/tx/impl/Transactor.cpp Outdated Show resolved Hide resolved
src/ripple/app/tx/impl/Transactor.cpp Outdated Show resolved Hide resolved
src/ripple/app/tx/impl/Transactor.h Outdated Show resolved Hide resolved
@RichardAH
Copy link
Collaborator Author

Thanks for the review, Nik. I will get on to these suggestions when back at work

@nbougalis
Copy link
Contributor

Thanks for the review, Nik. I will get on to these suggestions when back at work

Thank me after I'm done reviewing. This was just the opening salvo. Enjoy your vacation 😃

@greg7mdp
Copy link
Contributor

greg7mdp commented Aug 15, 2022 via email

@RichardAH
Copy link
Collaborator Author

@RichardAH - I think the issue might be that you used curly braces for the std::string constructor. Can you try using parentheses? std::string raddr((char*)(memory + read_ptr), read_len); greg

    std::string raddr((char*)(memory + read_ptr), read_len);

    auto const result = decodeBase58Token(raddr, TokenType::AccountID);
    if (result.empty())
        return INVALID_ARGUMENT;


    WRITE_WASM_MEMORY_AND_RETURN(
        write_ptr, write_len,
        result.data(), 20,
        memory, memory_length);

Still doesn't work for me. Can you please test yourself also? It's technically possible my specific build environment is responsible for the outcome I see

@greg7mdp
Copy link
Contributor

Still doesn't work for me. Can you please test yourself also? It's technically possible my specific build environment is responsible for the outcome I see

Hum, I apologize, I'm new at Ripple and I don't know how I would test this. I don't see a JTX test for this.

I tried building your branch on windows, but there are errors:

13>C:\greg\github\ripple\rippled\src\ripple/app/hook/Enum.h(209,9): error C2143: syntax error: missing '}' before 'constant' (compiling source file C:\greg\github\ripple\rippled\build_win\CMakeFiles\rippled.dir\Unity\unity_7_cxx.cxx)
13>C:\greg\github\ripple\rippled\src\ripple/app/hook/Enum.h(209,9): error C2059: syntax error: 'constant' (compiling source file C:\greg\github\ripple\rippled\build_win\CMakeFiles\rippled.dir\Unity\unity_7_cxx.cxx)
13>C:\greg\github\ripple\rippled\src\ripple/app/hook/Enum.h(222,5): error C2143: syntax error: missing ';' before '}' (compiling source file C:\greg\github\ripple\rippled\build_win\CMakeFiles\rippled.dir\Unity\unity_7_cxx.cxx)
13>C:\greg\github\ripple\rippled\src\ripple/app/hook/Enum.h(323,1): error C2059: syntax error: '}' (compiling source file C:\greg\github\ripple\rippled\build_win\CMakeFiles\rippled.dir\Unity\unity_7_cxx.cxx)
13>C:\greg\github\ripple\rippled\src\ripple/app/hook/Enum.h(323,1): error C2143: syntax error: missing ';' before '}' (compiling source file C:\greg\github\ripple\rippled\build_win\CMakeFiles\rippled.dir\Unity\unity_7_cxx.cxx)
13>C:\greg\github\ripple\rippled\src\ripple/app/hook/Enum.h(209,9): error C2143: syntax error: missing '}' before 'constant' (compiling source file C:\greg\github\ripple\rippled\build_win\CMakeFiles\rippled.dir\Unity\unity_6_cxx.cxx)
...

The first one is because the enum OVERFLOW in app/hook/Enum.h:

        OVERFLOW = -30,                 // if an operation with a float results in an overflow

clashes with the windows macro defined in corectr_math.h:

#if defined(_CRT_INTERNAL_NONSTDC_NAMES) && _CRT_INTERNAL_NONSTDC_NAMES

    #define DOMAIN      _DOMAIN
    #define SING        _SING
    #define OVERFLOW    _OVERFLOW
    #define UNDERFLOW   _UNDERFLOW

@RichardAH
Copy link
Collaborator Author

RichardAH commented Aug 15, 2022

The first one is because the enum OVERFLOW in app/hook/Enum.h:

        OVERFLOW = -30,                 // if an operation with a float results in an overflow

clashes with the windows macro defined in corectr_math.h:

I've actually seen this conflict before (can't recall when). It doesn't occur on the compilers I use but I will change the name for you now.

Comment on lines +212 to +214
#define NOT_IN_BOUNDS(ptr, len, memory_length)\
((static_cast<uint64_t>(ptr) > static_cast<uint64_t>(memory_length)) || \
((static_cast<uint64_t>(ptr) + static_cast<uint64_t>(len)) > static_cast<uint64_t>(memory_length)))
Copy link
Contributor

Choose a reason for hiding this comment

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

The first part of the || is unnecessary. The following is enough:
#define NOT_IN_BOUNDS(ptr, len, memory_length)\ ((static_cast<uint64_t>(ptr) + static_cast<uint64_t>(len)) > static_cast<uint64_t>(memory_length))

Copy link
Collaborator Author

@RichardAH RichardAH Aug 16, 2022

Choose a reason for hiding this comment

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

👍Good point

From memory the lower bound check was actually (although not obviously) to help check for an overflow, but the inputs are always going to be i32 from web assembly and it's an incomplete overflow check anyway. Since these are very inexpensive checks (fastest possible CPU operations) I think we should go with a better defensive (not strictly necessary) overflow check instead of the first part of the || like so:

static_cast<uint64_t>(ptr) + static_cast<uint64_t>(len) < std::min(static_cast<uint64_t>(ptr), static_cast<uint64_t>(len))

Happy to hear your thoughts

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe I am missing something, but I think this is equivalent to a + b < min(a, b) which, considering a and b are positive, will never be true. If the original i32 numbers can be negative maybe we can do a better check?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If for whatever reason at some point this bounds check macro is used with uint64_t's (keeping in mind macro arguments are not typed checked) then it's best to check for overflow here as a defensive programming principle. a + b can be less than min(a,b) if a+b overflows the uint64_t.
Here's an illustration:

#include <stdint.h>
#include <iostream>
#include <algorithm>
int main() {

    auto overflowTest = [](uint64_t a, uint64_t b)
    {
        uint64_t c = a + b;
        std::cout <<
            "a=" << a << ", "
            "b=" << b << ". "
            "a+b=" << (a+b) << ". "
            "a + b < min(a,b) ? " << (a + b < std::min(a,b) ? "yes": "no") << "\n";
    };

    // without an overflow event
    overflowTest(0xFFFFFFFFFFFFFFFDULL, 2);
    // with an overflow event
    overflowTest(0xFFFFFFFFFFFFFFFEULL, 2);
    return 0;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, if the inputs are always going to be i32 from webassembly, I don't think the check as described would serve any purpose. I though the NOT_IN_BOUNDS was intended to check whether we were indeed accessing memory withing the webassembly block.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, if the inputs are always going to be i32 from webassembly, I don't think the check as described would serve any purpose. I though the NOT_IN_BOUNDS was intended to check whether we were indeed accessing memory withing the webassembly block.

Agree. It's defensive: I.e. what happens if somehow for some reason uint64_ts are passed into the macro and they overflow the integer.

@greg7mdp
Copy link
Contributor

I've actually seen this conflict before (can't recall when). It doesn't occur on the compilers I use but I will change the name for you now.

Thanks @RichardAH, this addressed the first issue. It also doesn't build with stdc++17, so I switched to stdc++20. Now I have a boost issue:

13>C:\soft\boost_1_71_0\boost/outcome.hpp(32,20): error C2230: could not find module 'outcome_v2_0' (compiling source file C:\greg\github\ripple\rippled\build_win\CMakeFiles\rippled.dir\Unity\unity_17_cxx.cxx)

The doc still states that boost 1.70 is required, and I'm using 1.71.

@RichardAH
Copy link
Collaborator Author

RichardAH commented Aug 15, 2022

I've actually seen this conflict before (can't recall when). It doesn't occur on the compilers I use but I will change the name for you now.

Thanks @RichardAH, this addressed the first issue. It also doesn't build with stdc++17, so I switched to stdc++20. Now I have a boost issue:

13>C:\soft\boost_1_71_0\boost/outcome.hpp(32,20): error C2230: could not find module 'outcome_v2_0' (compiling source file C:\greg\github\ripple\rippled\build_win\CMakeFiles\rippled.dir\Unity\unity_17_cxx.cxx)

The doc still states that boost 1.70 is required, and I'm using 1.71.

I only build on linux, so I'm not sure 😬 Is this specific to the Hooks PR or does rippled in general cause this?

@greg7mdp
Copy link
Contributor

I only build on linux, so I'm not sure 😬 Is this specific to the Hooks PR or does rippled in general cause this?

The current version of rippled from XRPL (1.9.2) builds fine with stdc++17 and boost 1.71. Not sure where these new requirements originate from. I'm trying to build the Hooks PR with C++20 and boost 1.75, and I still have errors. It looks like it tries to import boost outcome as a C++ module. I'm not quite sure how to turn this off when C++20 is set.

#if defined(__cpp_modules) && !defined(GENERATING_OUTCOME_MODULE_INTERFACE)
import outcome_v2_0;
#else
...

@scottschurr
Copy link
Collaborator

Just to stick my nose in briefly, I'd like to discourage us from using modules in C++20. There are a number of issues that still need to be worked out, particularly with cross-platform applications. There are folks actively working on resolving these problems in C++23.

@RichardAH
Copy link
Collaborator Author

RichardAH commented Aug 16, 2022

The current version of rippled from XRPL (1.9.2) builds fine with stdc++17 and boost 1.71. Not sure where these new requirements originate from. I'm trying to build the Hooks PR with C++20 and boost 1.75, and I still have errors. It looks like it tries to import boost outcome as a C++ module. I'm not quite sure how to turn this off when C++20 is set.

I've never tried building Hooks on Windows. I will try now and see if I can shed some light on this.
Edit: Windows builds are a maze. I'm going to let someone else who is an expert in them figure this out 😛

@ximinez
Copy link
Collaborator

ximinez commented Aug 19, 2022

I've never tried building Hooks on Windows. I will try now and see if I can shed some light on this. Edit: Windows builds are a maze. I'm going to let someone else who is an expert in them figure this out 😛

@greg7mdp @RichardAH I haven't been keeping up with this conversation, but I happened to notice this comment, and tried to build for the first time on my Windows box. It failed to even get to rippled code, choking on the wasmedge dependency. Long story short, it looks like it has a dependency on LLVM, which isn't as commonly available on Windows by default. Here's the troublesome command and output: https://gist.github.com/ximinez/bb214950c64defe0404c44d02c3f7dc8

One option is to just document the dependency. Optimally, it would be great if rippled's CMake config could treat LLVM as yet another external project.

@intelliot
Copy link
Collaborator

@RichardAH what's your preferred path forward for this PR?

@RichardAH
Copy link
Collaborator Author

We are launching hooks on a sidechain so this can close

@RichardAH RichardAH closed this Apr 28, 2023
@RichardAH
Copy link
Collaborator Author

By popular demand Hooks PR is being reopened
https://x.com/sentosumosaba/status/1756877266481729958

Next steps:

  1. Accept the PR to place Hooks on its own branch
  2. New PR/s to bring the branch up to date with rippled.
  3. New PR/s to bring the branch up to date with production Hooks as runs on Xahau.
  4. New PR/s to harmonise Hooks with mainnet (for example it needs testing with xls 20 and other amendments).

We will provide developer time on points 3 and 4. We expect Ripple to provide dev time on 2 and 4.

Let’s go!

@RichardAH RichardAH reopened this Feb 12, 2024
@intelliot intelliot requested review from nbougalis and removed request for nbougalis February 22, 2024 02:44
Copy link
Collaborator

@intelliot intelliot left a comment

Choose a reason for hiding this comment

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

The target of this PR is the hooks branch, and this approval indicates this PR can be merged to (only) that branch as-is as a step along the path of developing Hooks. I have not code reviewed this, and these changes will not be merged to the mainline development branch (develop) until at least the following have been completed:

(Listed below in no particular order)

  • Merged/tested with the latest code in develop, including any additional testing/changes appropriate due to potential interactions with other amendments/functionality
  • Figure out how to build this on Windows for development
  • Improved APIs
  • Resolution of any known issues
  • Resolution of all TODOs
  • Security audits/reviews

@intelliot
Copy link
Collaborator

intelliot commented Feb 22, 2024

Suggested commit message:

Introduce in-development Hooks amendment (#4225)

* Hooks V2 squashed from
  https:/XRPL-Labs/xrpld-hooks/tree/006524a10aa406d09742dae69a9cf271bdea0d26

* Include changes from code review

Introduce "Hooks" functionality with the new SetHook transactor. This
significantly expands the scope of transaction processing capabilities
with an implementation that supports the execution of programmable
scripts that attach to accounts and execute during transactions. This
provides programmability and allows for advanced use cases involving
transaction validation, automation, and complex multi-signature
arrangements. The core transaction pipeline executes hooks, manages
their states, and handles associated fees. Adjustments across the code
enable seamless integration of hooks with existing functionality, e.g.
signer lists. This is a large advancement in the architecture and
functionality of the system.

Additional testing, including performance testing, and reviews,
including security reviews, are necessary before this change should be
considered production-ready. Additionally, there is a need to modify the
change so that it can build on Windows, which is currently a supported
platform for development purposes.

@intelliot intelliot merged commit df8d879 into XRPLF:hooks Feb 22, 2024
0 of 2 checks passed
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