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

RFC: libbitcoinconsensus #5235

Merged
merged 10 commits into from
Nov 20, 2014
Merged

RFC: libbitcoinconsensus #5235

merged 10 commits into from
Nov 20, 2014

Conversation

theuni
Copy link
Member

@theuni theuni commented Nov 7, 2014

This includes #5212, which will need to be merged before this one.

This adds a consensus lib that offers nothing more than transaction verification. It does not rely on any bitcoind globals or state, and boost is not required.

The external api still needs some attention, it's not intended to be final. What's here is just something to get started, I believe @sipa had some plans for mapping external api flags to internal ones.

Build-wise, I believe it's feature complete other than Windows dll's. They work fine, but libtool refuses to statically-link libc/libgcc. That can be fixed post-merge.

@sipa
Copy link
Member

sipa commented Nov 7, 2014

./autogen.sh
./configure --disable-wallet --without-gui
...
checking whether to build libraries... ./configure: line 23562: ],: command not found
yes
...

@theuni
Copy link
Member Author

theuni commented Nov 7, 2014

Er, sorry for the busted c/p. Fixed.

@laanwj laanwj added this to the 0.10.0 milestone Nov 7, 2014
@laanwj laanwj added the Feature label Nov 7, 2014
@laanwj
Copy link
Member

laanwj commented Nov 17, 2014

This needs some additions to .gitignore:

?? src/.libs/
?? src/core/libbitcoinconsensus_la-transaction.lo
?? src/crypto/libbitcoinconsensus_la-ripemd160.lo
?? src/crypto/libbitcoinconsensus_la-sha1.lo
?? src/crypto/libbitcoinconsensus_la-sha2.lo
?? src/libbitcoinconsensus.la
?? src/libbitcoinconsensus_la-eccryptoverify.lo
?? src/libbitcoinconsensus_la-ecwrapper.lo
?? src/libbitcoinconsensus_la-hash.lo
?? src/libbitcoinconsensus_la-pubkey.lo
?? src/libbitcoinconsensus_la-uint256.lo
?? src/libbitcoinconsensus_la-utilstrencodings.lo
?? src/script/libbitcoinconsensus_la-bitcoinconsensus.lo
?? src/script/libbitcoinconsensus_la-interpreter.lo
?? src/script/libbitcoinconsensus_la-script.lo

/// Returns true if the input nIn of the serialized transaction pointed to by
/// txTo correctly spends the scriptPubKey pointed to by scriptPubKey under
/// the additional constraints specified by flags.
EXPORT_SYMBOL bool bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, const unsigned int scriptPubKeyLen,
Copy link
Member

Choose a reason for hiding this comment

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

What is the reason for having const here on basic types like unsigned int?

Copy link
Member

Choose a reason for hiding this comment

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

Should we be using 'bool' here? That is C99. As we know, some platforms cough MSVC cough still don't implement that, so it may be better to just return a 0/1 int.

Copy link
Member Author

Choose a reason for hiding this comment

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

No reason, will change. ACK on not using bool as well.

@laanwj
Copy link
Member

laanwj commented Nov 17, 2014

Works for me.

I patched main.cpp to write out script verification operations

diff --git a/src/main.cpp b/src/main.cpp
index 2bff781..80433d8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1359,8 +1359,16 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach

 bool CScriptCheck::operator()() const {
     const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
-    if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingSignatureChecker(*ptxTo, nIn, cacheStore)))
+    bool rv;
+    if (!(rv=VerifyScript(scriptSig, scriptPubKey, nFlags, CachingSignatureChecker(*ptxTo, nIn, cacheStore))))
         return error("CScriptCheck() : %s:%d VerifySignature failed", ptxTo->GetHash().ToString(), nIn);
+
+    CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+    ssTx << *ptxTo;
+    std::string strTxTo = HexStr(ssTx.begin(), ssTx.end());
+    std::string strScriptPubKey = HexStr(scriptPubKey.begin(), scriptPubKey.end());
+    printf("verify_script %s %s %i %i %i\n", strTxTo.c_str(), strScriptPubKey.c_str(), nIn, nFlags, rv);
+
     return true;
 }

Then, I wrote a Python script that uses ctypes to load and call into the library, verifying the operations written

from __future__ import print_function, division
import json, binascii, os
import ctypes
from ctypes import c_uint

class BitcoinConsensus(object):
    def __init__(self, path):
        self.l = ctypes.CDLL(path)

    def version(self):
        return self.l.bitcoinconsensus_version()

    def verify_script(self, scriptPubKey, txTo, nIn, flags):
        return self.l.bitcoinconsensus_verify_script(scriptPubKey, c_uint(len(scriptPubKey)), 
                txTo, c_uint(len(txTo)), 
                c_uint(nIn), c_uint(flags))

if __name__ == '__main__':
    l = BitcoinConsensus('./src/.libs/libbitcoinconsensus.so')
    infile = '/tmp/scripttest.txt'

    print('API version', l.version())
    assert(l.version() == 0)

    with open(infile, 'r') as f:
        for i,line in enumerate(f):
            d = line.split(' ')
            txTo = binascii.a2b_hex(d[1])
            scriptPubKey = binascii.a2b_hex(d[2])
            nIn = int(d[3])
            nFlags = int(d[4]) # as CScript bitfield
            rv = int(d[5])

            # check
            r = l.verify_script(scriptPubKey, txTo, nIn, nFlags)
            if r != rv:
                print('Mismatch at line %i, verify returns %i versus %i (nFlags %i)' % (i+1, r, rv, nFlags))
  • I pumped 1.2GB of verification operations through the above script, and no errors occurred.
  • I've also tried corrupting the scripts and/or transactions as well as nIn, resulting as expected in the function returning False but no crashes
    • Appending arbitrary data to txTo did not cause verification to fail: is this expected? Should it check whether the entire input is consumed?
  • I've stress-tested it by running in a loop for quite a while - no errors, memory usage was constant

@theuni
Copy link
Member Author

theuni commented Nov 17, 2014

@laanwj Thanks for testing!

It sounds like a good idea to fail if the entire input isn't consumed. That can be reflected in a new error (error codes still need to be hooked up here, I'll do that once that PR is merged).

@theuni
Copy link
Member Author

theuni commented Nov 17, 2014

hah! I suppose I'll do that now :)

@theuni
Copy link
Member Author

theuni commented Nov 17, 2014

Rebased and updated for the comments above. Fixed the missing copyright, wrong name for include guard, and unnecessary const arguments, and squashed those into the original commits.

The last new commit adds error codes for busted txto's, though it's not very graceful. The errors aren't related to the script, but to the tx.. so the codes are shoe-horned into the existing enum. Suggestions for a better approach are welcome.

@laanwj
Copy link
Member

laanwj commented Nov 18, 2014

@theuni I'm not against a more-detailed failure reason in the API, but the current way is inconsistent. bitcoinconsensus.h should not depend on script_error.h which is an internal header.

IMO if we're going to do this we need a ScriptError just for external consumption, just like we did for the flags. This keeps bitcoinconsensus.h self-contained.

@sipa
Copy link
Member

sipa commented Nov 18, 2014

I think @theuni's purpose with script_error was to be able to use it as both an internal and external header.

However, I agree with @laanwj to keep the internal and external definitions completely separate - even if that means duplicating some code.

@laanwj
Copy link
Member

laanwj commented Nov 18, 2014

I prefer have our API, at least at version 0, to be defined by one header.

And yes - keeping the internal and external definitions completely separate is better, not least because they have diverged already: the new codes SCRIPT_ERR_TX_* errors are only used externally.

BTW: If script_error.h is supposed to be an external header, it should not define the internal, non-API function const char* ScriptErrorString(const ScriptError error);

@jtimon
Copy link
Contributor

jtimon commented Nov 18, 2014

Currently libconsensus only has script verification, but since we plan to add more things in the future, I don't think it should be in script/, maybe have its own dir?

@sipa
Copy link
Member

sipa commented Nov 18, 2014

@jtimon agree

@theuni
Copy link
Member Author

theuni commented Nov 18, 2014

Updated after discussion on IRC. I went ahead and squashed it down since it was impossible to review commit-by-commit with things changing back and forth. Probably easier to review the new files wholly anyway. Sorry if that complicates at all.

@theuni
Copy link
Member Author

theuni commented Nov 18, 2014

@laanwj I missed your comment above about api version before pushing. Agreed. I'll add that with the next changes pending review.

@jtimon Agreed, but ok to change that as we go as more functionality is added?

@theuni
Copy link
Member Author

theuni commented Nov 19, 2014

Briefly tested the .dll and .dylib enough to verify that they actually load and bitcoinconsensus_version() works, so presumably the objects themselves are in working order.

@laanwj
Copy link
Member

laanwj commented Nov 19, 2014

API looks good to me now.

@theuni objdump -p output for the dll looks good to me; http://paste.ubuntu.com/9090362/ . Only dependence is on system DLLs

  • DLL Name: ADVAPI32.dll
  • DLL Name: GDI32.dll
  • DLL Name: KERNEL32.dll
  • DLL Name: msvcrt.dll
  • DLL Name: USER32.dll

Only exported symbols are those two that we want:

Export Address Table -- Ordinal Base 1
    [   0] +base[   1] 22c40 Export RVA
    [   1] +base[   2] 21e10 Export RVA

[Ordinal/Name Pointer] Table
    [   0] bitcoinconsensus_verify_script
    [   1] bitcoinconsensus_version

Edit: test on windows completed succesfully. I executed the same above test in Python on windows - no problems. Tried to just load/unload the library a few times, just to try it out, no problems.

@laanwj
Copy link
Member

laanwj commented Nov 19, 2014

ACK commithash ae6bb2f7ff016df27456d544842ec6bc5f5c7cfa https://dev.visucore.com/bitcoin/acks/5235


typedef enum bitcoinconsensus_txerror_t
{
bitcoinconsensus_TX_ERR_OK = 0,
Copy link
Member

Choose a reason for hiding this comment

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

Thinking about it IMO this OK should be generic: bitcoinconsensus_ERR_OK = 0. It doesn't really matter whether this is a transaction OK or another error 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.

This was meant to imply that the tx was accepted by the api, whether or not the verification succeeded. bitcoinconsensus_ERR_OK is a bit hazy, as it could imply that bitcoinconsensus_verify_script() itself succeeded.

Ultimately I think you're right, we just need to make it clear in the docs that it means "verification was attempted".

@sipa
Copy link
Member

sipa commented Nov 19, 2014

ACK.

Compiling with -O2 -flto, and stripping results in an 83 kbyte .so file. Nice.

It would be nice to have some proof of concept command-line tool that shows how to use the library.

@jtimon
Copy link
Contributor

jtimon commented Nov 19, 2014

@laanwj "Appending arbitrary data to txTo did not cause verification to fail: is this expected? Should it check whether the entire input is consumed?"
The txTo is only used for signature verification (ie if there weren't different hashtypes we could just pass a hash and the script instead of txTo and nIn).
So if the script contains script sigs and you change the txTo without updating the signature the script should become invalid.

@sipa
Copy link
Member

sipa commented Nov 19, 2014

@jtimon That's changed now; a check was added that the passed transaction is deserialized completely.

@sipa
Copy link
Member

sipa commented Nov 19, 2014

Needs rebase.

…dflags

For windows builds, exe's are always static, but libs should still conform to
--enabled-shared and --enable-static.
They're not necessary, and not always supported. We only need to know about
hidden and default.
@theuni
Copy link
Member Author

theuni commented Nov 20, 2014

rebased, squashed down some, and addressed @laanwj's points.

@jtimon I'd like to move around later to avoid any bikeshedding wrt naming. There are still a few things left to handle (docs/tests/samples), so let's do it along with those.

Credit BlueMatt for libbitcoinsonsensus.h/cpp
They should be hooked up in other places as well, but this is a start.
dll's are no longer dynamically linked to libgcc/libstdc++/libssp
This ensures that users of the lib will be able to mangle the paths to work
in their bundles.
@laanwj laanwj merged commit 9eb5a5f into bitcoin:master Nov 20, 2014
laanwj added a commit that referenced this pull request Nov 20, 2014
9eb5a5f build: pad header for osx libs (Cory Fields)
9ed8979 build: fix static dll link for mingw (Cory Fields)
19df238 build: shared lib build should work reasonably well now (Cory Fields)
269efa3 build: add quick consensus lib tests (Cory Fields)
cdd36c6 build: add --with-libs so that libs are optional (Cory Fields)
2cf5f16 build: add libbitcoinconsensus files and hook up the lib build (Cory Fields)
ee64c53 build: remove internal/protected build attribute checks (Cory Fields)
f36a40f build: check visibility attributes (Cory Fields)
811a765 build: mingw needs libssp for hardening with dlls (Cory Fields)
e0077de build: make a distinction between static app ldflags and static lib ldflags (Cory Fields)
theuni added a commit to theuni/bitcoin that referenced this pull request Nov 20, 2014
Some users may have libtool libs (.la) installed in their linker search paths.
In this case, using -static-libtool-libs would try to link in .a's instead of
shared libs. That would be harmless unless the .a was built in a way that
would break linking, like non-fpic.

What we really want is "-static" here. Despite its name, it's actually less
aggressive than -static-libtool-libs. It causes only internal libs to be linked
statically (libbitcoinconsensus is the one were'a after).
laanwj added a commit that referenced this pull request Nov 21, 2014
f618577 build: fix link error on some platforms. Fixes #5235 (Cory Fields)
reddink pushed a commit to reddcoin-project/reddcoin-3.10 that referenced this pull request May 27, 2020
Some users may have libtool libs (.la) installed in their linker search paths.
In this case, using -static-libtool-libs would try to link in .a's instead of
shared libs. That would be harmless unless the .a was built in a way that
would break linking, like non-fpic.

What we really want is "-static" here. Despite its name, it's actually less
aggressive than -static-libtool-libs. It causes only internal libs to be linked
statically (libbitcoinconsensus is the one were'a after).

(cherry picked from commit f618577)

# Conflicts:
#	src/Makefile.test.include
laanwj added a commit that referenced this pull request Feb 12, 2021
…or Darwin targets

de4238f build: consolidate reduced export checks (fanquake)
012bdec build: add building libconsensus to end-of-configure output (fanquake)
8f360e3 build: remove ax_gcc_func_attribute macro (fanquake)
f054a08 build: remove AX_GCC_FUNC_ATTRIBUTE test for dllimport (fanquake)
7cd0a69 build: test for __declspec(dllexport) in configure (fanquake)
1624e17 build: remove duplicate visibility attribute detection (fanquake)

Pull request description:

  Darwin targets do not have a `protected` visibility function attribute, see [LLVM explanation](https://github.com/llvm/llvm-project/blob/8e9a505139fbef7d2e6e9d0adfe1efc87326f9ef/clang/lib/Basic/Targets/OSTargets.h#L131). This means that the `AX_GCC_FUNC_ATTRIBUTE` check for `visibility` fails:
  ```bash
  configure:24513: checking for __attribute__((visibility))
  configure:24537: g++ -std=c++11 -o conftest -g -O2  -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0  -Wl,-headerpad_max_install_names conftest.cpp  >&5
  conftest.cpp:35:56: warning: target does not support 'protected' visibility; using 'default' [-Wunsupported-visibility]
                      int foo_pro( void ) __attribute__((visibility("protected")));
                                                         ^
  1 warning generated.
  configure:24537: $? = 0
  configure:24550: result: no
  ```

  This leads to `EXPORT_SYMBOL` being [defined to nothing](https://github.com/bitcoin/bitcoin/blob/f4de89edfa8be4501534fec0c662c650a4ce7ef2/src/script/bitcoinconsensus.h#L29), as `HAVE_FUNC_ATTRIBUTE_VISIBILITY` is not defined, and when building with reduced exports, you end up with a libbitcoinconsensus.dylib that doesn't export any  `_bitcoinconsensus_*` symbols.
  ```bash
  ➜  git:(master) nm -C src/.libs/libbitcoinconsensus.dylib | rg _bitcoinconsensus_
  ➜  git:(master)
  ```

  We do have a [second check](https://github.com/bitcoin/bitcoin/blob/f4de89edfa8be4501534fec0c662c650a4ce7ef2/configure.ac#L882) for the `visibility` attribute, which works for Darwin as it's only testing for default visibility, however the result of this check isn't used at all. It was added in #4725, along with the `--enable-reduce-exports` option, however when libbitcoinconsensus was added in #5235, it used the results of the added `AX_GCC_FUNC_ATTRIBUTE` calls.

  This PR removes our usage of the AX_GCC_FUNC_ATTRIBUTE macro entirely, in favour of our own checks in configure. This meant adding a check for `dllexport`, which I've tested as working with both [GCC](https://gcc.gnu.org/onlinedocs/gcc/Microsoft-Windows-Function-Attributes.html) and [Clang](https://releases.llvm.org/10.0.0/tools/clang/docs/AttributeReference.html#dllexport) when building for Windows. I haven't added an equivalent check for `dllimport`, as we weren't actually using the result of that check, we're just testing that `MSC_VER` was defined before using.

  With these changes building a libbitcoinconsensus with reduced exports, when targeting Darwin, works as expected:
  ```bash
  ./autogen.sh
  ./configure --disable-tests --disable-bench --with-utils=no --with-daemon=no --with-gui=no --disable-wallet --with-libs=yes --enable-reduce-exports
  make -j8
  ...
  nm -C src/.libs/libbitcoinconsensus.dylib | rg _bitcoinconsensus_
  000000000000a340 T _bitcoinconsensus_verify_script
  00000000000097e0 T _bitcoinconsensus_verify_script_with_amount
  000000000000a3c0 T _bitcoinconsensus_version
  ```

  ```python
  >>> import ctypes
  >>> consensus = ctypes.CDLL("src/.libs/libbitcoinconsensus.dylib")
  >>> print(consensus.bitcoinconsensus_version())
  1
  >>> exit()
  ```

  TODO: Modify a CI job to compile with --enable-reduce-exports and check for symbols in shared lib?

ACKs for top commit:
  laanwj:
    Code review ACK de4238f

Tree-SHA512: d148f3c55d14dac6e9e5b718cc65bb557bcf6f663218d24bc9044b86281bd5dd3d931ebea79c336a58e8ed50d683218c0a9e75494f2267b91097665043e252ae
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Feb 12, 2021
…ports for Darwin targets

de4238f build: consolidate reduced export checks (fanquake)
012bdec build: add building libconsensus to end-of-configure output (fanquake)
8f360e3 build: remove ax_gcc_func_attribute macro (fanquake)
f054a08 build: remove AX_GCC_FUNC_ATTRIBUTE test for dllimport (fanquake)
7cd0a69 build: test for __declspec(dllexport) in configure (fanquake)
1624e17 build: remove duplicate visibility attribute detection (fanquake)

Pull request description:

  Darwin targets do not have a `protected` visibility function attribute, see [LLVM explanation](https://github.com/llvm/llvm-project/blob/8e9a505139fbef7d2e6e9d0adfe1efc87326f9ef/clang/lib/Basic/Targets/OSTargets.h#L131). This means that the `AX_GCC_FUNC_ATTRIBUTE` check for `visibility` fails:
  ```bash
  configure:24513: checking for __attribute__((visibility))
  configure:24537: g++ -std=c++11 -o conftest -g -O2  -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0  -Wl,-headerpad_max_install_names conftest.cpp  >&5
  conftest.cpp:35:56: warning: target does not support 'protected' visibility; using 'default' [-Wunsupported-visibility]
                      int foo_pro( void ) __attribute__((visibility("protected")));
                                                         ^
  1 warning generated.
  configure:24537: $? = 0
  configure:24550: result: no
  ```

  This leads to `EXPORT_SYMBOL` being [defined to nothing](https://github.com/bitcoin/bitcoin/blob/f4de89edfa8be4501534fec0c662c650a4ce7ef2/src/script/bitcoinconsensus.h#L29), as `HAVE_FUNC_ATTRIBUTE_VISIBILITY` is not defined, and when building with reduced exports, you end up with a libbitcoinconsensus.dylib that doesn't export any  `_bitcoinconsensus_*` symbols.
  ```bash
  ➜  git:(master) nm -C src/.libs/libbitcoinconsensus.dylib | rg _bitcoinconsensus_
  ➜  git:(master)
  ```

  We do have a [second check](https://github.com/bitcoin/bitcoin/blob/f4de89edfa8be4501534fec0c662c650a4ce7ef2/configure.ac#L882) for the `visibility` attribute, which works for Darwin as it's only testing for default visibility, however the result of this check isn't used at all. It was added in bitcoin#4725, along with the `--enable-reduce-exports` option, however when libbitcoinconsensus was added in bitcoin#5235, it used the results of the added `AX_GCC_FUNC_ATTRIBUTE` calls.

  This PR removes our usage of the AX_GCC_FUNC_ATTRIBUTE macro entirely, in favour of our own checks in configure. This meant adding a check for `dllexport`, which I've tested as working with both [GCC](https://gcc.gnu.org/onlinedocs/gcc/Microsoft-Windows-Function-Attributes.html) and [Clang](https://releases.llvm.org/10.0.0/tools/clang/docs/AttributeReference.html#dllexport) when building for Windows. I haven't added an equivalent check for `dllimport`, as we weren't actually using the result of that check, we're just testing that `MSC_VER` was defined before using.

  With these changes building a libbitcoinconsensus with reduced exports, when targeting Darwin, works as expected:
  ```bash
  ./autogen.sh
  ./configure --disable-tests --disable-bench --with-utils=no --with-daemon=no --with-gui=no --disable-wallet --with-libs=yes --enable-reduce-exports
  make -j8
  ...
  nm -C src/.libs/libbitcoinconsensus.dylib | rg _bitcoinconsensus_
  000000000000a340 T _bitcoinconsensus_verify_script
  00000000000097e0 T _bitcoinconsensus_verify_script_with_amount
  000000000000a3c0 T _bitcoinconsensus_version
  ```

  ```python
  >>> import ctypes
  >>> consensus = ctypes.CDLL("src/.libs/libbitcoinconsensus.dylib")
  >>> print(consensus.bitcoinconsensus_version())
  1
  >>> exit()
  ```

  TODO: Modify a CI job to compile with --enable-reduce-exports and check for symbols in shared lib?

ACKs for top commit:
  laanwj:
    Code review ACK de4238f

Tree-SHA512: d148f3c55d14dac6e9e5b718cc65bb557bcf6f663218d24bc9044b86281bd5dd3d931ebea79c336a58e8ed50d683218c0a9e75494f2267b91097665043e252ae
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants