Update tests and batteries for new 0.0.104 API, fix Java 18 build
[ldk-java] / src / test / java / org / ldk / HumanObjectPeerTest.java
index eb3d43f2d08667770461f0081d330608c97129c9..948544b6f7868391055a0eb46352004aa2731ad6 100644 (file)
@@ -7,6 +7,7 @@ import org.ldk.batteries.ChannelManagerConstructor;
 import org.ldk.batteries.NioPeerHandler;
 import org.ldk.enums.AccessError;
 import org.ldk.enums.Currency;
+import org.ldk.enums.Level;
 import org.ldk.enums.Network;
 import org.ldk.structs.*;
 import org.ldk.util.TwoTuple;
@@ -122,6 +123,11 @@ class HumanObjectPeerTestInstance {
                 public Result_RecoverableSignatureNoneZ sign_invoice(byte[] invoice_preimage) {
                     return underlying_if.sign_invoice(invoice_preimage);
                 }
+
+                @Override
+                public byte[] get_inbound_payment_key_material() {
+                    return underlying_if.get_inbound_payment_key_material();
+                }
             });
         }
 
@@ -138,8 +144,8 @@ class HumanObjectPeerTestInstance {
                     synchronized (monitors) {
                         String txid = Arrays.toString(funding_txo.get_txid());
                         assert monitors.containsKey(txid);
-                        Result_NoneMonitorUpdateErrorZ update_res = monitors.get(txid).update_monitor(update, tx_broadcaster, fee_estimator, logger);
-                        assert update_res instanceof Result_NoneMonitorUpdateErrorZ.Result_NoneMonitorUpdateErrorZ_OK;
+                        Result_NoneNoneZ update_res = monitors.get(txid).update_monitor(update, tx_broadcaster, fee_estimator, logger);
+                        assert update_res instanceof Result_NoneNoneZ.Result_NoneNoneZ_OK;
                     }
                     return Result_NoneChannelMonitorUpdateErrZ.ok();
                 }
@@ -189,19 +195,28 @@ class HumanObjectPeerTestInstance {
         InvoicePayer payer = null;
         GcCheck obj = new GcCheck();
 
-        private TwoTuple_OutPointScriptZ test_mon_roundtrip(ChannelMonitor mon) {
+        private ChannelMonitor test_mon_roundtrip(OutPoint expected_id, byte[] data) {
             // Because get_funding_txo() returns an OutPoint in a tuple that is a reference to an OutPoint inside the
             // ChannelMonitor, its a good test to ensure that the OutPoint isn't freed (or is cloned) before the
             // ChannelMonitor is. This used to be broken.
-            Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ roundtrip_monitor = UtilMethods.C2Tuple_BlockHashChannelMonitorZ_read(mon.write(), keys_interface);
+            Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ roundtrip_monitor = UtilMethods.C2Tuple_BlockHashChannelMonitorZ_read(data, keys_interface);
             assert roundtrip_monitor instanceof Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ.Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ_OK;
             TwoTuple_OutPointScriptZ funding_txo = ((Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ.Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ_OK) roundtrip_monitor).res.get_b().get_funding_txo();
             System.gc(); System.runFinalization(); // Give the GC a chance to run.
-            return funding_txo;
+            assert Arrays.equals(funding_txo.get_a().get_txid(), expected_id.get_txid());
+            assert funding_txo.get_a().get_index() == expected_id.get_index();
+            Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ roundtrip_two = UtilMethods.C2Tuple_BlockHashChannelMonitorZ_read(data, keys_interface);
+            assert roundtrip_two instanceof Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ.Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ_OK;
+            return ((Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ.Result_C2Tuple_BlockHashChannelMonitorZDecodeErrorZ_OK) roundtrip_two).res.get_b();
         }
 
         private Peer(Object _dummy, byte seed) {
-            logger = Logger.new_impl((String arg) -> System.out.println(seed + ": " + arg));
+            logger = Logger.new_impl((org.ldk.structs.Record arg)->{
+                if (arg.get_level() == Level.LDKLevel_Error)
+                    System.err.println(seed + ": " + arg.get_module_path() + " - " + arg.get_args());
+                else
+                    System.out.println(seed + ": " + arg.get_module_path() + " - " + arg.get_args());
+            });
             fee_estimator = FeeEstimator.new_impl((confirmation_target -> 253));
             tx_broadcaster = BroadcasterInterface.new_impl(tx -> {
                 synchronized (broadcast_set) {
@@ -216,10 +231,8 @@ class HumanObjectPeerTestInstance {
                 public Result_NoneChannelMonitorUpdateErrZ persist_new_channel(OutPoint id, ChannelMonitor data, MonitorUpdateId update_id) {
                     synchronized (monitors) {
                         String key = Arrays.toString(id.to_channel_id());
-                        assert monitors.put(key, data) == null;
-                        TwoTuple_OutPointScriptZ res = test_mon_roundtrip(data);
-                        assert Arrays.equals(res.get_a().get_txid(), id.get_txid());
-                        assert res.get_a().get_index() == id.get_index();
+                        ChannelMonitor res = test_mon_roundtrip(id, data.write());
+                        assert monitors.put(key, res) == null;
                     }
                     return Result_NoneChannelMonitorUpdateErrZ.ok();
                 }
@@ -228,10 +241,10 @@ class HumanObjectPeerTestInstance {
                 public Result_NoneChannelMonitorUpdateErrZ update_persisted_channel(OutPoint id, ChannelMonitorUpdate update, ChannelMonitor data, MonitorUpdateId update_id) {
                     synchronized (monitors) {
                         String key = Arrays.toString(id.to_channel_id());
-                        assert monitors.put(key, data) != null;
-                        TwoTuple_OutPointScriptZ res = test_mon_roundtrip(data);
-                        assert Arrays.equals(res.get_a().get_txid(), id.get_txid());
-                        assert res.get_a().get_index() == id.get_index();
+                        ChannelMonitor res = test_mon_roundtrip(id, data.write());
+                        // Note that we use a serialization-roundtrip copy of data here, not the original, as this can
+                        // expose the JVM JIT bug where it finalize()s things still being called.
+                        assert monitors.put(key, res) != null;
                     }
                     return Result_NoneChannelMonitorUpdateErrZ.ok();
                 }
@@ -345,8 +358,8 @@ class HumanObjectPeerTestInstance {
                     this.constructor = new ChannelManagerConstructor(Network.LDKNetwork_Bitcoin, UserConfig.with_default(), new byte[32], 0,
                                                        this.keys_interface, this.fee_estimator, this.chain_monitor, this.router, this.tx_broadcaster, this.logger);
                 }
-                LockableScore scorer = null;
-                if (use_invoice_payer) { scorer = LockableScore.of(Scorer.with_default().as_Score()); }
+                MultiThreadedLockableScore scorer = null;
+                if (use_invoice_payer) { scorer = MultiThreadedLockableScore.of(Scorer.with_default().as_Score()); }
                 constructor.chain_sync_completed(new ChannelManagerConstructor.EventHandler() {
                     @Override public void handle_event(Event event) {
                         synchronized (pending_manager_events) {
@@ -368,12 +381,13 @@ class HumanObjectPeerTestInstance {
                 if (use_invoice_payer) {
                     this.payer = InvoicePayer.of(this.chan_manager.as_Payer(), Router.new_impl(new Router.RouterInterface() {
                         @Override
-                        public Result_RouteLightningErrorZ find_route(byte[] payer, RouteParameters params, ChannelDetails[] first_hops, Score scorer) {
+                        public Result_RouteLightningErrorZ find_route(byte[] payer, RouteParameters params, byte[] payment_hash, ChannelDetails[] first_hops, Score scorer) {
                             return UtilMethods.find_route(payer, params, router, first_hops, logger, scorer);
                         }
-                    }), LockableScore.of(Score.new_impl(new Score.ScoreInterface() {
+                    }), MultiThreadedLockableScore.of(Score.new_impl(new Score.ScoreInterface() {
                         @Override public void payment_path_failed(RouteHop[] path, long scid) {}
-                        @Override public long channel_penalty_msat(long scid, NodeId src, NodeId dst) { return 0; }
+                        @Override public long channel_penalty_msat(long short_channel_id, long send_amt_msat, Option_u64Z channel_capacity_msat, NodeId source, NodeId target) { return 0; }
+                        @Override public void payment_path_successful(RouteHop[] path) {}
                         @Override public byte[] write() { assert false; return null; }
                     })), logger, EventHandler.new_impl(new EventHandler.EventHandlerInterface() {
                         @Override public void handle_event(Event event) {
@@ -411,9 +425,19 @@ class HumanObjectPeerTestInstance {
                         this.constructor = new ChannelManagerConstructor(serialized, monitors, UserConfig.with_default(),
                                 this.keys_interface, this.fee_estimator, this.chain_monitor, filter_nullable,
                                 this.router, this.tx_broadcaster, this.logger);
+                        try {
+                            // Test that ChannelManagerConstructor correctly rejects duplicate ChannelMonitors
+                            byte[][] monitors_dupd = new byte[2][];
+                            monitors_dupd[0] = monitors[0];
+                            monitors_dupd[1] = monitors[0];
+                            ChannelManagerConstructor constr = this.constructor = new ChannelManagerConstructor(serialized, monitors_dupd, UserConfig.with_default(),
+                                    this.keys_interface, this.fee_estimator, this.chain_monitor, filter_nullable,
+                                    null, this.tx_broadcaster, this.logger);
+                            assert false;
+                        } catch (ChannelManagerConstructor.InvalidSerializedDataException e) {}
                     }
-                    LockableScore scorer = null;
-                    if (use_invoice_payer) { scorer = LockableScore.of(Scorer.with_default().as_Score()); }
+                    MultiThreadedLockableScore scorer = null;
+                    if (use_invoice_payer) { scorer = MultiThreadedLockableScore.of(Scorer.with_default().as_Score()); }
                     constructor.chain_sync_completed(new ChannelManagerConstructor.EventHandler() {
                         @Override public void handle_event(Event event) {
                             synchronized (pending_manager_events) {
@@ -466,12 +490,13 @@ class HumanObjectPeerTestInstance {
                 if (use_invoice_payer) {
                     this.payer = InvoicePayer.of(this.chan_manager.as_Payer(), Router.new_impl(new Router.RouterInterface() {
                         @Override
-                        public Result_RouteLightningErrorZ find_route(byte[] payer, RouteParameters params, ChannelDetails[] first_hops, Score scorer) {
+                        public Result_RouteLightningErrorZ find_route(byte[] payer, RouteParameters params, byte[] _payment_hash, ChannelDetails[] first_hops, Score scorer) {
                             return UtilMethods.find_route(payer, params, router, first_hops, logger, scorer);
                         }
-                    }), LockableScore.of(Score.new_impl(new Score.ScoreInterface() {
+                    }), MultiThreadedLockableScore.of(Score.new_impl(new Score.ScoreInterface() {
+                        @Override public long channel_penalty_msat(long short_channel_id, long send_amt_msat, Option_u64Z channel_capacity_msat, NodeId source, NodeId target) { return 0; }
                         @Override public void payment_path_failed(RouteHop[] path, long scid) {}
-                        @Override public long channel_penalty_msat(long scid, NodeId src, NodeId dst) { return 0; }
+                        @Override public void payment_path_successful(RouteHop[] path) {}
                         @Override public byte[] write() { assert false; return null; }
                     })), logger, EventHandler.new_impl(new EventHandler.EventHandlerInterface() {
                         @Override public void handle_event(Event event) {
@@ -575,9 +600,11 @@ class HumanObjectPeerTestInstance {
             if (use_chan_manager_constructor) {
                 while (res.length < expected_len) {
                     synchronized (this.pending_manager_events) {
-                        res = this.pending_manager_events.toArray(res);
-                        assert res.length == expected_len || res.length == 0; // We don't handle partial results
-                        this.pending_manager_events.clear();
+                        if (this.pending_manager_events.size() >= expected_len) {
+                            res = this.pending_manager_events.toArray(res);
+                            assert res.length == expected_len;
+                            this.pending_manager_events.clear();
+                        }
                         if (res.length < expected_len) {
                             try { this.pending_manager_events.wait(); } catch (InterruptedException e) { assert false; }
                         }
@@ -592,7 +619,6 @@ class HumanObjectPeerTestInstance {
                         peer2.nio_peer_handler.check_events();
                     }
                     chan_manager.as_EventsProvider().process_pending_events(EventHandler.new_impl(l::add));
-                    assert l.size() == expected_len || l.size() == 0; // We don't handle partial results
                 }
                 return l.toArray(new Event[0]);
             }
@@ -882,9 +908,11 @@ class HumanObjectPeerTestInstance {
         assert !Arrays.equals(payment_preimage, new byte[32]);
         state.peer2.chan_manager.claim_funds(payment_preimage);
 
-        events = state.peer1.get_manager_events(1, state.peer1, state.peer2);
+        events = state.peer1.get_manager_events(2, state.peer1, state.peer2);
         assert events[0] instanceof Event.PaymentSent;
         assert Arrays.equals(((Event.PaymentSent) events[0]).payment_preimage, payment_preimage);
+        assert events[1] instanceof Event.PaymentPathSuccessful;
+        assert Arrays.equals(((Event.PaymentPathSuccessful) events[1]).payment_hash, ((Event.PaymentSent) events[0]).payment_hash);
 
         if (use_nio_peer_handler) {
             // We receive PaymentSent immediately upon receipt of the payment preimage, but we expect to not have an
@@ -1025,6 +1053,8 @@ class HumanObjectPeerTestInstance {
 
         t.interrupt();
 
+        state.peer1.router.write();
+
         // Construct the only Option_Enum::Variant(OpaqueStruct) we have in the codebase as this used to cause double-frees:
         byte[] serd = new byte[] {(byte)0xd9,(byte)0x77,(byte)0xcb,(byte)0x9b,(byte)0x53,(byte)0xd9,(byte)0x3a,(byte)0x6f,(byte)0xf6,(byte)0x4b,(byte)0xb5,(byte)0xf1,(byte)0xe1,(byte)0x58,(byte)0xb4,(byte)0x09,(byte)0x4b,(byte)0x66,(byte)0xe7,(byte)0x98,(byte)0xfb,(byte)0x12,(byte)0x91,(byte)0x11,(byte)0x68,(byte)0xa3,(byte)0xcc,(byte)0xdf,(byte)0x80,(byte)0xa8,(byte)0x30,(byte)0x96,(byte)0x34,(byte)0x0a,(byte)0x6a,(byte)0x95,(byte)0xda,(byte)0x0a,(byte)0xe8,(byte)0xd9,(byte)0xf7,(byte)0x76,(byte)0x52,(byte)0x8e,(byte)0xec,(byte)0xdb,(byte)0xb7,(byte)0x47,(byte)0xeb,(byte)0x6b,(byte)0x54,(byte)0x54,(byte)0x95,(byte)0xa4,(byte)0x31,(byte)0x9e,(byte)0xd5,(byte)0x37,(byte)0x8e,(byte)0x35,(byte)0xb2,(byte)0x1e,(byte)0x07,(byte)0x3a,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x19,(byte)0xd6,(byte)0x68,(byte)0x9c,(byte)0x08,(byte)0x5a,(byte)0xe1,(byte)0x65,(byte)0x83,(byte)0x1e,(byte)0x93,(byte)0x4f,(byte)0xf7,(byte)0x63,(byte)0xae,(byte)0x46,(byte)0xa2,(byte)0xa6,(byte)0xc1,(byte)0x72,(byte)0xb3,(byte)0xf1,(byte)0xb6,(byte)0x0a,(byte)0x8c,(byte)0xe2,(byte)0x6f,(byte)0x00,(byte)0x08,(byte)0x3a,(byte)0x84,(byte)0x00,(byte)0x00,(byte)0x03,(byte)0x4d,(byte)0x01,(byte)0x34,(byte)0x13,(byte)0xa7,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x90,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x0f,(byte)0x42,(byte)0x40,(byte)0x00,(byte)0x00,(byte)0x27,(byte)0x10,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x14,};
         Result_ChannelUpdateDecodeErrorZ upd_msg = ChannelUpdate.read(serd);
@@ -1062,6 +1092,14 @@ public class HumanObjectPeerTest {
     }
     @Test
     public void test_message_handler() throws InterruptedException {
+        Thread gc_thread = new Thread(() -> {
+            while (true) {
+                System.gc();
+                System.runFinalization();
+                try { Thread.sleep(0, 1); } catch (InterruptedException e) { break; }
+            }
+        }, "Test GC Thread");
+        gc_thread.start();
         for (int i = 0; i < (1 << 9) - 1; i++) {
             boolean nice_close =                   (i & (1 << 0)) != 0;
             boolean use_km_wrapper =               (i & (1 << 1)) != 0;
@@ -1094,6 +1132,8 @@ public class HumanObjectPeerTest {
             System.err.println("Running test with flags " + i);
             do_test(nice_close, use_km_wrapper, use_manual_watch, reload_peers, break_cross_refs, nio_peer_handler, use_ignoring_routing_handler, use_chan_manager_constructor, use_invoice_payer);
         }
+        gc_thread.interrupt();
+        gc_thread.join();
     }
 
     // This is used in the test jar to test the built jar is runnable