From 19cd9d584f7bd7868898ae7592633e2eb255eb46 Mon Sep 17 00:00:00 2001
From: Matt Whitlock <bitcoin@mattwhitlock.name>
Date: Sun, 17 Sep 2023 10:29:27 -0400
Subject: [PATCH] support linking against system-installed leveldb and
 libsecp256k1

- Abort if runtime leveldb != compiled-against leveldb.

Originally based on 22.0-fix_build_without_leveldb.patch.
---
 configure.ac              | 90 ++++++++++++++++++++++++++++++++++++++-
 src/Makefile.am           | 13 +++++-
 src/Makefile.test.include |  2 +
 src/dbwrapper.cpp         | 27 +++++++++++-
 src/dbwrapper.h           |  8 ++++
 src/kernel/checks.cpp     |  7 +++
 6 files changed, 143 insertions(+), 4 deletions(-)

diff --git a/configure.ac b/configure.ac
index 3bc6b13efc..290f289bf7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1141,6 +1141,41 @@ if test "$enable_fuzz_binary" = "yes"; then
      ]])])
 fi
 
+dnl Check for libsecp256k1, only if explicitly requested
+AC_ARG_WITH([system-libsecp256k1],
+  [AS_HELP_STRING([[--with-system-libsecp256k1[=PKG]]],
+    [build using system-installed libsecp256k1 instead of bundled, passing PKG (default: libsecp256k1) to pkg-config (default is no; DANGEROUS; NOT SUPPORTED)])],
+  [AS_IF([test "x$withval" = xyes], [with_system_libsecp256k1=libsecp256k1])],
+  [with_system_libsecp256k1=no])
+AM_CONDITIONAL([EMBEDDED_LIBSECP256K1],[test "x$with_system_libsecp256k1" = xno])
+AM_COND_IF([EMBEDDED_LIBSECP256K1], [
+  libsecp256k1_CFLAGS='-I$(srcdir)/secp256k1/include'
+  libsecp256k1_LIBS='secp256k1/libsecp256k1.la'
+], [
+  saved_CPPFLAGS=$CPPFLAGS
+  saved_LIBS=$LIBS
+  PKG_CHECK_MODULES([libsecp256k1], [$with_system_libsecp256k1])
+  CPPFLAGS="$libsecp256k1_CFLAGS $CPPFLAGS"
+  LIBS="$libsecp256k1_LIBS $LIBS"
+  missing_modules=
+  AC_DEFUN([CHECK_MODULE], [
+    AC_CHECK_HEADER([secp256k1_$1.h],
+      [AC_CHECK_FUNCS([$2], [], [missing_modules="${missing_modules} $1"])],
+      [missing_modules="${missing_modules} $1"])
+  ])
+  CHECK_MODULE([ellswift], [secp256k1_ellswift_encode])
+  CHECK_MODULE([extrakeys], [secp256k1_xonly_pubkey_parse])
+  CHECK_MODULE([recovery], [secp256k1_ecdsa_recover])
+  CHECK_MODULE([schnorrsig], [secp256k1_schnorrsig_verify])
+  AS_IF([test -n "${missing_modules}"], [
+    AC_MSG_ERROR([system-installed libsecp256k1 does not support these required modules:${missing_modules}])
+  ])
+  CPPFLAGS=$saved_CPPFLAGS
+  LIBS=$saved_LIBS
+])
+AC_SUBST(libsecp256k1_CFLAGS)
+AC_SUBST(libsecp256k1_LIBS)
+
 if test "$enable_wallet" != "no"; then
     dnl Check for libdb_cxx only if wallet enabled
     if test "$use_bdb" != "no"; then
@@ -1200,6 +1235,55 @@ if test "$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests" = "nononono"; th
   use_zmq=no
 fi
 
+dnl Check for leveldb, only if explicitly requested
+AC_ARG_WITH([system-leveldb],
+  [AS_HELP_STRING([--with-system-leveldb],
+    [Build with system LevelDB (default is no; DANGEROUS; NOT SUPPORTED)])],
+  [system_leveldb=$withval],
+  [system_leveldb=no])
+AC_ARG_VAR([leveldb_CFLAGS],[C compiler flags for system-leveldb])
+AC_ARG_VAR([leveldb_LIBS],[linker flags for system-leveldb])
+AS_IF([test x$system_leveldb != xno],[
+  TEMP_CPPFLAGS="$CPPFLAGS"
+  TEMP_LIBS="$LIBS"
+  CPPFLAGS="$leveldb_CFLAGS"
+  LIBS="$leveldb_LIBS"
+  AC_SEARCH_LIBS([leveldb_open],[leveldb],[leveldb_LIBS="$LIBS"],
+    [AC_MSG_ERROR([leveldb library not found; using --with-system-leveldb is not supported anyway])])
+  AC_CHECK_HEADER([leveldb/filter_policy.h],[],
+    [AC_MSG_ERROR([LevelDB headers not found; using --with-system-leveldb is not supported anyway])])
+  AC_CHECK_HEADER([leveldb/helpers/memenv.h],[],
+    [AC_MSG_ERROR([LevelDB headers not found; using --with-system-leveldb is not supported anyway])])
+
+  AC_MSG_CHECKING([for library containing leveldb::NewMemEnv])
+  for searchlib in "" "-lmemenv" ERR; do
+    if test "x$searchlib" = "xERR"; then
+      AC_MSG_RESULT([no])
+      AC_MSG_ERROR([LevelDB's memenv helper not found; using --with-system-leveldb is not supported anyway])
+    fi
+    LIBS="$searchlib $leveldb_LIBS"
+    AC_LINK_IFELSE([AC_LANG_PROGRAM([
+        #include <leveldb/env.h>
+        #include <leveldb/helpers/memenv.h>
+      ],[
+        leveldb::Env *myenv = leveldb::NewMemEnv(leveldb::Env::Default());
+        delete myenv;
+      ])
+    ],[
+      AC_MSG_RESULT([$searchlib])
+      break
+    ])
+  done
+  leveldb_LIBS="$LIBS"
+  LIBS="$TEMP_LIBS"
+  CPPFLAGS="$TEMP_CPPFLAGS"
+],[
+  AC_DEFINE([EMBEDDED_LEVELDB],[1],[Define to use the bundled LevelDB sources])
+])
+AM_CONDITIONAL([EMBEDDED_LEVELDB],[test x$system_leveldb = xno])
+AC_SUBST(leveldb_CFLAGS)
+AC_SUBST(leveldb_LIBS)
+
 dnl Check for libminiupnpc (optional)
 if test "$use_upnp" != "no"; then
   TEMP_CPPFLAGS="$CPPFLAGS"
@@ -1693,8 +1777,10 @@ CPPFLAGS="$CPPFLAGS_TEMP"
 if test -n "$use_sanitizers"; then
   export SECP_CFLAGS="$SECP_CFLAGS $SANITIZER_CFLAGS"
 fi
-ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --disable-module-ecdh"
-AC_CONFIG_SUBDIRS([src/secp256k1])
+AM_COND_IF([EMBEDDED_LIBSECP256K1],[
+  ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --disable-module-ecdh"
+  AC_CONFIG_SUBDIRS([src/secp256k1])
+])
 
 AC_OUTPUT
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 1ccb5332c4..04e9d54962 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,7 +25,7 @@ check_PROGRAMS =
 TESTS =
 BENCHMARKS =
 
-BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT)
+BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) $(libsecp256k1_CFLAGS) -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT)
 
 LIBBITCOIN_NODE=libbitcoin_node.a
 LIBBITCOIN_COMMON=libbitcoin_common.a
@@ -34,7 +34,11 @@ LIBBITCOIN_CLI=libbitcoin_cli.a
 LIBBITCOIN_UTIL=libbitcoin_util.a
 LIBBITCOIN_CRYPTO_BASE=crypto/libbitcoin_crypto_base.la
 LIBBITCOINQT=qt/libbitcoinqt.a
+if EMBEDDED_LIBSECP256K1
 LIBSECP256K1=secp256k1/libsecp256k1.la
+else
+LIBSECP256K1=$(libsecp256k1_LIBS)
+endif
 
 if ENABLE_ZMQ
 LIBBITCOIN_ZMQ=libbitcoin_zmq.a
@@ -66,8 +70,10 @@ LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_ARM_SHANI)
 endif
 noinst_LTLIBRARIES += $(LIBBITCOIN_CRYPTO)
 
+if EMBEDDED_LIBSECP256K1
 $(LIBSECP256K1): $(wildcard secp256k1/src/*.h) $(wildcard secp256k1/src/*.c) $(wildcard secp256k1/include/*)
 	$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F)
+endif
 
 # Make is not made aware of per-object dependencies to avoid limiting building parallelization
 # But to build the less dependent modules first, we manually select their order here:
@@ -1110,8 +1116,13 @@ endif
 
 include Makefile.minisketch.include
 
+if EMBEDDED_LEVELDB
 include Makefile.crc32c.include
 include Makefile.leveldb.include
+else
+LEVELDB_CPPFLAGS = $(leveldb_CFLAGS)
+LIBLEVELDB = $(leveldb_LIBS)
+endif
 
 include Makefile.test_util.include
 include Makefile.test_fuzz.include
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index c396cc2ebf..233f1a1c21 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -449,7 +449,9 @@ if ENABLE_BENCH
 	$(BENCH_BINARY) -sanity-check -priority-level=high
 endif
 endif
+if EMBEDDED_LIBSECP256K1
 	$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check
+endif
 
 if ENABLE_TESTS
 UNIVALUE_TESTS = univalue/test/object univalue/test/unitester
diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp
index 479064d468..b476b42c85 100644
--- a/src/dbwrapper.cpp
+++ b/src/dbwrapper.cpp
@@ -22,7 +22,11 @@
 #include <leveldb/db.h>
 #include <leveldb/env.h>
 #include <leveldb/filter_policy.h>
-#include <leveldb/helpers/memenv/memenv.h>
+#if EMBEDDED_LEVELDB
+# include <leveldb/helpers/memenv/memenv.h>
+#else
+# include <leveldb/helpers/memenv.h>
+#endif
 #include <leveldb/iterator.h>
 #include <leveldb/options.h>
 #include <leveldb/slice.h>
@@ -51,6 +55,27 @@ static void HandleError(const leveldb::Status& status)
     throw dbwrapper_error(errmsg);
 }
 
+#if !EMBEDDED_LEVELDB
+#include <node/interface_ui.h>
+#include <util/translation.h>
+#include <leveldb/c.h>
+bool dbwrapper_SanityCheck()
+{
+    unsigned long header_version = (leveldb::kMajorVersion << 16) | leveldb::kMinorVersion;
+    unsigned long library_version = (leveldb_major_version() << 16) | leveldb_minor_version();
+
+    if (header_version != library_version) {
+        InitError(Untranslated(strprintf("Compiled with LevelDB %d.%d, but linked with LevelDB %d.%d (incompatible).",
+            leveldb::kMajorVersion, leveldb::kMinorVersion,
+            leveldb_major_version(), leveldb_minor_version()
+        )));
+        return false;
+    }
+
+    return true;
+}
+#endif
+
 class CBitcoinLevelDBLogger : public leveldb::Logger {
 public:
     // This code is adapted from posix_logger.h, which is why it is using vsprintf.
diff --git a/src/dbwrapper.h b/src/dbwrapper.h
index 63c2f99d2a..406d03f1ea 100644
--- a/src/dbwrapper.h
+++ b/src/dbwrapper.h
@@ -5,6 +5,10 @@
 #ifndef BITCOIN_DBWRAPPER_H
 #define BITCOIN_DBWRAPPER_H
 
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
 #include <attributes.h>
 #include <serialize.h>
 #include <span.h>
@@ -46,6 +50,10 @@ struct DBParams {
     DBOptions options{};
 };
 
+#if !EMBEDDED_LEVELDB
+bool dbwrapper_SanityCheck();
+#endif
+
 class dbwrapper_error : public std::runtime_error
 {
 public:
diff --git a/src/kernel/checks.cpp b/src/kernel/checks.cpp
index e4a13ee4cc..2ca51437bf 100644
--- a/src/kernel/checks.cpp
+++ b/src/kernel/checks.cpp
@@ -4,6 +4,7 @@
 
 #include <kernel/checks.h>
 
+#include <dbwrapper.h>
 #include <random.h>
 #include <util/result.h>
 #include <util/translation.h>
@@ -14,6 +15,12 @@ namespace kernel {
 
 util::Result<void> SanityChecks(const Context&)
 {
+#if !EMBEDDED_LEVELDB
+    if (!dbwrapper_SanityCheck()) {
+        return util::Error{Untranslated("Database sanity check failure. Aborting.")};
+    }
+#endif
+
     if (!Random_SanityCheck()) {
         return util::Error{Untranslated("OS cryptographic RNG sanity check failure. Aborting.")};
     }
-- 
2.46.0

