package com.google.bitcoin.core; import static com.google.bitcoin.core.Utils.doubleDigest; import static com.google.bitcoin.core.Utils.reverseBytes; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexManager; public class GraphTransaction extends Transaction implements GraphSaveable, Nodeable, Timeable { public final static Comparator<Relationship> R_INDEX_ORDER = new Comparator<Relationship>() { public int compare(Relationship r1, Relationship r2) { Integer i1=(!r1.hasProperty("index") ? 0 : (Integer) r1.getProperty("index")); Integer i2=(!r2.hasProperty("index") ? 0 : (Integer) r2.getProperty("index")); return i1.compareTo(i2); } }; private static MapCache<String, Node> cache=new MapCache<String,Node>(2000); private Node node; public int createdAt; public int index; public int outgoingWallets=0; public int incomingWallets=0; public BigInteger coinbaseValue; public ArrayList<GraphTransactionInput> inputs; public ArrayList<GraphTransactionOutput> outputs; public Boolean cachedIsIncoming=null; public GraphTransaction(NetworkParameters params) { super(params); // TODO Auto-generated constructor stub } public GraphTransaction(NetworkParameters params, byte[] payloadBytes) throws ProtocolException { super(params, payloadBytes); // TODO Auto-generated constructor stub } public GraphTransaction(NetworkParameters params, byte[] payload, int offset) throws ProtocolException { super(params, payload, offset); // TODO Auto-generated constructor stub } public GraphTransaction(NetworkParameters params, Node n){ super(params); this.node=n; /// this.version=(Long) node.getProperty("version"); /// this.lockTime=(Long) node.getProperty("lockTime"); this.createdAt=(Integer) node.getProperty("createdAt"); } public ArrayList<GraphWallet> getIncomingWallets(){ ArrayList<GraphWallet> w=new ArrayList<GraphWallet>(); GraphWallet w2; for(GraphTransactionInput t : inputs){ if(t.address()!=null){ w2=t.address().wallet(); if(!w.contains(w2)){ w.add(w2); } } } return w; } public ArrayList<GraphWallet> getOutgoingWallets(){ ArrayList<GraphWallet> w=new ArrayList<GraphWallet>(); GraphWallet w2; for(GraphTransactionOutput t : outputs){ if(t.address()!=null){ w2=t.address().wallet(); if(!w.contains(w2)){ w.add(w2); } } } return w; } public static Node findTransactionNode(GraphDatabaseService graph, String hash){ if(cache.containsKey(hash)){ return cache.get(hash); } IndexManager index=graph.index(); Index<Node> transactionIndex=index.forNodes("transactions"); Node n=transactionIndex.get("hash", hash).getSingle(); cache.put(hash, n); return n; } public BigInteger incomingAmountForAddress(GraphAddress a){ BigInteger amount=BigInteger.ZERO; for(TransactionOutput o : outputs){ GraphTransactionOutput o2=(GraphTransactionOutput) o; if(o2.address().equals(a)){ amount=amount.add(o2.value); } } return amount; } public BigInteger outgoingAmountForAddress(GraphAddress a){ BigInteger amount=BigInteger.ZERO; for(TransactionInput i : inputs){ GraphTransactionInput i2=(GraphTransactionInput) i; if(i2.isCoinBase()){ continue; } if(i2.address()!=null && i2.address().equals(a)){ amount=amount.add(i2.value); } } return amount; } public Relationship paymentFor(GraphTransactionOutput o){ GraphWallet src; GraphWallet dst=o.address().wallet(); for(Relationship r : dst.node().getRelationships(GraphRelationships.PAYMENT, Direction.INCOMING)){ if(((Long) r.getProperty("transaction_id")).equals(node().getId())){ return r; } } return null; } public BigInteger incomingAmountForWallet(GraphWallet w){ BigInteger amount=BigInteger.ZERO; for(GraphTransactionOutput o : outputs){ if(o.address().wallet().equals(w)){ amount=amount.add(o.value); } } return amount; } public BigInteger outgoingAmountForWallet(GraphWallet w){ BigInteger amount=BigInteger.ZERO; if(this.isCoinBase() || !inputs.get(0).address().wallet().equals(w)){ return amount; } for(GraphTransactionInput o : inputs){ if(o.address().wallet().equals(w)){ amount=amount.add(o.value); } } return amount; } private void load(Node n, boolean precache){ this.node=n; ArrayList<Relationship> r_inputs=new ArrayList<Relationship>(); ArrayList<Relationship> r_outputs=new ArrayList<Relationship>(); /* we never use these this.version=(Long) node.getProperty("version"); this.lockTime=(Long) node.getProperty("lockTime"); */ this.createdAt=(Integer) node.getProperty("time"); inputs=new ArrayList<GraphTransactionInput>(); outputs=new ArrayList<GraphTransactionOutput>(); for(Relationship r : node.getRelationships(Direction.OUTGOING, GraphRelationships.TRANSACTION_INPUT)){ r_inputs.add(r); } for(Relationship r : node.getRelationships(Direction.OUTGOING, GraphRelationships.TRANSACTION_OUTPUT)){ r_outputs.add(r); } Collections.sort(r_inputs,R_INDEX_ORDER); Collections.sort(r_outputs,R_INDEX_ORDER); for(Relationship r : r_inputs){ Node n2=r.getEndNode(); GraphTransactionInput in=new GraphTransactionInput(NetworkParameters.prodNet(),n2); if(precache){ if(in.address()!=null){ in.address().wallet(); } } inputs.add(in); } for(Relationship r : r_outputs){ Node n2=r.getEndNode(); GraphTransactionOutput out=new GraphTransactionOutput(NetworkParameters.prodNet(),n2); if(precache){ if(out.address()!=null){ out.address().wallet(); } } outputs.add(out); } } public GraphTransaction(Node n, boolean precache){ super(NetworkParameters.prodNet()); load(n,precache); } public GraphTransaction(Node n){ super(NetworkParameters.prodNet()); load(n,false); } public boolean equals(Object o){ return o instanceof GraphTransaction && ((GraphTransaction) o).node().equals(node); } @Override public Node node() { // TODO Auto-generated method stub return node; } @Override public void save(GraphDatabaseService graph) { if(node==null){ node=graph.createNode(); } // node.setProperty("version", this.version); // node.setProperty("lockTime", this.lockTime); node.setProperty("time", this.createdAt); node.setProperty("hash", this.getHash().hash); if(this.index!=0){ node.setProperty("index", this.index); } IndexManager index=graph.index(); Index<Node> transactionsIndex=index.forNodes("transactions"); transactionsIndex.remove(node); transactionsIndex.add(node, "hash", this.getHash().toString()); int i=0; for(GraphTransactionInput in : inputs){ in.index=i; in.save(graph); if(in.node()==null){ continue; } Relationship r=node.createRelationshipTo(in.node(), GraphRelationships.TRANSACTION_INPUT); if(i!=0){ r.setProperty("index", in.index); } i++; } i=0; for(GraphTransactionOutput out : outputs){ out.save(graph); Relationship r=node.createRelationshipTo(out.node(), GraphRelationships.TRANSACTION_OUTPUT); if(i!=0){ r.setProperty("index", i); } i++; } } /** * A coinbase transaction is one that creates a new coin. They are the first transaction in each block and their * value is determined by a formula that all implementations of BitCoin share. In 2011 the value of a coinbase * transaction is 50 coins, but in future it will be less. A coinbase transaction is defined not only by its * position in a block but by the data in the inputs. */ public boolean isCoinBase() { return inputs.get(0).isCoinBase(); } void parse() throws ProtocolException { version = readUint32(); // First come the inputs. long numInputs = readVarInt(); inputs = new ArrayList<GraphTransactionInput>((int)numInputs); for (int i = 0; i < numInputs; i++) { GraphTransactionInput input = new GraphTransactionInput(params, this, bytes, cursor); input.index=i; inputs.add(input); cursor += input.getMessageSize(); } // Now the outputs long numOutputs = readVarInt(); outputs = new ArrayList<GraphTransactionOutput>((int)numOutputs); for (int i = 0; i < numOutputs; i++) { GraphTransactionOutput output = new GraphTransactionOutput(params, this, bytes, cursor); output.index=i; outputs.add(output); cursor += output.getMessageSize(); } lockTime = readUint32(); // Store a hash, it may come in useful later (want to avoid reserialization costs). hash = new Sha256Hash(reverseBytes(doubleDigest(bytes, offset, cursor - offset))); } public int hashCode() { return node().hashCode(); } @Override public Date time() { return new Date((((long) createdAt) & 0xFFFFFFFFL)*1000L); } public void save(){ save(node().getGraphDatabase()); } }