Skip to content

Commit

Permalink
Merge pull request #49 from Bitcoin-com/fix/backup-poller
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreasLarssons authored May 18, 2024
2 parents d2a96ba + 148500b commit 8aaa78b
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 2 deletions.
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<provider
android:name=".application.GenericFileProvider"
android:authorities="${applicationId}.provider"
android:exported="true"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
Expand Down
18 changes: 17 additions & 1 deletion app/src/main/java/com/bitcoin/merchant/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ import com.bitcoin.merchant.app.network.PaymentReceived
import com.bitcoin.merchant.app.network.websocket.TxWebSocketHandler
import com.bitcoin.merchant.app.network.websocket.WebSocketListener
import com.bitcoin.merchant.app.network.websocket.impl.blockchaininfo.BlockchainInfoSocketSocketHandler
import com.bitcoin.merchant.app.network.websocket.impl.poller.PollerSocket
import com.bitcoin.merchant.app.screens.dialogs.DialogHelper
import com.bitcoin.merchant.app.screens.features.ToolbarAwareFragment
import com.bitcoin.merchant.app.util.AmountUtil
import com.bitcoin.merchant.app.util.AppUtil
import com.bitcoin.merchant.app.util.ScanQRUtil
import com.bitcoin.merchant.app.util.Settings
import com.google.android.material.navigation.NavigationView
import okhttp3.OkHttpClient

open class MainActivity : AppCompatActivity(), WebSocketListener {
private lateinit var mDrawerLayout: DrawerLayout
Expand All @@ -53,11 +55,15 @@ open class MainActivity : AppCompatActivity(), WebSocketListener {

lateinit var blockchainDotInfoSocket: TxWebSocketHandler

lateinit var pollerSocket: PollerSocket

private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Action.SUBSCRIBE_TO_ADDRESS == intent.action) {
println("Subscribed to address!")
blockchainDotInfoSocket.subscribeToAddress(intent.getStringExtra("address"))
val address = intent.getStringExtra("address")
blockchainDotInfoSocket.subscribeToAddress(address)
pollerSocket.subscribeToAddress(address)
}
}
}
Expand Down Expand Up @@ -91,6 +97,13 @@ open class MainActivity : AppCompatActivity(), WebSocketListener {
blockchainDotInfoSocket.setListener(this)
blockchainDotInfoSocket.start()
}
if (!this::pollerSocket.isInitialized || !pollerSocket.isConnected) {
if (this::pollerSocket.isInitialized)
pollerSocket.stop()
pollerSocket = PollerSocket(this, OkHttpClient())
pollerSocket.start()
}

}

private fun listenToConnectivityChanges() {
Expand Down Expand Up @@ -255,6 +268,9 @@ open class MainActivity : AppCompatActivity(), WebSocketListener {

override fun onIncomingPayment(payment: PaymentReceived?) {
if (payment != null) {
if (app.paymentProcessor.paymentAlreadyRecorded(payment.txHash)) {
return;
}
if (payment.bchExpected != 0L && payment.fiatExpected != null) {
if (!payment.isUnderpayment && !payment.isOverpayment) {
Log.d(TAG, "${payment.txHash} has been received.")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.bitcoin.merchant.app.network.websocket.impl.poller;

import android.util.Log;

import com.bitcoin.merchant.app.network.ExpectedAmounts;
import com.bitcoin.merchant.app.network.ExpectedPayments;
import com.bitcoin.merchant.app.network.PaymentReceived;
import com.bitcoin.merchant.app.network.websocket.TxWebSocketHandler;
import com.bitcoin.merchant.app.network.websocket.WebSocketListener;
import com.bitcoin.merchant.app.network.websocket.impl.TxWebSocketHandlerImpl;
import com.google.gson.JsonObject;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketFactory;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class PollerSocket implements TxWebSocketHandler {

private WebSocketListener webSocketListener;
private final OkHttpClient okHttpClient;

private String BASE_URL = "https://rest.bch.actorforth.org/v2/address/utxo/";

private ScheduledExecutorService executorService;

private final Set<String> subscribedAddresses = new HashSet<>();

private final Set<String> alreadyHandled = new HashSet<>();

public PollerSocket(WebSocketListener webSocketListener, OkHttpClient okHttpClient) {
this.webSocketListener = webSocketListener;
this.okHttpClient = okHttpClient;
}

@Override
public void setListener(WebSocketListener webSocketListener) {
this.webSocketListener = webSocketListener;
}

@Override
public void subscribeToAddress(String address) {
subscribedAddresses.add(address);
}

@Override
public void start() {
if (executorService != null && !executorService.isShutdown()) {
return;
}
Log.i("Poller Task", "Starting poller task");
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(createPollerTask(), 0, 5, TimeUnit.SECONDS);
}

@Override
public void stop() {
if (executorService == null) {
return;
}
Log.i("Poller Task", "Stopping poller task");
executorService.shutdownNow();
}

@Override
public boolean isConnected() {
return true;
}

/*{
"height": 653632,
"txid": "78ffb00ae72702b0a37f7c2e85cc40caca7fde3086637f18d29e4a208e2bbfb5",
"vout": 0,
"satoshis": 8673,
"amount": 0.00008673,
"confirmations": 192489
}*/

private Runnable createPollerTask() {
return () -> {
Log.i("Poller Task", "Polling for new transactions");

try {
for (String subscribedAddress : subscribedAddresses) {
String query = BASE_URL + subscribedAddress;
Request request = new Request.Builder()
.get()
.url(query)
.build();
Response execute = okHttpClient.newCall(request).execute();
JSONObject jsonObject = new JSONObject(execute.body().string());
JSONArray utxos = jsonObject.getJSONArray("utxos");
Log.i("Poller Task", "Found " + utxos.length() + " utxos for address " + subscribedAddress);
searchUtxos(subscribedAddress, utxos);
}
} catch (Exception e) {
Log.e("Poller Task", "Error while polling for new transactions", e);
}
};
}

private void searchUtxos(String subscribedAddress, JSONArray utxos) throws JSONException {
for (int i = 0; i < utxos.length(); i++) {
JSONObject utxo = utxos.getJSONObject(i);
long satoshis = utxo.getLong("satoshis");
String txid = utxo.getString("txid");
int confirmations = utxo.getInt("confirmations");
if (confirmations == 0) {
Log.i("Poller Task", "Found unconfirmed transaction for address " + subscribedAddress + " with txid " + txid + " and amount " + satoshis + " satoshis");
triggerPaymentReceived(subscribedAddress, satoshis, txid);
}
}
}

private void triggerPaymentReceived(String subscribedAddress, long satoshis, String txid) {
ExpectedAmounts expected = ExpectedPayments.getInstance().getExpectedAmounts(subscribedAddress);
if (alreadyHandled.contains(txid)) {
return;
}

webSocketListener.onIncomingPayment(new PaymentReceived(
subscribedAddress,
satoshis,
txid,
System.currentTimeMillis() / 1000, 0,
expected
));
alreadyHandled.add(txid);
}
}

0 comments on commit 8aaa78b

Please sign in to comment.