mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-11-02 18:26:43 +00:00
remove legacy standalone application
This commit is contained in:
parent
a436de319a
commit
562d82ddaf
9 changed files with 0 additions and 601 deletions
35
build.gradle
35
build.gradle
|
@ -9,7 +9,6 @@ buildscript {
|
||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'extra-java-module-info'
|
id 'extra-java-module-info'
|
||||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(AbstractArchiveTask) {
|
tasks.withType(AbstractArchiveTask) {
|
||||||
|
@ -17,26 +16,17 @@ tasks.withType(AbstractArchiveTask) {
|
||||||
reproducibleFileOrder = true
|
reproducibleFileOrder = true
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'com.sparrowwallet'
|
|
||||||
version '1.0'
|
|
||||||
|
|
||||||
def os = org.gradle.internal.os.OperatingSystem.current()
|
def os = org.gradle.internal.os.OperatingSystem.current()
|
||||||
def osName = os.getFamilyName()
|
def osName = os.getFamilyName()
|
||||||
if(os.macOsX) {
|
if(os.macOsX) {
|
||||||
osName = "osx"
|
osName = "osx"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = 16
|
|
||||||
targetCompatibility = 16
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation ('org.zeromq:jeromq:0.5.0') {
|
|
||||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
|
||||||
}
|
|
||||||
implementation ('com.googlecode.json-simple:json-simple:1.1.1') {
|
implementation ('com.googlecode.json-simple:json-simple:1.1.1') {
|
||||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
||||||
exclude group: 'junit', module: 'junit'
|
exclude group: 'junit', module: 'junit'
|
||||||
|
@ -70,28 +60,6 @@ processResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task(runDrongo, dependsOn: 'classes', type: JavaExec) {
|
|
||||||
mainClass = 'com.sparrowwallet.drongo.Main'
|
|
||||||
classpath = sourceSets.main.runtimeClasspath
|
|
||||||
args 'drongo.properties'
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
manifest {
|
|
||||||
attributes "Main-Class": "com.sparrowwallet.drongo.Main"
|
|
||||||
}
|
|
||||||
|
|
||||||
exclude('logback.xml')
|
|
||||||
|
|
||||||
archiveBaseName = 'drongo'
|
|
||||||
archiveVersion = '0.9'
|
|
||||||
}
|
|
||||||
|
|
||||||
shadowJar {
|
|
||||||
archiveVersion = '0.9'
|
|
||||||
classifier = 'all'
|
|
||||||
}
|
|
||||||
|
|
||||||
extraJavaModuleInfo {
|
extraJavaModuleInfo {
|
||||||
module('logback-core-1.2.8.jar', 'logback.core', '1.2.8') {
|
module('logback-core-1.2.8.jar', 'logback.core', '1.2.8') {
|
||||||
exports('ch.qos.logback.core')
|
exports('ch.qos.logback.core')
|
||||||
|
@ -106,9 +74,6 @@ extraJavaModuleInfo {
|
||||||
requires('java.xml')
|
requires('java.xml')
|
||||||
requires('java.logging')
|
requires('java.logging')
|
||||||
}
|
}
|
||||||
module('jeromq-0.5.0.jar', 'jeromq', '0.5.0') {
|
|
||||||
exports('org.zeromq')
|
|
||||||
}
|
|
||||||
module('json-simple-1.1.1.jar', 'json.simple', '1.1.1') {
|
module('json-simple-1.1.1.jar', 'json.simple', '1.1.1') {
|
||||||
exports('org.json.simple')
|
exports('org.json.simple')
|
||||||
exports('org.json.simple.parser')
|
exports('org.json.simple.parser')
|
||||||
|
|
|
@ -1,83 +1,12 @@
|
||||||
package com.sparrowwallet.drongo;
|
package com.sparrowwallet.drongo;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.rpc.BitcoinJSONRPCClient;
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.slf4j.event.Level;
|
import org.slf4j.event.Level;
|
||||||
import org.zeromq.SocketType;
|
|
||||||
import org.zeromq.ZContext;
|
|
||||||
import org.zeromq.ZMQ;
|
|
||||||
|
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
public class Drongo {
|
public class Drongo {
|
||||||
private static final Logger log = LoggerFactory.getLogger(Drongo.class);
|
|
||||||
|
|
||||||
private String nodeZmqAddress;
|
|
||||||
private BitcoinJSONRPCClient bitcoinJSONRPCClient;
|
|
||||||
private List<WatchWallet> watchWallets;
|
|
||||||
private String[] notifyRecipients;
|
|
||||||
|
|
||||||
public Drongo(String nodeZmqAddress, Map<String, String> nodeRpc, List<WatchWallet> watchWallets, String[] notifyRecipients) {
|
|
||||||
this.nodeZmqAddress = nodeZmqAddress;
|
|
||||||
this.bitcoinJSONRPCClient = new BitcoinJSONRPCClient(nodeRpc.get("host"), nodeRpc.get("port"), nodeRpc.get("user"), nodeRpc.get("password"));
|
|
||||||
this.watchWallets = watchWallets;
|
|
||||||
this.notifyRecipients = notifyRecipients;
|
|
||||||
|
|
||||||
for(WatchWallet wallet : watchWallets) {
|
|
||||||
wallet.initialiseAddresses();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
ExecutorService executorService = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
executorService = Executors.newFixedThreadPool(2);
|
|
||||||
|
|
||||||
try (ZContext context = new ZContext()) {
|
|
||||||
ZMQ.Socket subscriber = context.createSocket(SocketType.SUB);
|
|
||||||
subscriber.setRcvHWM(0);
|
|
||||||
subscriber.connect(nodeZmqAddress);
|
|
||||||
|
|
||||||
String subscription = "rawtx";
|
|
||||||
subscriber.subscribe(subscription.getBytes(ZMQ.CHARSET));
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
String topic = subscriber.recvStr();
|
|
||||||
if (topic == null)
|
|
||||||
break;
|
|
||||||
byte[] data = subscriber.recv();
|
|
||||||
assert (topic.equals(subscription));
|
|
||||||
|
|
||||||
if(subscriber.hasReceiveMore()) {
|
|
||||||
byte[] endData = subscriber.recv();
|
|
||||||
}
|
|
||||||
|
|
||||||
TransactionTask transactionTask = new TransactionTask(this, data);
|
|
||||||
executorService.submit(transactionTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if(executorService != null) {
|
|
||||||
executorService.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitcoinJSONRPCClient getBitcoinJSONRPCClient() {
|
|
||||||
return bitcoinJSONRPCClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<WatchWallet> getWallets() {
|
|
||||||
return watchWallets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setRootLogLevel(Level level) {
|
public static void setRootLogLevel(Level level) {
|
||||||
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
||||||
root.setLevel(ch.qos.logback.classic.Level.toLevel(level.toString()));
|
root.setLevel(ch.qos.logback.classic.Level.toLevel(level.toString()));
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
package com.sparrowwallet.drongo;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class Main {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(Main.class);
|
|
||||||
|
|
||||||
public static void main(String [] args) {
|
|
||||||
String propertiesFile = "./drongo.properties";
|
|
||||||
if(args.length > 0) {
|
|
||||||
propertiesFile = args[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties properties = new Properties();
|
|
||||||
properties.setProperty("nodeAddress", "localhost");
|
|
||||||
|
|
||||||
try {
|
|
||||||
File file = new File(propertiesFile);
|
|
||||||
properties.load(new FileInputStream(propertiesFile));
|
|
||||||
log.info("Loaded properties from " + file.getCanonicalPath());
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Could not load properties from provided path " + propertiesFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
String nodeZmqAddress = properties.getProperty("node.zmqpubrawtx");
|
|
||||||
if(nodeZmqAddress == null) {
|
|
||||||
log.error("Property node.zmqpubrawtx not set, provide the zmqpubrawtx setting of the local node");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> rpcConnection = new LinkedHashMap<String, String>() {
|
|
||||||
{
|
|
||||||
put("host", properties.getProperty("node.rpcconnect", "127.0.0.1"));
|
|
||||||
put("port", properties.getProperty("node.rpcport", "8332"));
|
|
||||||
put("user", properties.getProperty("node.rpcuser"));
|
|
||||||
put("password", properties.getProperty("node.rpcpassword"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
List<WatchWallet> watchWallets = new ArrayList<>();
|
|
||||||
int walletNumber = 1;
|
|
||||||
WatchWallet wallet = getWalletFromProperties(properties, walletNumber);
|
|
||||||
if(wallet == null) {
|
|
||||||
log.error("Property wallet.name.1 and/or wallet.descriptor.1 not set, provide wallet name and Base58 encoded key starting with xpub or ypub");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
while(wallet != null) {
|
|
||||||
watchWallets.add(wallet);
|
|
||||||
wallet = getWalletFromProperties(properties, ++walletNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
String notifyRecipients = properties.getProperty("notify.recipients");
|
|
||||||
if(notifyRecipients == null) {
|
|
||||||
log.error("Property notify.recipients not set, provide comma separated email addresses to receive wallet change notifications");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Drongo drongo = new Drongo(nodeZmqAddress, rpcConnection, watchWallets, notifyRecipients.split(","));
|
|
||||||
drongo.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static WatchWallet getWalletFromProperties(Properties properties, int walletNumber) {
|
|
||||||
String walletName = properties.getProperty("wallet.name." + walletNumber);
|
|
||||||
String walletDescriptor = properties.getProperty("wallet.descriptor." + walletNumber);
|
|
||||||
if(walletName != null && walletDescriptor != null) {
|
|
||||||
return new WatchWallet(walletName, walletDescriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
package com.sparrowwallet.drongo;
|
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class TransactionTask implements Runnable {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(Drongo.class);
|
|
||||||
|
|
||||||
private Drongo drongo;
|
|
||||||
private byte[] transactionData;
|
|
||||||
|
|
||||||
public TransactionTask(Drongo drongo, byte[] transactionData) {
|
|
||||||
this.drongo = drongo;
|
|
||||||
this.transactionData = transactionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Transaction transaction = new Transaction(transactionData);
|
|
||||||
Map<String, Transaction> referencedTransactions = new HashMap<>();
|
|
||||||
|
|
||||||
Sha256Hash txid = transaction.getTxId();
|
|
||||||
StringBuilder builder = new StringBuilder("Txid: " + txid.toString() + " ");
|
|
||||||
StringJoiner inputJoiner = new StringJoiner(", ", "[", "]");
|
|
||||||
|
|
||||||
int vin = 0;
|
|
||||||
for(TransactionInput input : transaction.getInputs()) {
|
|
||||||
if(input.isCoinBase()) {
|
|
||||||
inputJoiner.add("Coinbase:" + vin);
|
|
||||||
} else {
|
|
||||||
String referencedTxID = input.getOutpoint().getHash().toString();
|
|
||||||
long referencedVout = input.getOutpoint().getIndex();
|
|
||||||
|
|
||||||
Transaction referencedTransaction = referencedTransactions.get(referencedTxID);
|
|
||||||
if(referencedTransaction == null) {
|
|
||||||
String referencedTransactionHex = drongo.getBitcoinJSONRPCClient().getRawTransaction(referencedTxID);
|
|
||||||
referencedTransaction = new Transaction(Utils.hexToBytes(referencedTransactionHex));
|
|
||||||
referencedTransactions.put(referencedTxID, referencedTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
TransactionOutput referencedOutput = referencedTransaction.getOutputs().get((int)referencedVout);
|
|
||||||
if(referencedOutput.getScript().containsToAddress()) {
|
|
||||||
try {
|
|
||||||
Address[] inputAddresses = referencedOutput.getScript().getToAddresses();
|
|
||||||
input.getOutpoint().setAddresses(inputAddresses);
|
|
||||||
inputJoiner.add((inputAddresses.length == 1 ? inputAddresses[0] : Arrays.asList(inputAddresses)) + ":" + vin);
|
|
||||||
} catch(NonStandardScriptException e) {
|
|
||||||
//Cannot happen
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn("Could not determine nature of referenced input tx: " + referencedTxID + ":" + referencedVout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vin++;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append(inputJoiner.toString() + " => ");
|
|
||||||
StringJoiner outputJoiner = new StringJoiner(", ", "[", "]");
|
|
||||||
|
|
||||||
int vout = 0;
|
|
||||||
for(TransactionOutput output : transaction.getOutputs()) {
|
|
||||||
try {
|
|
||||||
if(output.getScript().containsToAddress()) {
|
|
||||||
try {
|
|
||||||
Address[] outputAddresses = output.getScript().getToAddresses();
|
|
||||||
output.setAddresses(outputAddresses);
|
|
||||||
outputJoiner.add((outputAddresses.length == 1 ? outputAddresses[0] : Arrays.asList(outputAddresses)) + ":" + vout + " (" + output.getValue() + ")");
|
|
||||||
} catch(NonStandardScriptException e) {
|
|
||||||
//Cannot happen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(ProtocolException e) {
|
|
||||||
log.debug("Invalid script for output " + vout + " detected (" + e.getMessage() + "). Skipping...");
|
|
||||||
}
|
|
||||||
|
|
||||||
vout++;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append(outputJoiner.toString());
|
|
||||||
log.debug(builder.toString());
|
|
||||||
|
|
||||||
checkWallet(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkWallet(Transaction transaction) {
|
|
||||||
for(WatchWallet wallet : drongo.getWallets()) {
|
|
||||||
List<Address> fromAddresses = new ArrayList<>();
|
|
||||||
for(TransactionInput input : transaction.getInputs()) {
|
|
||||||
for(Address address : input.getOutpoint().getAddresses()) {
|
|
||||||
if(wallet.containsAddress(address)) {
|
|
||||||
fromAddresses.add(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<Address,Long> toAddresses = new HashMap<>();
|
|
||||||
for(TransactionOutput output : transaction.getOutputs()) {
|
|
||||||
for(Address address : output.getAddresses()) {
|
|
||||||
if(wallet.containsAddress(address)) {
|
|
||||||
toAddresses.put(address, output.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!fromAddresses.isEmpty()) {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Wallet ").append(wallet.getName()).append(" sent from address").append(fromAddresses.size() == 1 ? " " : "es ");
|
|
||||||
StringJoiner fromJoiner = new StringJoiner(", ", "[", "]");
|
|
||||||
for(Address address : fromAddresses) {
|
|
||||||
fromJoiner.add(address.toString() + " [" + Utils.formatHDPath(wallet.getAddressPath(address)) + "]");
|
|
||||||
}
|
|
||||||
builder.append(fromJoiner.toString()).append(" in txid ").append(transaction.getTxId());
|
|
||||||
log.info(builder.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!toAddresses.isEmpty()) {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("Wallet ").append(wallet.getName()).append(" received to address").append(toAddresses.size() == 1 ? " " : "es ");
|
|
||||||
StringJoiner toJoiner = new StringJoiner(", ", "[", "]");
|
|
||||||
for(Address address : toAddresses.keySet()) {
|
|
||||||
toJoiner.add(address.toString() + " [" + Utils.formatHDPath(wallet.getAddressPath(address)) + "]" + " (" + toAddresses.get(address) + " sats)");
|
|
||||||
}
|
|
||||||
builder.append(toJoiner.toString()).append(" in txid ").append(transaction.getTxId());
|
|
||||||
log.info(builder.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
package com.sparrowwallet.drongo.rpc;
|
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.json.simple.parser.JSONParser;
|
|
||||||
import org.json.simple.parser.ParseException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.*;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class BitcoinJSONRPCClient {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(BitcoinJSONRPCClient.class);
|
|
||||||
public static final Charset QUERY_CHARSET = Charset.forName("ISO8859-1");
|
|
||||||
public static final String RESPONSE_ID = "drongo";
|
|
||||||
|
|
||||||
public final URL rpcURL;
|
|
||||||
private final URL noAuthURL;
|
|
||||||
private final String authStr;
|
|
||||||
|
|
||||||
public BitcoinJSONRPCClient(String host, String port, String user, String password) {
|
|
||||||
this.rpcURL = getConnectUrl(host, port, user, password);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.noAuthURL = new URI(rpcURL.getProtocol(), null, rpcURL.getHost(), rpcURL.getPort(), rpcURL.getPath(), rpcURL.getQuery(), null).toURL();
|
|
||||||
} catch (MalformedURLException | URISyntaxException ex) {
|
|
||||||
throw new IllegalArgumentException(rpcURL.toString(), ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.authStr = rpcURL.getUserInfo() == null ? null : new String(Base64.getEncoder().encode(rpcURL.getUserInfo().getBytes(QUERY_CHARSET)), QUERY_CHARSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
private URL getConnectUrl(String host, String port, String user, String password) {
|
|
||||||
try {
|
|
||||||
return new URL("http://" + user + ':' + password + "@" + host + ":" + (port == null ? "8332" : port) + "/");
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
throw new IllegalArgumentException("Invalid RPC connection details", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object query(String method, Object... o) throws BitcoinRPCException {
|
|
||||||
HttpURLConnection conn;
|
|
||||||
try {
|
|
||||||
conn = (HttpURLConnection) noAuthURL.openConnection();
|
|
||||||
|
|
||||||
conn.setDoOutput(true);
|
|
||||||
conn.setDoInput(true);
|
|
||||||
|
|
||||||
conn.setRequestProperty("Authorization", "Basic " + authStr);
|
|
||||||
byte[] r = prepareRequest(method, o);
|
|
||||||
log.debug("Bitcoin JSON-RPC request: " + new String(r, QUERY_CHARSET));
|
|
||||||
conn.getOutputStream().write(r);
|
|
||||||
conn.getOutputStream().close();
|
|
||||||
int responseCode = conn.getResponseCode();
|
|
||||||
if (responseCode != 200) {
|
|
||||||
InputStream errorStream = conn.getErrorStream();
|
|
||||||
throw new BitcoinRPCException(method,
|
|
||||||
Arrays.deepToString(o),
|
|
||||||
responseCode,
|
|
||||||
conn.getResponseMessage(),
|
|
||||||
errorStream == null ? null : new String(loadStream(errorStream, true)));
|
|
||||||
}
|
|
||||||
return loadResponse(conn.getInputStream(), RESPONSE_ID, true);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new BitcoinRPCException(method, Arrays.deepToString(o), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] prepareRequest(final String method, final Object... params) {
|
|
||||||
return JSONObject.toJSONString(new LinkedHashMap<String, Object>() {
|
|
||||||
{
|
|
||||||
put("method", method);
|
|
||||||
put("params", Arrays.asList(params));
|
|
||||||
put("id", RESPONSE_ID);
|
|
||||||
put("jsonrpc", "1.0");
|
|
||||||
}
|
|
||||||
}).getBytes(QUERY_CHARSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] loadStream(InputStream in, boolean close) throws IOException {
|
|
||||||
ByteArrayOutputStream o = new ByteArrayOutputStream();
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
for (;;) {
|
|
||||||
int nr = in.read(buffer);
|
|
||||||
|
|
||||||
if (nr == -1)
|
|
||||||
break;
|
|
||||||
if (nr == 0)
|
|
||||||
throw new IOException("Read timed out");
|
|
||||||
|
|
||||||
o.write(buffer, 0, nr);
|
|
||||||
}
|
|
||||||
return o.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public Object loadResponse(InputStream in, Object expectedID, boolean close) throws IOException, BitcoinRPCException {
|
|
||||||
try {
|
|
||||||
String r = new String(loadStream(in, close), QUERY_CHARSET);
|
|
||||||
log.debug("Bitcoin JSON-RPC response: " + r);
|
|
||||||
try {
|
|
||||||
JSONParser jsonParser = new JSONParser();
|
|
||||||
Map response = (Map) jsonParser.parse(r);
|
|
||||||
|
|
||||||
if (!expectedID.equals(response.get("id")))
|
|
||||||
throw new BitcoinRPCException("Wrong response ID (expected: " + String.valueOf(expectedID) + ", response: " + response.get("id") + ")");
|
|
||||||
|
|
||||||
if (response.get("error") != null)
|
|
||||||
throw new BitcoinRPCException(new BitcoinRPCError((Map)response.get("error")));
|
|
||||||
|
|
||||||
return response.get("result");
|
|
||||||
} catch (ClassCastException | ParseException ex) {
|
|
||||||
throw new BitcoinRPCException("Invalid server response format (data: \"" + r + "\")");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (close)
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRawTransaction(String txId) throws BitcoinRPCException {
|
|
||||||
return (String) query("getrawtransaction", txId);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package com.sparrowwallet.drongo.rpc;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class BitcoinRPCError {
|
|
||||||
private int code;
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes" })
|
|
||||||
public BitcoinRPCError(Map errorMap) {
|
|
||||||
Number n = (Number) errorMap.get("code");
|
|
||||||
this.code = n != null ? n.intValue() : 0;
|
|
||||||
this.message = (String) errorMap.get("message");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
package com.sparrowwallet.drongo.rpc;
|
|
||||||
|
|
||||||
import org.json.simple.parser.JSONParser;
|
|
||||||
import org.json.simple.parser.ParseException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class BitcoinRPCException extends RuntimeException {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(BitcoinJSONRPCClient.class);
|
|
||||||
|
|
||||||
private String rpcMethod;
|
|
||||||
private String rpcParams;
|
|
||||||
private int responseCode;
|
|
||||||
private String responseMessage;
|
|
||||||
private String response;
|
|
||||||
private BitcoinRPCError rpcError;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of <code>BitcoinRPCException</code> with response
|
|
||||||
* detail.
|
|
||||||
*
|
|
||||||
* @param method the rpc method called
|
|
||||||
* @param params the parameters sent
|
|
||||||
* @param responseCode the HTTP code received
|
|
||||||
* @param responseMessage the HTTP response message
|
|
||||||
* @param response the error stream received
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public BitcoinRPCException(String method,
|
|
||||||
String params,
|
|
||||||
int responseCode,
|
|
||||||
String responseMessage,
|
|
||||||
String response) {
|
|
||||||
super("RPC Query Failed (method: " + method + ", params: " + params + ", response code: " + responseCode + " responseMessage " + responseMessage + ", response: " + response);
|
|
||||||
this.rpcMethod = method;
|
|
||||||
this.rpcParams = params;
|
|
||||||
this.responseCode = responseCode;
|
|
||||||
this.responseMessage = responseMessage;
|
|
||||||
this.response = response;
|
|
||||||
if ( responseCode == 500 ) {
|
|
||||||
// Bitcoind application error when handle the request
|
|
||||||
// extract code/message for callers to handle
|
|
||||||
try {
|
|
||||||
JSONParser jsonParser = new JSONParser();
|
|
||||||
Map error = (Map) ((Map)jsonParser.parse(response)).get("error");
|
|
||||||
if ( error != null ) {
|
|
||||||
rpcError = new BitcoinRPCError(error);
|
|
||||||
}
|
|
||||||
} catch(ParseException e) {
|
|
||||||
log.error("Could not parse bitcoind error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitcoinRPCException(String method, String params, Throwable cause) {
|
|
||||||
super("RPC Query Failed (method: " + method + ", params: " + params + ")", cause);
|
|
||||||
this.rpcMethod = method;
|
|
||||||
this.rpcParams = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance of <code>BitcoinRPCException</code> with the
|
|
||||||
* specified detail message.
|
|
||||||
*
|
|
||||||
* @param msg the detail message.
|
|
||||||
*/
|
|
||||||
public BitcoinRPCException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitcoinRPCException(BitcoinRPCError error) {
|
|
||||||
super(error.getMessage());
|
|
||||||
this.rpcError = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitcoinRPCException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getResponseCode() {
|
|
||||||
return responseCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRpcMethod() {
|
|
||||||
return rpcMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRpcParams() {
|
|
||||||
return rpcParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the HTTP response message
|
|
||||||
*/
|
|
||||||
public String getResponseMessage() {
|
|
||||||
return responseMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return response message from bitcored
|
|
||||||
*/
|
|
||||||
public String getResponse() {
|
|
||||||
return this.response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return response message from bitcored
|
|
||||||
*/
|
|
||||||
public BitcoinRPCError getRPCError() {
|
|
||||||
return this.rpcError;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ open module com.sparrowwallet.drongo {
|
||||||
requires logback.core;
|
requires logback.core;
|
||||||
requires logback.classic;
|
requires logback.classic;
|
||||||
requires json.simple;
|
requires json.simple;
|
||||||
requires jeromq;
|
|
||||||
exports com.sparrowwallet.drongo;
|
exports com.sparrowwallet.drongo;
|
||||||
exports com.sparrowwallet.drongo.psbt;
|
exports com.sparrowwallet.drongo.psbt;
|
||||||
exports com.sparrowwallet.drongo.protocol;
|
exports com.sparrowwallet.drongo.protocol;
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
<configuration>
|
|
||||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
|
||||||
<file>drongo.log</file>
|
|
||||||
<encoder>
|
|
||||||
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<encoder>
|
|
||||||
<pattern>%date %level %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<root level="debug">
|
|
||||||
<appender-ref ref="FILE" />
|
|
||||||
<appender-ref ref="STDOUT" />
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
Loading…
Reference in a new issue