Skip to content

Commit

Permalink
Speedloader
Browse files Browse the repository at this point in the history
Co-authored-by: Hampus Sjöberg <[email protected]>
  • Loading branch information
djkazic and hsjoberg authored Jun 21, 2023
1 parent e7a21f1 commit ce8b3d5
Show file tree
Hide file tree
Showing 30 changed files with 807 additions and 23 deletions.
1 change: 1 addition & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ dependencies {
implementation "net.freehaven.tor.control:jtorctl:0.2" // controlling tor instance via its control port

implementation "com.jakewharton:process-phoenix:2.0.0"
implementation 'org.brotli:dec:0.1.2'
}

configurations {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.blixtwallet;

import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.NetworkType;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;

import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.hypertrack.hyperlog.HyperLog;

class GossipFileScheduledSync extends ReactContextBaseJavaModule {
private final String TAG = "GossipFileScheduledSync";
private final String GOSSIP_FILE_SCHEDULED_SYNC_WORK_NAME = "GOSSIP_FILE_SCHEDULED_SYNC_WORK";
private WorkManager workManager;
private PeriodicWorkRequest periodicWorkRequest;

public GossipFileScheduledSync(ReactApplicationContext reactContext) {
super(reactContext);

workManager = WorkManager.getInstance(getReactApplicationContext());
periodicWorkRequest = BuildConfig.DEBUG
? new PeriodicWorkRequest.Builder(GossipFileScheduledSyncWorker.class, 15, TimeUnit.MINUTES)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).build())
.build()
: new PeriodicWorkRequest.Builder(GossipFileScheduledSyncWorker.class, 1, TimeUnit.DAYS)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).build())
.build();
}

@Override
public String getName() {
return "GossipFileScheduledSync";
}

@ReactMethod
public void setupScheduledSyncWork(Promise promise) {
workManager.enqueueUniquePeriodicWork(GOSSIP_FILE_SCHEDULED_SYNC_WORK_NAME, ExistingPeriodicWorkPolicy.REPLACE, periodicWorkRequest);
promise.resolve(true);
}

@ReactMethod
public void removeScheduledSyncWork(Promise promise) {
workManager.cancelUniqueWork(GOSSIP_FILE_SCHEDULED_SYNC_WORK_NAME);
promise.resolve(true);
}

@ReactMethod
public void checkScheduledSyncWorkStatus(Promise promise) {
try {
HyperLog.d(TAG, "Checking unique periodic work");

ListenableFuture<List<WorkInfo>> future = workManager.getWorkInfosForUniqueWork(GOSSIP_FILE_SCHEDULED_SYNC_WORK_NAME);
List<WorkInfo> workInfoList = future.get();
if (workInfoList.size() == 0) {
promise.resolve("WORK_NOT_EXIST");
}
else if (workInfoList.size() > 1) {
HyperLog.w(TAG, "Found more than 1 work");
}

for (WorkInfo workInfo : workInfoList) {
WorkInfo.State state = workInfo.getState();
promise.resolve(state.toString());
return;
}
} catch (Throwable e) {
HyperLog.e(TAG, "Could not create periodic work", e);
promise.reject("Could not create periodic work", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.blixtwallet;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class GossipFileScheduledSyncPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.asList(new GossipFileScheduledSync(reactContext));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.blixtwallet;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import com.google.common.util.concurrent.ListenableFuture;
import androidx.work.ListenableWorker;
import androidx.work.WorkerParameters;

import com.facebook.react.modules.storage.ReactDatabaseSupplier;
import com.facebook.react.modules.storage.AsyncLocalStorageUtil;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;

import com.oblador.keychain.KeychainModule;
import com.google.protobuf.ByteString;
import com.hypertrack.hyperlog.HyperLog;

import org.brotli.dec.BrotliInputStream;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.HttpURLConnection;
import java.util.zip.GZIPInputStream;

public class GossipFileScheduledSyncWorker extends ListenableWorker {
private final String TAG = "GossipFileScheduledSyncWorker";
private ReactDatabaseSupplier dbSupplier;
private boolean persistentServicesEnabled = false;

public GossipFileScheduledSyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
dbSupplier = ReactDatabaseSupplier.getInstance(getApplicationContext());
}

@Override
public ListenableFuture<Result> startWork() {
persistentServicesEnabled = getPersistentServicesEnabled();

return CallbackToFutureAdapter.getFuture(completer -> {
HyperLog.i(TAG, "------------------------------------");
HyperLog.i(TAG, "Starting scheduled sync work");
HyperLog.i(TAG, "I am " + getApplicationContext().getPackageName());
writeLastScheduledSyncAttemptToDb();

if (persistentServicesEnabled) {
HyperLog.i(TAG, "persistentServicesEnabled = " + persistentServicesEnabled + ", quitting job");
completer.set(Result.success());
return null;
}
HyperLog.i(TAG, "Starting gossip file download");
startGossipWorkThread(completer);
return null;
});
}

private void startGossipWorkThread(CallbackToFutureAdapter.Completer<Result> completer) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
HyperLog.i(TAG, "Handling periodic gossip file download");
try {
URL url = new URL("https://maps.eldamar.icu/mainnet/graph/graph-001d.db");
File dgraph = new File(getApplicationContext().getCacheDir().getAbsolutePath() + "/dgraph");
dgraph.mkdirs();
FileOutputStream out = new FileOutputStream(new File(getApplicationContext().getCacheDir().getAbsolutePath() + "/dgraph/channel.db"));
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestProperty("Accept-Encoding", "br, gzip");
InputStream stream = null;
if ("gzip".equals(con.getContentEncoding())) {
stream = new GZIPInputStream(con.getInputStream());
} else if ("br".equals(con.getContentEncoding())) {
stream = new BrotliInputStream(con.getInputStream());
} else {
stream = con.getInputStream();
}
out.write(stream.readAllBytes());
out.close();
stream.close();
} catch (Throwable e) {
Log.e(TAG, e.getMessage());
HyperLog.e(TAG, e.getMessage());
completer.set(Result.failure());
return;
}
HyperLog.i(TAG, "Periodic gossip file download finished");
completer.set(Result.success());
writeLastScheduledSyncToDb();
}
});
thread.start();
}

private boolean getPersistentServicesEnabled() {
SQLiteDatabase db = dbSupplier.get();
String persistentServicesEnabled = AsyncLocalStorageUtil.getItemImpl(db, "persistentServicesEnabled");
if (persistentServicesEnabled != null) {
return persistentServicesEnabled.equals("true");
}
HyperLog.w(TAG, "Could not find persistentServicesEnabled in asyncStorage");
return false;
}

private void writeLastScheduledSyncAttemptToDb() {
SQLiteDatabase db = dbSupplier.get();
String key = "lastScheduledGossipSyncAttempt";
Long tsLong = System.currentTimeMillis() / 1000;
String value = tsLong.toString();
String sql = "INSERT OR REPLACE INTO catalystLocalStorage VALUES (?, ?);";
SQLiteStatement statement = db.compileStatement(sql);
try {
db.beginTransaction();
statement.clearBindings();
statement.bindString(1, key);
statement.bindString(2, value);
statement.execute();
db.setTransactionSuccessful();
} catch (Exception e) {
HyperLog.w(TAG, e.getMessage(), e);
} finally {
try {
db.endTransaction();
} catch (Exception e) {
HyperLog.w(TAG, e.getMessage(), e);
}
}
}

private void writeLastScheduledSyncToDb() {
SQLiteDatabase db = dbSupplier.get();
String key = "lastScheduledGossipSync";
Long tsLong = System.currentTimeMillis() / 1000;
String value = tsLong.toString();
String sql = "INSERT OR REPLACE INTO catalystLocalStorage VALUES (?, ?);";
SQLiteStatement statement = db.compileStatement(sql);
try {
db.beginTransaction();
statement.clearBindings();
statement.bindString(1, key);
statement.bindString(2, value);
statement.execute();
db.setTransactionSuccessful();
} catch (Exception e) {
HyperLog.w(TAG, e.getMessage(), e);
} finally {
try {
db.endTransaction();
} catch (Exception e) {
HyperLog.w(TAG, e.getMessage(), e);
}
}
}
}
36 changes: 36 additions & 0 deletions android/app/src/main/java/com/blixtwallet/LndMobile.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
Expand Down Expand Up @@ -138,6 +139,20 @@ public void handleMessage(Message msg) {

break;
}
case LndMobileService.MSG_GOSSIP_SYNC_RESULT: {
final int request = msg.arg1;
final Promise promise = requests.remove(request);
if (bundle.containsKey("response")) {
final byte[] bytes = (byte[]) bundle.get("response");
promise.resolve("response=" + new String(bytes, StandardCharsets.UTF_8));
} else if (bundle.containsKey("error_code")) {
HyperLog.e(TAG, "ERROR" + msg);
promise.reject(bundle.getString("error_code"), bundle.getString("error_desc"));
} else {
promise.reject("noresponse");
}
break;
}
case LndMobileService.MSG_GRPC_STREAM_RESULT: {
// TODO EOF Stream error
final String method = (String) bundle.get("method");
Expand Down Expand Up @@ -377,6 +392,27 @@ public void stopLnd(Promise promise) {
}
}

@ReactMethod
public void gossipSync(String networkType, Promise promise) {
int req = new Random().nextInt();
requests.put(req, promise);

Message message = Message.obtain(null, LndMobileService.MSG_GOSSIP_SYNC, req, 0);
message.replyTo = messenger;
Bundle bundle = new Bundle();
bundle.putString(
"networkType",
networkType
);
message.setData(bundle);

try {
lndMobileServiceMessenger.send(message);
} catch (RemoteException e) {
promise.reject(TAG, "Could not Send MSG_GOSSIP_SYNC to LndMobileService", e);
}
}

@ReactMethod
public void sendCommand(String method, String payloadStr, final Promise promise) {
HyperLog.d(TAG, "sendCommand() " + method);
Expand Down
Loading

1 comment on commit ce8b3d5

@vercel
Copy link

@vercel vercel bot commented on ce8b3d5 Jun 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

blixt-wallet – ./

blixt-wallet-hsjoberg.vercel.app
blixt-wallet-git-master-hsjoberg.vercel.app

Please sign in to comment.