Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 84 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -65,4 +88,62 @@ application {
}

// Ensure proper build order
compileJava.dependsOn compileBitcoinCore
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
26 changes: 16 additions & 10 deletions src/main/java/org/bitcoinkernel/KernelData.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/bitcoinkernel/Transactions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 19 additions & 2 deletions src/main/java/org/bitcoinkernel/jextract/bitcoinkernel_h.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand Down
Loading