package com.google.bitcoin.core;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.Traverser.Order;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.graphdb.index.IndexManager;
public class GraphWallet implements Noteable, Nodeable, Accountable {
public final static Comparator<GraphWallet> ADDRESS_COUNT_ORDER = new Comparator<GraphWallet>() {
public int compare(GraphWallet t1, GraphWallet t2) {
return -t1.addressCount().compareTo(t2.addressCount());
}
};
public BigInteger cachedTotalIncoming;
public BigInteger cachedTotalOutgoing;
private Node node;
public String notes="";
public String label="";
private static GraphWallet coinbaseWallet;
public GraphWallet(){
notes="";
label="";
}
public static GraphWallet coinbaseWallet(GraphDatabaseService graph){
if(coinbaseWallet==null){
Index<Node> namedNodes=graph.index().forNodes("namedNodes");
IndexHits<Node> hits= namedNodes.get("name", "coinbaseWallet");
Node node=hits.getSingle();
if(node!=null){
coinbaseWallet=new GraphWallet(node);
}
else{
org.neo4j.graphdb.Transaction tx = graph.beginTx();
try{
coinbaseWallet=new GraphWallet();
coinbaseWallet.label="COINBASE";
coinbaseWallet.save(graph);
namedNodes.add(coinbaseWallet.node(),"name", "coinbaseWallet");
tx.success();
}
finally {
tx.finish();
}
}
}
return coinbaseWallet;
}
public boolean isLabelled(){
return !label.equals("");
}
public GraphWallet(Node n) {
node=n;
notes="";
label="";
if(n.hasProperty("label"))
label=(String) n.getProperty("label");
if(n.hasProperty("notes"))
notes=(String) n.getProperty("notes");
}
@Override
public Node node() {
// TODO Auto-generated method stub
return node;
}
@Override
public void save(GraphDatabaseService graph) {
org.neo4j.graphdb.Transaction tx = graph.beginTx();
try {
IndexManager index=graph.index();
Index<Node> walletIndex=index.forNodes("wallets");
if(node==null){
node=graph.createNode();
}
if(node.hasProperty("label")){
node.removeProperty("label");
walletIndex.remove(node,"label");
}
if(node.hasProperty("notes")){
node.removeProperty("notes");
}
if(!label.equals("")){
node.setProperty("label",label);
walletIndex.add(node, "label", label);
}
if(!notes.equals("")){
node.setProperty("notes",notes);
}
tx.success();
}
finally{
tx.finish();
}
}
public boolean equals(Object rhs){
return rhs instanceof GraphWallet && ((GraphWallet) rhs).node().equals(node);
}
public Integer addressCount(){
if(node.hasProperty("addressCount")){
return (Integer) node.getProperty("addressCount");
}
return 1;
}
public ArrayList<GraphAddress> addresses(){
ArrayList<GraphAddress> list=new ArrayList<GraphAddress>();
Collection<Node> nodeList= node.traverse( Order.BREADTH_FIRST,
StopEvaluator.DEPTH_ONE, ReturnableEvaluator.ALL_BUT_START_NODE,
GraphRelationships.HAS_ADDRESS, Direction.OUTGOING ).getAllNodes();
for(Node n : nodeList){
try {
list.add(new GraphAddress(n));
} catch (AddressFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Collections.sort(list,Nodeable.NODE_ORDER);
return list;
}
public static void processTransaction(GraphTransaction t){
int time=t.createdAt;
ArrayList<GraphWallet> wallets=new ArrayList<GraphWallet>();
for(GraphTransactionInput i : t.inputs){
if(i.isCoinBase() || i.address()==null){
continue;
}
GraphWallet w=i.address().wallet();
if(!wallets.contains(w)){
wallets.add(w);
}
}
GraphWallet main;
if(wallets.size()!=0){
Collections.sort(wallets,ADDRESS_COUNT_ORDER);
main=wallets.get(0);
System.out.println("main wallet merge count="+main.addressCount());
for(int i=1;i<wallets.size();i++){
System.out.println("merging wallet "+wallets.get(i));
main.mergeWallet(wallets.get(i));
}
} else {
main=GraphWallet.coinbaseWallet(t.node().getGraphDatabase());
}
for(GraphTransactionOutput o : t.outputs){
GraphAddress addr=o.address();
if(addr==null){
continue;
}
GraphWallet w=addr.wallet();
Relationship r=main.node().createRelationshipTo(w.node(), GraphRelationships.PAYMENT);
r.setProperty("time", time);
r.setProperty("value",o.node().getProperty("value"));
r.setProperty("transaction_id", t.node().getId());
}
}
public void mergeWallet(GraphWallet rhs){
if(this.equals(rhs)){
return;
}
GraphDatabaseService graph = node.getGraphDatabase();
IndexManager index=graph.index();
Index<Node> walletIndex=index.forNodes("wallets");
Iterator<Relationship> i=rhs.node().getRelationships(Direction.OUTGOING,GraphRelationships.HAS_ADDRESS).iterator();
ArrayList<Relationship> toDelete=new ArrayList<Relationship>();
boolean needsSave=false;
if(!rhs.label.equals("")){
if(label.equals("")){
label=rhs.label;
}
else{
label=label+", "+rhs.label;
}
needsSave=true;
}
if(!rhs.notes.equals("")){
if(notes.equals("")){
notes=rhs.notes;
}
else{
notes=notes+", "+rhs.notes;
}
needsSave=true;
}
if(needsSave){
save(node.getGraphDatabase());
}
while(i.hasNext()){
Relationship r=i.next();
Node addr=r.getEndNode();
toDelete.add(r);
node.createRelationshipTo(addr, GraphRelationships.HAS_ADDRESS );
}
i=rhs.node().getRelationships(Direction.BOTH,GraphRelationships.PAYMENT).iterator();
while(i.hasNext()){
Relationship r=i.next();
Relationship r2;
Node e=r.getEndNode();
Node s=r.getStartNode();
if(s.equals(rhs.node()) && e.equals(rhs.node())){
r2=node.createRelationshipTo(node, GraphRelationships.PAYMENT);
}
else if(s.equals(rhs.node())){
r2=node.createRelationshipTo(e, GraphRelationships.PAYMENT);
}
else {
r2=s.createRelationshipTo(node, GraphRelationships.PAYMENT);
}
r2.setProperty("time", r.getProperty("time"));
r2.setProperty("value", r.getProperty("value"));
r2.setProperty("transaction_id", r.getProperty("transaction_id"));
toDelete.add(r);
}
for(Relationship r : toDelete){
r.delete();
}
walletIndex.remove(rhs.node());
node.setProperty("addressCount",this.addressCount()+rhs.addressCount());
rhs.node().delete();
System.out.println("deleting "+rhs.node().getId());
}
@Override
public BigInteger incomingAmount(GraphTransaction t){
return t.incomingAmountForWallet(this);
}
@Override
public BigInteger outgoingAmount(GraphTransaction t) {
// TODO Auto-generated method stub
return t.outgoingAmountForWallet(this);
}
public ArrayList<GraphTransaction> transactions(){
return transactions(false);
}
public ArrayList<GraphTransaction> transactions(boolean precache) {
ArrayList<GraphTransaction> list=new ArrayList<GraphTransaction>();
HashMap<Node,Boolean> contains=new HashMap<Node,Boolean>();
HashMap<Node,Boolean> outWalletMap=new HashMap<Node,Boolean>();
HashMap<Node,Boolean> inWalletMap=new HashMap<Node,Boolean>();
long incoming=0;
long outgoing=0;
for(Relationship r : node.getRelationships(Direction.BOTH, GraphRelationships.PAYMENT)){
Node n=node().getGraphDatabase().getNodeById((Long) r.getProperty("transaction_id"));
inWalletMap.put(r.getStartNode(), true);
outWalletMap.put(r.getEndNode(), true);
GraphTransaction t=new GraphTransaction(n,precache);
t.cachedIsIncoming=true;
if(!r.getStartNode().equals(r.getEndNode())){
long v=(Long) r.getProperty("value");
if(r.getStartNode().equals(node)){
outgoing+=v;
t.cachedIsIncoming=false;
}
else{
incoming+=v;
}
}
if(!contains.containsKey(t.node())){
list.add(t);
contains.put(t.node(), true);
}
}
cachedTotalIncoming=BigInteger.valueOf(incoming);
cachedTotalOutgoing=BigInteger.valueOf(outgoing);
Collections.sort(list, Timeable.TIME_ORDER);
GraphTransaction first=list.get(0);
if(first!=null){
first.incomingWallets=inWalletMap.size()-1;
first.outgoingWallets=outWalletMap.size()-1;
}
return list;
}
public String label() {
if(!label.equals("")){
return label;
}
return Long.valueOf(node.getId()).toString();
}
@Override
public void setLabel(String label) {
this.label=label;
}
@Override
public void setNotes(String notes) {
this.notes=notes;
}
@Override
public String getLabel() {
// TODO Auto-generated method stub
return label;
}
@Override
public String getNotes() {
// TODO Auto-generated method stub
return notes;
}
public void save(){
save(node().getGraphDatabase());
}
@Override
public BigInteger cachedTotalIncoming() {
return cachedTotalIncoming;
}
@Override
public BigInteger cachedTotalOutgoing() {
// TODO Auto-generated method stub
return cachedTotalOutgoing;
}
}