diff --git a/build.gradle b/build.gradle index 702f222..6373f0a 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,15 @@ plugins { id 'application' } +dependencies { + // JSON processing + implementation 'com.google.code.gson:gson:2.10.1' + + // Testing + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + java { toolchain { languageVersion = JavaLanguageVersion.of(24) @@ -22,10 +31,24 @@ def bitcoinKernelHeader = file("${bitcoinCoreDir}/src/kernel/bitcoinkernel.h") tasks.register('buildBitcoinCore', Exec) { workingDir bitcoinCoreDir - // Configure Bitcoin Core build + // Configure Bitcoin Core build (matching rust-bitcoinkernel approach) commandLine 'cmake', '-B', 'build', + '-DCMAKE_BUILD_TYPE=RelWithDebInfo', '-DBUILD_KERNEL_LIB=ON', - '-DBUILD_UTIL_CHAINSTATE=ON' + '-DBUILD_TESTS=OFF', + '-DBUILD_KERNEL_TEST=OFF', + '-DBUILD_TX=OFF', + '-DBUILD_WALLET_TOOL=OFF', + '-DENABLE_WALLET=OFF', + '-DENABLE_EXTERNAL_SIGNER=OFF', + '-DBUILD_UTIL=OFF', + '-DBUILD_BITCOIN_BIN=OFF', + '-DBUILD_DAEMON=OFF', + '-DBUILD_UTIL_CHAINSTATE=OFF', + '-DBUILD_CLI=OFF', + '-DBUILD_SHARED_LIBS=ON', + '-DCMAKE_INSTALL_LIBDIR=lib', + '-DENABLE_IPC=OFF' // Only run if build directory doesn't exist or header has changed // inputs.file bitcoinKernelHeader @@ -65,4 +88,62 @@ application { } // Ensure proper build order -compileJava.dependsOn compileBitcoinCore \ No newline at end of file +compileJava.dependsOn compileBitcoinCore + +// Configure test task +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "full" + } +} + +// Task to run conformance handler +tasks.register('conformanceHandler', Exec) { + group = 'conformance' + description = 'Run the conformance test handler (reads from stdin, writes to stdout)' + + classpath = sourceSets.main.runtimeClasspath + mainClass = 'org.bitcoinkernel.conformance.ConformanceTestHandler' + + // Required for FFM + jvmArgs = ['--enable-native-access=ALL-UNNAMED'] + + // Connect stdin/stdout + standardInput = System.in + standardOutput = System.out + standardError = System.err +} + +// Build uber JAR with all dependencies +tasks.register('buildConformanceJar', Jar) { + group = 'conformance' + description = 'Build standalone conformance handler JAR with all dependencies' + + archiveBaseName = 'java-bitcoinkernel-conformance-handler' + archiveVersion = '0.1.0' + + from sourceSets.main.output + + // Include all dependencies + from { + configurations.runtimeClasspath.collect { + it.isDirectory() ? it : zipTree(it) + } + } + + // Avoid duplicate files + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + manifest { + attributes( + 'Main-Class': 'org.bitcoinkernel.conformance.ConformanceTestHandler', + 'Implementation-Title': 'Java Bitcoin Kernel Conformance Handler', + 'Implementation-Version': archiveVersion.get() + ) + } +} + +// Make conformance JAR depend on compilation +buildConformanceJar.dependsOn compileJava diff --git a/src/main/java/org/bitcoinkernel/KernelData.java b/src/main/java/org/bitcoinkernel/KernelData.java index 74a2863..2f20aad 100644 --- a/src/main/java/org/bitcoinkernel/KernelData.java +++ b/src/main/java/org/bitcoinkernel/KernelData.java @@ -49,14 +49,19 @@ public int verify(long amount, Transaction txTo, TransactionOutput[] spentOutput txTo.checkClosed(); try (var arena = Arena.ofConfined()) { - // Allocate array of output pointers - MemorySegment outputPtrs = arena.allocate( - ValueLayout.ADDRESS, - spentOutputs.length - ); - - for (int i = 0; i < spentOutputs.length; i++) { - outputPtrs.setAtIndex(ValueLayout.ADDRESS, i, spentOutputs[i].getInner()); + // Prepare spent outputs array + int numOutputs = (spentOutputs != null) ? spentOutputs.length : 0; + MemorySegment outputPtrs; + + if (numOutputs > 0) { + // Allocate array of output pointers + outputPtrs = arena.allocate(ValueLayout.ADDRESS, numOutputs); + for (int i = 0; i < spentOutputs.length; i++) { + outputPtrs.setAtIndex(ValueLayout.ADDRESS, i, spentOutputs[i].getInner()); + } + } else { + // Pass NULL for empty/null spent outputs + outputPtrs = MemorySegment.NULL; } MemorySegment statusPtr = arena.allocate(ValueLayout.JAVA_BYTE); @@ -66,13 +71,14 @@ public int verify(long amount, Transaction txTo, TransactionOutput[] spentOutput amount, txTo.getInner(), outputPtrs, - spentOutputs.length, + numOutputs, inputIndex, flags, statusPtr ); - if (result != 0) { + // Note: return value 1 = success, 0 = error + if (result == 0) { byte status = statusPtr.get(ValueLayout.JAVA_BYTE, 0); throw new KernelTypes.KernelException( KernelTypes.KernelException.ScriptVerifyError.fromNative(status) diff --git a/src/main/java/org/bitcoinkernel/Transactions.java b/src/main/java/org/bitcoinkernel/Transactions.java index 07d24bc..78f77f0 100644 --- a/src/main/java/org/bitcoinkernel/Transactions.java +++ b/src/main/java/org/bitcoinkernel/Transactions.java @@ -84,7 +84,7 @@ MemorySegment getInner() { } @Override - public void close() throws Exception { + public void close() { if (inner != MemorySegment.NULL) { btck_transaction_destroy(inner); inner = MemorySegment.NULL; @@ -264,7 +264,7 @@ MemorySegment getInner() { } @Override - public void close() throws Exception { + public void close() { if (inner != MemorySegment.NULL && ownsMemory) { btck_transaction_output_destroy(inner); inner = MemorySegment.NULL; diff --git a/src/main/java/org/bitcoinkernel/jextract/bitcoinkernel_h.java b/src/main/java/org/bitcoinkernel/jextract/bitcoinkernel_h.java index 9c48d0b..33d361e 100644 --- a/src/main/java/org/bitcoinkernel/jextract/bitcoinkernel_h.java +++ b/src/main/java/org/bitcoinkernel/jextract/bitcoinkernel_h.java @@ -20,8 +20,25 @@ public class bitcoinkernel_h extends bitcoinkernel_h$shared { static final Arena LIBRARY_ARENA = Arena.ofAuto(); - static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup() - .or(Linker.nativeLinker().defaultLookup()); + static final SymbolLookup SYMBOL_LOOKUP; + + static { + // Load the bitcoinkernel library + SymbolLookup lookup = null; + try { + System.loadLibrary("bitcoinkernel"); + lookup = SymbolLookup.loaderLookup(); + } catch (UnsatisfiedLinkError e) { + // Fall back to default lookup (LD_LIBRARY_PATH) + System.err.println("Warning: Could not load libbitcoinkernel via System.loadLibrary, trying LD_LIBRARY_PATH"); + } + + if (lookup == null) { + lookup = Linker.nativeLinker().defaultLookup(); + } + + SYMBOL_LOOKUP = lookup; + } private static final int __WORDSIZE = (int)64L; /**