initial commit
This commit is contained in:
commit
e72cebcb28
17
.project
Normal file
17
.project
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>opret-parent</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
4
.settings/org.eclipse.m2e.core.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
|
@ -0,0 +1,4 @@
|
|||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
31
opret-testapp/.classpath
Normal file
31
opret-testapp/.classpath
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
4
opret-testapp/.gitignore
vendored
Normal file
4
opret-testapp/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/target/
|
||||
/walletappkit-example.spvchain
|
||||
/walletappkit-example.wallet
|
||||
/opretwalletorg.bitcoin.test.wallet
|
23
opret-testapp/.project
Normal file
23
opret-testapp/.project
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>opret-testapp</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
5
opret-testapp/.settings/org.eclipse.jdt.core.prefs
Normal file
5
opret-testapp/.settings/org.eclipse.jdt.core.prefs
Normal file
|
@ -0,0 +1,5 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
4
opret-testapp/.settings/org.eclipse.m2e.core.prefs
Normal file
4
opret-testapp/.settings/org.eclipse.m2e.core.prefs
Normal file
|
@ -0,0 +1,4 @@
|
|||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
84
opret-testapp/pom.xml
Normal file
84
opret-testapp/pom.xml
Normal file
|
@ -0,0 +1,84 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.tcpid</groupId>
|
||||
<artifactId>opret-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>opret-testapp</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bitcoinj</groupId>
|
||||
<artifactId>bitcoinj-core</artifactId>
|
||||
<version>0.14.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.0.13</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.abstractj.kalium</groupId>
|
||||
<artifactId>kalium</artifactId>
|
||||
<version>0.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.tcpid</groupId>
|
||||
<artifactId>opretj</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.jopt-simple</groupId>
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
<version>4.3</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<configuration>
|
||||
<mainClass>org.tcpid.opretj.testapp.App</mainClass>
|
||||
<systemProperties>
|
||||
<systemProperty>
|
||||
<key>logback.configurationFile</key>
|
||||
<value>${basedir}/opret-testapp/src/main/resources/logback.xml</value>
|
||||
</systemProperty>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
297
opret-testapp/src/main/java/org/tcpid/opretj/testapp/App.java
Normal file
297
opret-testapp/src/main/java/org/tcpid/opretj/testapp/App.java
Normal file
|
@ -0,0 +1,297 @@
|
|||
package org.tcpid.opretj.testapp;
|
||||
|
||||
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.InputMismatchException;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
import org.abstractj.kalium.keys.SigningKey;
|
||||
import org.abstractj.kalium.keys.VerifyKey;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.bitcoinj.core.listeners.TransactionConfidenceEventListener;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.wallet.KeyChain.KeyPurpose;
|
||||
import org.bitcoinj.wallet.SendRequest;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.bitcoinj.wallet.Wallet.SendResult;
|
||||
import org.bitcoinj.wallet.listeners.KeyChainEventListener;
|
||||
import org.bitcoinj.wallet.listeners.ScriptsChangeEventListener;
|
||||
import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener;
|
||||
import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.tcpid.opretj.*;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
|
||||
public class App {
|
||||
private static final Logger logger = LoggerFactory.getLogger(App.class);
|
||||
|
||||
public static final long OPRET_BIRTHDAY = 1471989600;
|
||||
static byte[] rawKey = Sha256Hash.hash("TESTSEED".getBytes());
|
||||
static SigningKey sk = new SigningKey(rawKey);
|
||||
static VerifyKey v = sk.getVerifyKey();
|
||||
private static byte[] pkhash = Sha256Hash.hash(v.toBytes());
|
||||
static byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), pkhash);
|
||||
|
||||
public static void check(final byte[] pkhash, final byte[] sig) {
|
||||
logger.warn("CHECKING REVOKE PK {} - SIG {}", Utils.HEX.encode(pkhash), Utils.HEX.encode(sig));
|
||||
|
||||
if (!Arrays.equals(App.pkhash, pkhash)) {
|
||||
logger.warn("Unknown PK {}", Utils.HEX.encode(pkhash));
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn("Using VerifyKey {}", v);
|
||||
|
||||
if (v.verify(revokemsg, sig)) {
|
||||
logger.warn("REVOKED VerifyKey {}", v);
|
||||
} else {
|
||||
logger.warn("SIGNATURE does not match!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
OptionParser parser = new OptionParser();
|
||||
OptionSpec<NetworkEnum> net = parser.accepts("net", "The network to run the examples on").withRequiredArg().ofType(NetworkEnum.class).defaultsTo(NetworkEnum.TEST);
|
||||
parser.accepts("help", "Displays program options");
|
||||
OptionSet opts = parser.parse(args);
|
||||
if (opts.has("help") || !opts.has(net)) {
|
||||
System.err.println("usage: App --net=MAIN/TEST/REGTEST");
|
||||
parser.printHelpOn(System.err);
|
||||
return;
|
||||
}
|
||||
final NetworkParameters params = net.value(opts).get();
|
||||
|
||||
final OPRETECParser bs = new OPRETECParser();
|
||||
|
||||
bs.addOPRETECRevokeEventListener(new OPRETECRevokeEventListener() {
|
||||
public void onOPRETRevoke(final byte[] pkhash, final byte[] sig) {
|
||||
// logger.warn("REVOKE PK {} - SIG {}",
|
||||
// Utils.HEX.encode(pkhash), Utils.HEX.encode(sig));
|
||||
check(pkhash, sig);
|
||||
}
|
||||
});
|
||||
|
||||
long earliestTime;
|
||||
if (params.getId().equals(NetworkParameters.ID_REGTEST)) {
|
||||
earliestTime = OPRET_BIRTHDAY;
|
||||
} else if (params.getId().equals(NetworkParameters.ID_TESTNET)) {
|
||||
earliestTime = OPRET_BIRTHDAY;
|
||||
} else {
|
||||
earliestTime = Utils.currentTimeSeconds();
|
||||
}
|
||||
|
||||
bs.addOPRET(pkhash, earliestTime);
|
||||
//bs.addOPRET(Sha256Hash.hash("test1".getBytes()), earliestTime);
|
||||
//bs.addOPRET(Utils.HEX.decode("0f490dee643b01b06e0ea84c253a90050a3543cfb7c74319fb47b04afee5b872"), earliestTime);
|
||||
|
||||
// Now we initialize a new WalletAppKit. The kit handles all the
|
||||
// boilerplate for us and is the easiest way to get everything up and
|
||||
// running.
|
||||
// Have a look at the WalletAppKit documentation and its source to
|
||||
// understand what's happening behind the scenes:
|
||||
// https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java
|
||||
final OPRETWalletAppKit kit = new OPRETWalletAppKit(params, new File("."), "opretwallet" + params.getId(), bs);
|
||||
|
||||
// In case you want to connect with your local bitcoind tell the kit to
|
||||
// connect to localhost.
|
||||
// You must do that in reg test mode.
|
||||
if (params.getId().equals(NetworkParameters.ID_REGTEST)) {
|
||||
kit.connectToLocalHost();
|
||||
}
|
||||
|
||||
// Now we start the kit and sync the blockchain.
|
||||
// bitcoinj is working a lot with the Google Guava libraries. The
|
||||
// WalletAppKit extends the AbstractIdleService. Have a look at the
|
||||
// introduction to Guava services:
|
||||
// https://github.com/google/guava/wiki/ServiceExplained
|
||||
kit.startAsync();
|
||||
kit.awaitRunning();
|
||||
|
||||
final OPRETWallet wallet = kit.opretwallet();
|
||||
|
||||
wallet.addCoinsReceivedEventListener(new WalletCoinsReceivedEventListener() {
|
||||
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
System.out.println("-----> coins resceived: " + tx.getHashAsString());
|
||||
System.out.println("received: " + tx.getValue(wallet));
|
||||
}
|
||||
});
|
||||
|
||||
wallet.addCoinsSentEventListener(new WalletCoinsSentEventListener() {
|
||||
public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
System.out.println("coins sent");
|
||||
}
|
||||
});
|
||||
|
||||
wallet.addKeyChainEventListener(new KeyChainEventListener() {
|
||||
public void onKeysAdded(List<ECKey> keys) {
|
||||
System.out.println("new key added");
|
||||
}
|
||||
});
|
||||
|
||||
wallet.addScriptsChangeEventListener(new ScriptsChangeEventListener() {
|
||||
public void onScriptsChanged(Wallet wallet, List<Script> scripts, boolean isAddingScripts) {
|
||||
System.out.println("new script added");
|
||||
}
|
||||
});
|
||||
|
||||
wallet.addTransactionConfidenceEventListener(new TransactionConfidenceEventListener() {
|
||||
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
|
||||
System.out.println("-----> confidence changed: " + tx.getHashAsString());
|
||||
final TransactionConfidence confidence = tx.getConfidence();
|
||||
System.out.println("new block depth: " + confidence.getDepthInBlocks());
|
||||
}
|
||||
});
|
||||
|
||||
//wallet.allowSpendingUnconfirmedTransactions();
|
||||
|
||||
// Ready to run. The kit syncs the blockchain and our wallet event
|
||||
// listener gets notified when something happens.
|
||||
// To test everything we create and print a fresh receiving address.
|
||||
// Send some coins to that address and see if everything works.
|
||||
String receiveStr = wallet.freshReceiveAddress().toString();
|
||||
System.out.println("send money to: " + receiveStr);
|
||||
try {
|
||||
System.out.print(executeCommand("qrencode -t UTF8 -o - " + receiveStr));
|
||||
} catch (Exception e) {
|
||||
;
|
||||
}
|
||||
|
||||
final Scanner input = new Scanner(System.in);
|
||||
|
||||
display: while (true) {
|
||||
|
||||
System.out.println("-- Actions --");
|
||||
System.out.println("Select an option: \n" + " 0) QUIT\n" + " 1) Display Balance\n"
|
||||
+ " 2) Display Receive Address\n" + " 3) Send OP_Return\n");
|
||||
try {
|
||||
final int selection = input.nextInt();
|
||||
|
||||
switch (selection) {
|
||||
case 0:
|
||||
System.out.println("Returning...");
|
||||
break display;
|
||||
case 1:
|
||||
System.out.println("Balance: " + wallet.getBalance().toFriendlyString());
|
||||
break;
|
||||
case 2:
|
||||
System.out.println("send money to: " + wallet.freshReceiveAddress().toString());
|
||||
break;
|
||||
case 3:
|
||||
sendOPReturn(kit);
|
||||
break;
|
||||
default:
|
||||
System.out.println("Invalid action.");
|
||||
break;
|
||||
}
|
||||
} catch (final InputMismatchException e) {
|
||||
;
|
||||
}
|
||||
input.nextLine();
|
||||
|
||||
}
|
||||
input.close();
|
||||
|
||||
// Make sure to properly shut down all the running services when you
|
||||
// manually want to stop the kit. The WalletAppKit registers a runtime
|
||||
// ShutdownHook so we actually do not need to worry about that when our
|
||||
// application is stopping.
|
||||
System.out.println("shutting down again");
|
||||
kit.stopAsync();
|
||||
kit.awaitTerminated();
|
||||
}
|
||||
|
||||
private static boolean sendOPReturn(OPRETWalletAppKit kit) {
|
||||
final OPRETWallet wallet = kit.opretwallet();
|
||||
final NetworkParameters params = wallet.getNetworkParameters();
|
||||
|
||||
Transaction t = new Transaction(params);
|
||||
final byte[] sig = sk.sign(revokemsg);
|
||||
|
||||
Script script = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("ec1d")).data(Utils.HEX.decode("fe"))
|
||||
.data(pkhash).data(Arrays.copyOfRange(sig, 0, 32)).build();
|
||||
t.addOutput(Coin.ZERO, script);
|
||||
t.addOutput(Transaction.DEFAULT_TX_FEE, wallet.freshAddress(KeyPurpose.CHANGE));
|
||||
SendRequest request = SendRequest.forTx(t);
|
||||
request.ensureMinRequiredFee = true;
|
||||
SendResult sr = null;
|
||||
|
||||
try {
|
||||
sr = wallet.sendCoins(request);
|
||||
} catch (final InsufficientMoneyException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
logger.debug("SendRequest {}", request);
|
||||
|
||||
script = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("ec1d")).data(Utils.HEX.decode("ff"))
|
||||
.data(pkhash).data(Arrays.copyOfRange(sig, 32, 64)).build();
|
||||
t = new Transaction(params);
|
||||
|
||||
for (final TransactionOutput out : sr.tx.getOutputs()) {
|
||||
if (out.getValue().compareTo(Transaction.DEFAULT_TX_FEE) == 0) {
|
||||
logger.debug("Add Output: {} of value {}", out, out.getValue());
|
||||
t.addInput(out);
|
||||
}
|
||||
}
|
||||
|
||||
t.addOutput(Coin.ZERO, script);
|
||||
request = SendRequest.forTx(t);
|
||||
request.ensureMinRequiredFee = true;
|
||||
sr = null;
|
||||
|
||||
try {
|
||||
sr = wallet.sendCoins(request);
|
||||
} catch (final InsufficientMoneyException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug("SendRequest {}", request);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String executeCommand(String command) {
|
||||
|
||||
StringBuffer output = new StringBuffer();
|
||||
|
||||
Process p;
|
||||
try {
|
||||
p = Runtime.getRuntime().exec(command);
|
||||
p.waitFor();
|
||||
BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||
|
||||
String line = "";
|
||||
while ((line = reader.readLine())!= null) {
|
||||
output.append(line + "\n");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.tcpid.opretj.testapp;
|
||||
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.bitcoinj.params.RegTestParams;
|
||||
import org.bitcoinj.params.TestNet3Params;
|
||||
|
||||
public enum NetworkEnum {
|
||||
MAIN,
|
||||
PROD, // alias for MAIN
|
||||
TEST,
|
||||
REGTEST;
|
||||
|
||||
public NetworkParameters get() {
|
||||
switch(this) {
|
||||
case MAIN:
|
||||
case PROD:
|
||||
return MainNetParams.get();
|
||||
case TEST:
|
||||
return TestNet3Params.get();
|
||||
case REGTEST:
|
||||
default:
|
||||
return RegTestParams.get();
|
||||
}
|
||||
}
|
||||
}
|
21
opret-testapp/src/main/resources/logback.xml
Normal file
21
opret-testapp/src/main/resources/logback.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
|
||||
by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<logger name="org.tcpid.opretj.testapp.App" level="debug" />
|
||||
<logger name="org.tcpid.opretj.OPRETECParser" level="debug" />
|
||||
<!--
|
||||
<logger name="eckey.OPRETSimpleLogger" level="debug" />
|
||||
<logger name="eckey.OPRETSimpleParser" level="debug" />
|
||||
<logger name="eckey.OPRETBaseHandler" level="debug" />
|
||||
-->
|
||||
<root level="warn">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
26
opretj/.classpath
Normal file
26
opretj/.classpath
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
1
opretj/.gitignore
vendored
Normal file
1
opretj/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target/
|
23
opretj/.project
Normal file
23
opretj/.project
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>opretj</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
12
opretj/.settings/org.eclipse.jdt.core.prefs
Normal file
12
opretj/.settings/org.eclipse.jdt.core.prefs
Normal file
|
@ -0,0 +1,12 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
4
opretj/.settings/org.eclipse.m2e.core.prefs
Normal file
4
opretj/.settings/org.eclipse.m2e.core.prefs
Normal file
|
@ -0,0 +1,4 @@
|
|||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
54
opretj/pom.xml
Normal file
54
opretj/pom.xml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.tcpid</groupId>
|
||||
<artifactId>opret-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>opretj</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bitcoinj</groupId>
|
||||
<artifactId>bitcoinj-core</artifactId>
|
||||
<version>0.14.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.0.13</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
92
opretj/src/main/java/org/tcpid/opretj/OPRETBaseHandler.java
Normal file
92
opretj/src/main/java/org/tcpid/opretj/OPRETBaseHandler.java
Normal file
|
@ -0,0 +1,92 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.bitcoinj.utils.ListenerRegistration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public abstract class OPRETBaseHandler implements OPRETHandlerInterface {
|
||||
private static final Logger logger = LoggerFactory.getLogger(OPRETBaseHandler.class);
|
||||
private final CopyOnWriteArrayList<ListenerRegistration<OPRETChangeEventListener>> opReturnChangeListeners = new CopyOnWriteArrayList<ListenerRegistration<OPRETChangeEventListener>>();
|
||||
private final Map<List<Byte>, Long> magicBytes = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void addOPRET(byte[] magic, long earliestTime) {
|
||||
logger.debug("addMagicBytes: {} - Time {}", Utils.HEX.encode(magic), earliestTime);
|
||||
final List<Byte> blist = Bytes.asList(magic);
|
||||
magicBytes.put(blist, earliestTime);
|
||||
queueOnOPRETChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener object. Methods on this object are called when
|
||||
* scripts watched by this wallet change. The listener is executed by the
|
||||
* given executor.
|
||||
*/
|
||||
@Override
|
||||
public void addOPRETChangeEventListener(Executor executor, OPRETChangeEventListener listener) {
|
||||
// This is thread safe, so we don't need to take the lock.
|
||||
opReturnChangeListeners.add(new ListenerRegistration<OPRETChangeEventListener>(listener, executor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEarliestElementCreationTime() {
|
||||
long earliestTime = Long.MAX_VALUE;
|
||||
|
||||
for (Long t : magicBytes.values()) {
|
||||
if (t == Long.MAX_VALUE) {
|
||||
t = Utils.currentTimeSeconds();
|
||||
}
|
||||
earliestTime = Math.min(t, earliestTime);
|
||||
}
|
||||
|
||||
return earliestTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOPRETCount() {
|
||||
return magicBytes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<List<Byte>> getOPRETSet() {
|
||||
return magicBytes.keySet();
|
||||
}
|
||||
|
||||
protected void queueOnOPRETChanged() {
|
||||
for (final ListenerRegistration<OPRETChangeEventListener> registration : opReturnChangeListeners) {
|
||||
registration.executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
registration.listener.onOPRETChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOPRET(byte[] magic) {
|
||||
magicBytes.remove(Bytes.asList(magic));
|
||||
queueOnOPRETChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given event listener object. Returns true if the listener was
|
||||
* removed, false if that listener was never added.
|
||||
*/
|
||||
@Override
|
||||
public boolean removeOPRETChangeEventListener(OPRETChangeEventListener listener) {
|
||||
return ListenerRegistration.removeFromList(listener, opReturnChangeListeners);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
public interface OPRETChangeEventListener {
|
||||
void onOPRETChanged();
|
||||
}
|
136
opretj/src/main/java/org/tcpid/opretj/OPRETECParser.java
Normal file
136
opretj/src/main/java/org/tcpid/opretj/OPRETECParser.java
Normal file
|
@ -0,0 +1,136 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.bitcoinj.core.PartialMerkleTree;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.bitcoinj.utils.ListenerRegistration;
|
||||
import org.bitcoinj.utils.Threading;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class OPRETECParser extends OPRETBaseHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(OPRETECParser.class);
|
||||
private static final List<Byte> OPRET_MAGIC = Bytes.asList(Utils.HEX.decode("ec1d"));
|
||||
protected final Map<Sha256Hash, PartialMerkleTree> merkleHashMap = new HashMap<>();
|
||||
protected final Map<Sha256Hash, OPRETTransaction> transHashMap = new HashMap<>();
|
||||
private final CopyOnWriteArrayList<ListenerRegistration<OPRETECRevokeEventListener>> opReturnChangeListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* Adds an event listener object. Methods on this object are called when
|
||||
* scripts watched by this wallet change. The listener is executed by the
|
||||
* given executor.
|
||||
*/
|
||||
public void addOPRETECRevokeEventListener(OPRETECRevokeEventListener listener) {
|
||||
// This is thread safe, so we don't need to take the lock.
|
||||
opReturnChangeListeners
|
||||
.add(new ListenerRegistration<OPRETECRevokeEventListener>(listener, Threading.USER_THREAD));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given event listener object. Returns true if the listener was
|
||||
* removed, false if that listener was never added.
|
||||
*/
|
||||
public boolean removeOPRETECRevokeEventListener(OPRETECRevokeEventListener listener) {
|
||||
return ListenerRegistration.removeFromList(listener, opReturnChangeListeners);
|
||||
}
|
||||
|
||||
protected void queueOnOPRETRevoke(final byte[] pkhash, final byte[] sig) {
|
||||
for (final ListenerRegistration<OPRETECRevokeEventListener> registration : opReturnChangeListeners) {
|
||||
registration.executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
registration.listener.onOPRETRevoke(pkhash, sig);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkData(OPRETTransaction t1, OPRETTransaction t2) {
|
||||
final List<List<Byte>> opret_data = new ArrayList<>(t1.opretData);
|
||||
opret_data.addAll(t2.opretData);
|
||||
logger.debug("checking {}", opret_data);
|
||||
|
||||
List<Byte> chunk;
|
||||
chunk = opret_data.get(0);
|
||||
if (!chunk.equals(OPRET_MAGIC)) {
|
||||
logger.debug("0: != OPRET_MAGIC");
|
||||
return false;
|
||||
}
|
||||
|
||||
chunk = opret_data.get(1);
|
||||
if (chunk.size() != 1) {
|
||||
logger.debug("1: size != 1");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chunk.get(0) == (byte) 0xFE) {
|
||||
if (opret_data.size() != 8) {
|
||||
logger.debug("FE: size != 8");
|
||||
return false;
|
||||
}
|
||||
chunk = opret_data.get(4);
|
||||
if (!chunk.equals(OPRET_MAGIC)) {
|
||||
logger.debug("FE 4 != OPRET_MAGIC");
|
||||
return false;
|
||||
}
|
||||
if (!opret_data.get(2).equals(opret_data.get(6))) {
|
||||
logger.debug("FE 2 != 6");
|
||||
return false;
|
||||
}
|
||||
chunk = opret_data.get(5);
|
||||
if ((chunk.size() != 1) || (chunk.get(0) != (byte) 0xFF)) {
|
||||
logger.debug("FE 5 size!=1 or != FF");
|
||||
return false;
|
||||
}
|
||||
|
||||
return handleRevoke(t1, t2);
|
||||
} else {
|
||||
logger.debug("1: != 0xFE");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleRevoke(OPRETTransaction t1, OPRETTransaction t2) {
|
||||
final byte[] pkhash = Bytes.toArray(t1.opretData.get(2));
|
||||
final byte[] sig = Bytes.concat(Bytes.toArray(t1.opretData.get(3)), Bytes.toArray(t2.opretData.get(3)));
|
||||
|
||||
logger.debug("REVOKE PK {} - SIG {}", Utils.HEX.encode(pkhash), Utils.HEX.encode(sig));
|
||||
queueOnOPRETRevoke(pkhash, sig);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushData(final Sha256Hash blockHash, final Sha256Hash txHash, final Set<Sha256Hash> txPrevHash,
|
||||
final List<List<Byte>> opret_data) {
|
||||
final OPRETTransaction optrans = new OPRETTransaction(blockHash, txHash, txPrevHash, opret_data);
|
||||
logger.debug("pushData: {}", optrans);
|
||||
for (final Sha256Hash t : txPrevHash) {
|
||||
if (transHashMap.containsKey(t)) {
|
||||
final OPRETTransaction opprev = transHashMap.get(t);
|
||||
if (checkData(opprev, optrans)) {
|
||||
transHashMap.remove(t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
transHashMap.put(txHash, optrans);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushMerkle(final Sha256Hash blockHash, final PartialMerkleTree partialMerkleTree) {
|
||||
merkleHashMap.put(blockHash, partialMerkleTree);
|
||||
logger.info("block hash {}", blockHash);
|
||||
logger.info("Merkle Tree: {}", partialMerkleTree);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
public interface OPRETECRevokeEventListener {
|
||||
void onOPRETRevoke(final byte[] pkhash, final byte[] sig);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.bitcoinj.core.PartialMerkleTree;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
|
||||
public interface OPRETHandlerInterface {
|
||||
|
||||
void addOPRET(byte[] magic, long earliestTime);
|
||||
|
||||
void addOPRETChangeEventListener(Executor executor, OPRETChangeEventListener listener);
|
||||
|
||||
long getEarliestElementCreationTime();
|
||||
|
||||
int getOPRETCount();
|
||||
|
||||
Set<List<Byte>> getOPRETSet();
|
||||
|
||||
void pushData(final Sha256Hash h, final Sha256Hash sha256Hash, final Set<Sha256Hash> txprev,
|
||||
final List<List<Byte>> myList);
|
||||
|
||||
void pushMerkle(final Sha256Hash sha256Hash, final PartialMerkleTree partialMerkleTree);
|
||||
|
||||
void removeOPRET(byte[] magic);
|
||||
|
||||
boolean removeOPRETChangeEventListener(OPRETChangeEventListener listener);
|
||||
|
||||
}
|
41
opretj/src/main/java/org/tcpid/opretj/OPRETSimpleLogger.java
Normal file
41
opretj/src/main/java/org/tcpid/opretj/OPRETSimpleLogger.java
Normal file
|
@ -0,0 +1,41 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bitcoinj.core.PartialMerkleTree;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class OPRETSimpleLogger extends OPRETBaseHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(OPRETSimpleLogger.class);
|
||||
|
||||
@Override
|
||||
public void pushData(final Sha256Hash blockHash, final Sha256Hash txHash, final Set<Sha256Hash> txPrevHash,
|
||||
final List<List<Byte>> opret_data) {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
final StringBuilder bufPrev = new StringBuilder();
|
||||
|
||||
for (final List<Byte> d : opret_data) {
|
||||
buf.append(Utils.HEX.encode(Bytes.toArray(d)));
|
||||
buf.append(" ");
|
||||
}
|
||||
|
||||
for (final Sha256Hash d : txPrevHash) {
|
||||
bufPrev.append(d.toString());
|
||||
bufPrev.append(" ");
|
||||
}
|
||||
logger.info("Received in Block: {}\nTX: {}\nTxPrev: {}\nData: {}", blockHash, txHash, bufPrev, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushMerkle(final Sha256Hash blockHash, final PartialMerkleTree partialMerkleTree) {
|
||||
logger.info("block hash {}", blockHash);
|
||||
logger.info("Merkle Tree: {}", partialMerkleTree);
|
||||
}
|
||||
}
|
50
opretj/src/main/java/org/tcpid/opretj/OPRETTransaction.java
Normal file
50
opretj/src/main/java/org/tcpid/opretj/OPRETTransaction.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class OPRETTransaction implements Serializable {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 4234625243756902517L;
|
||||
public final Sha256Hash blockHash;
|
||||
public final Sha256Hash txHash;
|
||||
public final Set<Sha256Hash> txPrevHash;
|
||||
public final List<List<Byte>> opretData;
|
||||
|
||||
public OPRETTransaction(final Sha256Hash blockHash, final Sha256Hash txHash, final Set<Sha256Hash> txPrevHash,
|
||||
final List<List<Byte>> opret_data) {
|
||||
this.blockHash = blockHash;
|
||||
this.txHash = txHash;
|
||||
this.txPrevHash = txPrevHash;
|
||||
this.opretData = opret_data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
buf.append("Received in Block: ").append(blockHash).append("\n");
|
||||
buf.append("TX: ").append(txHash).append("\n");
|
||||
buf.append("TxPrev: ");
|
||||
|
||||
for (final Sha256Hash d : txPrevHash) {
|
||||
buf.append(d.toString());
|
||||
buf.append(" ");
|
||||
}
|
||||
buf.append("\n");
|
||||
buf.append("Data: ");
|
||||
for (final List<Byte> d : opretData) {
|
||||
buf.append(Utils.HEX.encode(Bytes.toArray(d)));
|
||||
buf.append(" ");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
194
opretj/src/main/java/org/tcpid/opretj/OPRETWallet.java
Normal file
194
opretj/src/main/java/org/tcpid/opretj/OPRETWallet.java
Normal file
|
@ -0,0 +1,194 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bitcoinj.core.Block;
|
||||
import org.bitcoinj.core.BlockChain;
|
||||
import org.bitcoinj.core.BloomFilter;
|
||||
import org.bitcoinj.core.FilteredBlock;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Peer;
|
||||
import org.bitcoinj.core.ScriptException;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.StoredBlock;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.bitcoinj.core.VerificationException;
|
||||
import org.bitcoinj.core.listeners.BlocksDownloadedEventListener;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptChunk;
|
||||
import org.bitcoinj.wallet.KeyChainGroup;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener, OPRETChangeEventListener {
|
||||
|
||||
private final OPRETHandlerInterface opbs;
|
||||
private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class);
|
||||
|
||||
private final Set<Sha256Hash> blocksToStore = new HashSet<>();
|
||||
protected final Map<Sha256Hash, Transaction> pendingTransactions;
|
||||
|
||||
public OPRETWallet(NetworkParameters params, KeyChainGroup keyChainGroup, OPRETHandlerInterface bs) {
|
||||
super(params, keyChainGroup);
|
||||
opbs = bs;
|
||||
pendingTransactions = new HashMap<Sha256Hash, Transaction>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
|
||||
beginBloomFilterCalculation();
|
||||
try {
|
||||
final BloomFilter filter = super.getBloomFilter(size, falsePositiveRate, nTweak);
|
||||
for (final List<Byte> magic : opbs.getOPRETSet()) {
|
||||
logger.debug("Magic add bloom: {}", Utils.HEX.encode(Bytes.toArray(magic)));
|
||||
filter.insert(Bytes.toArray(magic));
|
||||
}
|
||||
return filter;
|
||||
} finally {
|
||||
endBloomFilterCalculation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBloomFilterElementCount() {
|
||||
beginBloomFilterCalculation();
|
||||
try {
|
||||
logger.debug("Magic Bytes Size: {}", opbs.getOPRETCount());
|
||||
return super.getBloomFilterElementCount() + opbs.getOPRETCount();
|
||||
} finally {
|
||||
endBloomFilterCalculation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEarliestKeyCreationTime() {
|
||||
long earliestTime = opbs.getEarliestElementCreationTime();
|
||||
|
||||
if (earliestTime == Long.MAX_VALUE) {
|
||||
earliestTime = Utils.currentTimeSeconds();
|
||||
}
|
||||
|
||||
return Math.min(super.getEarliestKeyCreationTime(), earliestTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPendingTransactionRelevant(Transaction tx) throws ScriptException {
|
||||
logger.debug("isPendingTransactionRelevant {}", tx.getHashAsString());
|
||||
|
||||
if (pendingTransactions.containsValue(tx)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isTransactionOPReturn(tx) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<List<Byte>> isTransactionOPReturn(Transaction tx) throws ScriptException {
|
||||
final Set<List<Byte>> magicBytes = opbs.getOPRETSet();
|
||||
final List<List<Byte>> myList = new ArrayList<>();
|
||||
|
||||
for (final TransactionOutput out : tx.getOutputs()) {
|
||||
final Script script = out.getScriptPubKey();
|
||||
final List<ScriptChunk> chunks = script.getChunks();
|
||||
if (chunks.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!chunks.get(0).equalsOpCode(OP_RETURN)) {
|
||||
continue;
|
||||
}
|
||||
boolean found = false;
|
||||
|
||||
for (final ScriptChunk chunk : chunks) {
|
||||
if (chunk.data == null) {
|
||||
continue;
|
||||
}
|
||||
final List<Byte> magic = Bytes.asList(chunk.data);
|
||||
|
||||
if (chunk.equalsOpCode(OP_RETURN)) {
|
||||
continue;
|
||||
} else {
|
||||
myList.add(magic);
|
||||
}
|
||||
|
||||
if (magicBytes.contains(magic)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found == true) {
|
||||
return myList;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlocksDownloaded(Peer peer, Block block, FilteredBlock filteredBlock, int blocksLeft) {
|
||||
if (!blocksToStore.contains(block.getHash())) {
|
||||
return;
|
||||
}
|
||||
|
||||
opbs.pushMerkle(block.getHash(), filteredBlock.getPartialMerkleTree());
|
||||
|
||||
blocksToStore.remove(block.getHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOPRETChanged() {
|
||||
queueOnScriptsChanged(null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType,
|
||||
int relativityOffset) throws VerificationException {
|
||||
|
||||
super.receiveFromBlock(tx, block, blockType, relativityOffset);
|
||||
|
||||
final List<List<Byte>> myList = isTransactionOPReturn(tx);
|
||||
|
||||
if (myList == null) {
|
||||
logger.debug("False Positive Transaction {}", tx.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
final Set<Sha256Hash> txprev = new HashSet<>();
|
||||
|
||||
for (final TransactionInput in : tx.getInputs()) {
|
||||
try {
|
||||
final Transaction t = pendingTransactions.get(in.getOutpoint().getHash());
|
||||
txprev.add(t.getHash());
|
||||
pendingTransactions.remove(t);
|
||||
} catch (final Exception e) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
pendingTransactions.put(tx.getHash(), tx);
|
||||
|
||||
final Sha256Hash h = block.getHeader().getHash();
|
||||
if (!blocksToStore.contains(h)) {
|
||||
blocksToStore.add(h);
|
||||
}
|
||||
|
||||
opbs.pushData(h, tx.getHash(), txprev, myList);
|
||||
}
|
||||
}
|
60
opretj/src/main/java/org/tcpid/opretj/OPRETWalletAppKit.java
Normal file
60
opretj/src/main/java/org/tcpid/opretj/OPRETWalletAppKit.java
Normal file
|
@ -0,0 +1,60 @@
|
|||
package org.tcpid.opretj;
|
||||
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.kits.WalletAppKit;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.bitcoinj.store.SPVBlockStore;
|
||||
import org.bitcoinj.utils.Threading;
|
||||
import org.bitcoinj.wallet.KeyChainGroup;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.bitcoinj.wallet.WalletProtobufSerializer.WalletFactory;
|
||||
|
||||
public class OPRETWalletAppKit extends WalletAppKit {
|
||||
// private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class);
|
||||
private final OPRETHandlerInterface opbs;
|
||||
|
||||
public OPRETWalletAppKit(NetworkParameters params, File directory, String filePrefix, OPRETHandlerInterface bs) {
|
||||
super(params, directory, filePrefix);
|
||||
opbs = bs;
|
||||
walletFactory = new WalletFactory() {
|
||||
@Override
|
||||
public Wallet create(NetworkParameters params, KeyChainGroup keyChainGroup) {
|
||||
return new OPRETWallet(params, keyChainGroup, opbs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetupCompleted() {
|
||||
final OPRETWallet wallet = opretwallet();
|
||||
opbs.addOPRETChangeEventListener(Threading.USER_THREAD, wallet);
|
||||
// TODO: remove
|
||||
wallet.reset();
|
||||
peerGroup().addBlocksDownloadedEventListener(wallet);
|
||||
}
|
||||
|
||||
public OPRETWallet opretwallet() throws RuntimeException, IllegalStateException {
|
||||
checkState((state() == State.STARTING) || (state() == State.RUNNING), "Cannot call until startup is complete");
|
||||
final Wallet w = wallet();
|
||||
if (w instanceof OPRETWallet) {
|
||||
return (OPRETWallet) w;
|
||||
} else {
|
||||
throw new RuntimeException("wallet != OPTRETWallet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockStore provideBlockStore(File file) throws BlockStoreException {
|
||||
// TODO: save state
|
||||
if (params.getId().equals(NetworkParameters.ID_REGTEST) || params.getId().equals(NetworkParameters.ID_TESTNET)) {
|
||||
file.deleteOnExit();
|
||||
}
|
||||
return new SPVBlockStore(params, file);
|
||||
}
|
||||
}
|
11
pom.xml
Normal file
11
pom.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.tcpid</groupId>
|
||||
<artifactId>opret-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>opretj</module>
|
||||
<module>opret-testapp</module>
|
||||
</modules>
|
||||
</project>
|
Loading…
Reference in a new issue