shashank created CAMEL-25730:
--------------------------------

             Summary: camel-web3j - the consumer never subscribes again after 
its subscription ends with an error, so one failed request to the node stops 
BLOCK_OBSERVABLE and TRANSACTION_OBSERVABLE while the route stays started
                 Key: CAMEL-25730
                 URL: https://issues.apache.org/jira/browse/CAMEL-25730
             Project: Camel
          Issue Type: Bug
          Components: camel-web3j
            Reporter: shashank
            Assignee: shashank


{{Web3jConsumer.doStart}} (main 55ed20f14e90, lines 55-128) subscribes once to 
a web3j {{Flowable}}, with {{processError}} as the error callback. An RxJava 
{{Flowable}} that signals an error is over, and nothing subscribes again: 
{{processError}} (lines 184-189) routes one exchange with the exception, and 
from then on the consumer receives nothing while the route stays started.

In web3j 5.0.0 several of these flowables make a request to the node for each 
item, inside a {{flatMap}}:
* {{blockFlowable}} = {{ethBlockHashFlowable(...).flatMap(hash -> 
web3j.ethGetBlockByHash(hash, full).flowable())}} 
({{JsonRpc2_0Rx.lambda$blockFlowable$6}});
* {{transactionFlowable}} = {{blockFlowable(true, ...).flatMapIterable(...)}};
* {{pendingTransactionFlowable}} = 
{{ethPendingTransactionHashFlowable(...).flatMap(hash -> 
ethGetTransactionByHash(hash).flowable())}}.
{{Request.flowable()}} is a {{RemoteCall}} over {{Request.send()}}, which 
throws the {{IOException}} of a failed request. So with 
{{operation=BLOCK_OBSERVABLE}}, {{TRANSACTION_OBSERVABLE}} or 
{{PENDING_TRANSACTION_OBSERVABLE}}, a single failed request (a connection 
reset, a node restart, a timeout of the HTTP client) ends the consumer for 
good. The block hash filter itself survives such failures (web3j's {{Filter}} 
catches the {{IOException}} of its polls), so the stop depends on which request 
fails. For every operation, a node that cannot be reached when the route starts 
has the same effect.

The consumer is a long-running listener; a node that is briefly unavailable is 
normal. After the one routed error the blocks and transactions are lost without 
further notice, and only a route restart brings the consumer back.

h3. Reproduction

New {{Web3jConsumerResubscribeTest}} (mocked {{Web3j}}, no node):
* {{blockSubscriptionEndedByAFailedRequestIsSubscribedAgain}}: 
{{blockFlowable}} emits a block, then fails with {{IOException("Connection 
reset")}}; a second subscription emits a block. On main the second block is 
never received ({{mock://result Received message count. Expected: <2> but was: 
<1>}}).
* {{failuresWhileSubscribingAgainAreRoutedOnce}}: three failed subscriptions, 
then one that emits a block ({{Expected: <1> but was: <0>}}).
* {{logFilterFromTheLatestBlockIsSubscribedAgain}}: {{ETH_LOG_OBSERVABLE}} 
whose filter cannot be installed at start ({{Expected: <1> but was: <0>}}).
* {{subscriptionMadeWhileTheConsumerStopsIsDisposed}}: the route is stopped 
while the consumer subscribes again (a slow request to the node); the new 
subscription must be disposed.
The committed tests set a 10 ms retry delay through a package-private setter 
that main does not have. Run against unmodified main, a copy of the tests 
without that setter call and with a 25 s wait fails the same three ({{Expected: 
<2> but was: <1>}}, {{Expected: <1> but was: <0>}}, {{Expected: <1> but was: 
<0>}}); with the fix the same copy passes with the default 15 s delay. Two more 
tests pass on main and with the fix; they guard what must not be subscribed 
again: a replay of a block range, and a log filter from a given block number. 
The stop test needs the fix (it waits for the second subscription); removing 
only the dispose after a subscription made while stopping makes it fail.

h3. Proposed fix

After a live subscription ended with an error, the consumer subscribes again 
every 15 seconds until it succeeds, on a scheduled executor created in 
{{doStart}} and shut down in {{doStop}}. The first error is routed as before 
and logged once at WARN without a stack trace; the attempts and their failures 
are logged at DEBUG until an item is received again, which is logged once at 
INFO. A subscription that is made while the consumer stops (the executor 
shutdown does not wait for a running attempt) is disposed. 
{{REPLAY_BLOCKS_OBSERVABLE}}, {{REPLAY_TRANSACTIONS_OBSERVABLE}} and an 
{{ETH_LOG_OBSERVABLE}} with {{fromBlock}} set to a block number are not 
subscribed again, as that would route the same blocks or logs again. A new 
subscription starts at the latest block, so the blocks between the error and 
the new subscription are not routed; this is stated in the upgrade note (4.24 
guide).

With the fix the camel-web3j tests pass: 138 run, 0 failures, 0 errors, 67 
skipped (the integration tests that need a node).

Why the resubscription does not route anything twice: the block hash, pending 
transaction and log flowables use web3j's {{Filter}}, which catches the 
{{IOException}} of its polls and logs it, so they end with an error only when 
the filter cannot be installed (or its first {{eth_getFilterLogs}} fails), 
before anything was routed; a new filter only returns what comes after it is 
installed. {{blockFlowable}} and the transaction flowables end on a failed 
per-item request, and the new subscription starts with a new block filter.

Not covered: no option for the delay (a constant, with a package-private setter 
for the tests); the blocks of the gap are not fetched; a log filter from 
{{safe}} or {{finalized}} is not subscribed again (only {{latest}} and 
{{pending}} are treated as live); when the node forgot a filter and web3j's own 
reinstall of it fails, web3j stops polling without an error, which the consumer 
cannot see. Checked against web3j 5.0.0 only (CAMEL-23567, the upgrade to 
5.0.1+, is open).

Affected: main (tested); camel-4.23.x, camel-4.22.x and camel-4.18.x have the 
same consumer (read, not run).

Duplicate check (2026-10-17): JIRA text "web3j consumer", "web3j subscription 
error", "web3j resubscribe", "web3j reconnect": only CAMEL-24748 (metadata); 
GitHub pull requests "web3j": dependency bumps, header renames and metadata 
fixes only.

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to