├── README.md
├── main
├── java
│ └── org
│ │ └── altchain
│ │ └── neo4j
│ │ ├── bitcoind
│ │ ├── IsCoinbaseException.java
│ │ ├── NotCoinbaseException.java
│ │ ├── BitcoindNotRunningException.java
│ │ ├── Output.java
│ │ ├── Input.java
│ │ ├── Transaction.java
│ │ ├── Block.java
│ │ └── BitcoinD.java
│ │ ├── ETL.java
│ │ └── database
│ │ └── Database.java
└── resources
│ ├── logback.xml
│ └── sample-cypher-queries
├── NOTICE.txt
├── pom.xml
├── test
└── java
│ └── org
│ └── altchain
│ └── neo4j
│ ├── DatabaseTest.java
│ └── JsonRPCTest.java
└── LICENSE.txt
/README.md:
--------------------------------------------------------------------------------
1 | bitcoingraphdb
2 | ==============
3 |
4 | A Tool For Importing the Blitcoin Blockchain into the Neo4j Graph Database
5 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/bitcoind/IsCoinbaseException.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.bitcoind;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | public class IsCoinbaseException extends Exception {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/bitcoind/NotCoinbaseException.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.bitcoind;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | public class NotCoinbaseException extends Exception {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/bitcoind/BitcoindNotRunningException.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.bitcoind;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | public class BitcoindNotRunningException extends Exception {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Bitcoin Graph Database Browser
2 | Copyright 2013 Joshua Mark Zeidner
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/bitcoind/Output.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.bitcoind;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | import org.json.simple.JSONArray;
7 | import org.json.simple.JSONObject;
8 |
9 | public class Output {
10 |
11 | JSONObject obj;
12 |
13 | public Output( JSONObject o ){
14 | obj = o;
15 | }
16 |
17 | public Double getValue() {
18 | return (Double)obj.get("value");
19 | }
20 |
21 | public Long getN() {
22 | return (Long)obj.get("n");
23 | }
24 |
25 | public String getScriptSigAsm() {
26 | return (String)((JSONObject)obj.get("scriptPubKey")).get("asm");
27 | }
28 |
29 | public String getScriptSigHex() {
30 | return (String)((JSONObject)obj.get("scriptPubKey")).get("hex");
31 | }
32 |
33 | public Long getReqSigs() {
34 | return (Long)((JSONObject)obj.get("scriptPubKey")).get("reqSigs");
35 | }
36 |
37 | public String getType() {
38 | return (String)((JSONObject)obj.get("scriptPubKey")).get("type");
39 | }
40 |
41 | public JSONArray getAddresses() {
42 | return (JSONArray)((JSONObject)obj.get("scriptPubKey")).get("addresses");
43 |
44 | }
45 |
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/main/resources/sample-cypher-queries:
--------------------------------------------------------------------------------
1 |
2 | // here are few sample Cypher queries for interacting with the database
3 |
4 | // gets block #180
5 |
6 | START block=node:blocks("index:180")
7 | return block;
8 |
9 | START tx=node:transactions(hash="bd8cfe1837c88caa4ca37df77380d5b0af9692ed92ddbc247cd2aef388691d4f")
10 | return tx;
11 |
12 | // get output for a given Tx hash + output number
13 |
14 | START tx=node:transactions(hash="bd8cfe1837c88caa4ca37df77380d5b0af9692ed92ddbc247cd2aef388691d4f")
15 | MATCH (tx)-[out:has_output]-(output)
16 | where out.n = 0
17 | return output;
18 |
19 | // get blocks connected by tx
20 |
21 | START block=node:blocks("index:*")
22 | MATCH (block)-[:has_transaction]->(tx)-[:has_output]->(output)-[:output_to_input]->(input)<-[:has_input]-(tx2)<-[:has_transaction]-(block2)
23 | RETURN block,block2
24 |
25 |
26 | START address=node:addresses("address:*")
27 | MATCH (address)-[:address_out]->(output)-[output_to_input]->(input)<-[:has_input]-(tx)-[has_output]->(output2)<-[:address_out]-(address2)
28 | RETURN address, address2
29 |
30 |
31 | START address=node:addresses("address:*")
32 | MATCH (address)-[:address_out]->(output)-[output_to_input]->(input)<-[:has_input]-(tx)-[has_output]->(output2)<-[:address_out]-(address2)
33 | RETURN address, output.value, address2, output2.value
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/bitcoind/Input.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.bitcoind;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | import org.json.simple.JSONObject;
7 |
8 |
9 | public class Input {
10 |
11 | JSONObject obj;
12 |
13 | boolean isCoinbase;
14 |
15 | Input( JSONObject o ){
16 | obj = o;
17 | if ( o.get("coinbase") != null ) {
18 | isCoinbase = true;
19 | } else {
20 | isCoinbase = false;
21 | }
22 | }
23 |
24 | public boolean isCoinbase(){
25 | return isCoinbase;
26 | }
27 |
28 | public Long getSequence() {
29 | return (Long)obj.get("sequence");
30 | }
31 |
32 | public String getCoinbase() throws NotCoinbaseException {
33 | if ( !isCoinbase ) throw new NotCoinbaseException();
34 | return (String)obj.get("coinbase");
35 | }
36 |
37 | public String getTxid() throws IsCoinbaseException {
38 | if ( isCoinbase ) throw new IsCoinbaseException();
39 | return (String)obj.get("txid");
40 | }
41 |
42 | public Long getVout() throws IsCoinbaseException {
43 | if ( isCoinbase ) throw new IsCoinbaseException();
44 | return (Long)obj.get("vout");
45 | }
46 |
47 | public String getScriptSigAsm() throws IsCoinbaseException {
48 | if ( isCoinbase ) throw new IsCoinbaseException();
49 | return (String)((JSONObject)obj.get("scriptSig")).get("asm");
50 | }
51 |
52 | public String getScriptSigHex() throws IsCoinbaseException {
53 | if ( isCoinbase ) throw new IsCoinbaseException();
54 | return (String)((JSONObject)obj.get("scriptSig")).get("hex");
55 | }
56 |
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/bitcoind/Transaction.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.bitcoind;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 |
7 | import org.json.simple.JSONArray;
8 | import org.json.simple.JSONObject;
9 |
10 | public class Transaction {
11 |
12 | JSONObject obj;
13 |
14 | Input[] inputs;
15 |
16 | Output[] outputs;
17 |
18 | public Transaction( JSONObject o ){
19 | obj = o;
20 |
21 | JSONArray vin = (JSONArray)obj.get("vin");
22 | inputs = new Input[vin.size()]; int i=0;
23 | for( Object obj : vin ) {
24 | inputs[i++] = new Input((JSONObject)obj);
25 | }
26 |
27 | JSONArray vout = (JSONArray)obj.get("vout");
28 | outputs = new Output[vout.size()]; i=0;
29 | for( Object obj : vout ) {
30 | outputs[i++] = new Output((JSONObject)obj);
31 | }
32 |
33 | }
34 |
35 | public Output[] getOutputs() {
36 | return outputs;
37 | }
38 |
39 | public Input[] getInputs() {
40 | return inputs;
41 | }
42 |
43 | public String getJSONData() {
44 | return obj.toJSONString();
45 | }
46 |
47 | public String getHex() {
48 | return (String)obj.get("hex");
49 | }
50 |
51 | public String getTxID() {
52 | return (String)obj.get("txid");
53 | }
54 |
55 | public String getBlockHash() {
56 | return (String)obj.get("blockhash");
57 | }
58 |
59 | public Long getConfirmations() {
60 | return (Long)obj.get("confirmations");
61 | }
62 |
63 | public Long getTime() {
64 | return (Long)obj.get("time");
65 | }
66 |
67 | public Long getBlocktime() {
68 | return (Long)obj.get("blocktime");
69 | }
70 |
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | org.altchain
4 | bitcoingraphdb
5 | 0.0.1-SNAPSHOT
6 | bitcoingraphdb
7 | Bitcoin Block Chain Graph DB Importer
8 |
9 |
10 |
11 |
12 | junit
13 | junit
14 | 4.6
15 |
16 |
17 |
18 | org.slf4j
19 | slf4j-api
20 | 1.7.5
21 |
22 |
23 |
24 | ch.qos.logback
25 | logback-classic
26 | 1.0.13
27 |
28 |
29 |
30 | com.googlecode.json-simple
31 | json-simple
32 | 1.1
33 |
34 |
35 |
36 | org.apache.httpcomponents
37 | httpclient
38 | 4.1.1
39 |
40 |
41 |
42 | org.neo4j
43 | neo4j
44 | 1.9.1
45 |
46 |
47 |
48 | com.sun.jersey
49 | jersey-client
50 | 1.17.1
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/bitcoind/Block.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.bitcoind;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | import org.json.simple.JSONArray;
7 | import org.json.simple.JSONObject;
8 |
9 | public class Block {
10 |
11 | JSONObject obj;
12 |
13 | Block( JSONObject o ) {
14 | obj = o;
15 | }
16 |
17 | public String getJSONData() {
18 | return obj.toJSONString();
19 | }
20 |
21 | public JSONArray getTXArray() {
22 | return (JSONArray)obj.get("tx");
23 |
24 | }
25 |
26 | public String getPreviousBlockHash() {
27 | return (String)obj.get("previousblockhash");
28 | }
29 |
30 | public Long getConfirmations() {
31 | return (Long)obj.get("confirmations");
32 | }
33 |
34 | public String getHash() {
35 | return (String)obj.get("hash");
36 | }
37 |
38 | public String getNextBlockHash() {
39 | return (String)obj.get("nextblockhash");
40 | }
41 |
42 | public Double getDifficulty() {
43 | return (Double)obj.get("difficulty");
44 | }
45 |
46 | public String getMerkleRoot() {
47 | return (String)obj.get("merkleroot");
48 | }
49 |
50 | public Integer getSize() {
51 | return (Integer)obj.get("size");
52 | }
53 |
54 | public Integer getVersion() {
55 | return (Integer)obj.get("version");
56 | }
57 |
58 | public Integer getTime() {
59 | return (Integer)obj.get("time");
60 | }
61 |
62 | public Long getHeight() {
63 | return (Long)obj.get("height");
64 | }
65 |
66 | public Integer getNonce() {
67 | return (Integer)obj.get("nonce");
68 | }
69 |
70 | public String getBits() {
71 | return (String)obj.get("bits");
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/test/java/org/altchain/neo4j/DatabaseTest.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import java.net.URI;
6 | import java.util.Random;
7 | import java.util.UUID;
8 |
9 | import org.altchain.neo4j.database.Database;
10 | import org.junit.After;
11 | import org.junit.AfterClass;
12 | import org.junit.Before;
13 | import org.junit.BeforeClass;
14 | import org.junit.Test;
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 |
18 | public class DatabaseTest {
19 |
20 | final static Logger logger = LoggerFactory.getLogger(DatabaseTest.class);
21 |
22 | @BeforeClass
23 | public static void setUpBeforeClass() throws Exception {
24 | }
25 |
26 | @AfterClass
27 | public static void tearDownAfterClass() throws Exception {
28 | }
29 |
30 | @Before
31 | public void setUp() throws Exception {
32 | }
33 |
34 | @After
35 | public void tearDown() throws Exception {
36 | }
37 |
38 | @Test
39 | public void simpleNodeCreateTest() {
40 |
41 | Database d = new Database();
42 |
43 | if ( d.isServerRunning() )
44 |
45 | logger.debug("SERVER IS RUNNING.");
46 |
47 | URI firstNode = d.createNode();
48 | d.addProperty( firstNode, "hash", "2321" );
49 | d.addProperty( firstNode, "blah", "Joe Strummer" );
50 |
51 | }
52 |
53 | @Test
54 | public void addNodeToIndexText() {
55 |
56 | Database d = new Database();
57 |
58 | if ( d.isServerRunning() )
59 |
60 | logger.debug("SERVER IS RUNNING.");
61 |
62 | URI firstNode = d.createNode();
63 | d.addNodeToIndex(firstNode, "blocks", "hash", "eeeeeee");
64 |
65 |
66 | }
67 |
68 | @Test
69 | public void testGetNodeFromIndex() {
70 |
71 | UUID uniqueID = UUID.randomUUID();
72 |
73 | Database d = new Database();
74 |
75 | if ( d.isServerRunning() )
76 |
77 | logger.debug("SERVER IS RUNNING.");
78 |
79 | URI firstNode = d.createNode();
80 | logger.debug("MADE NODE: " + firstNode.toString() );
81 |
82 | d.addNodeToIndex(firstNode, "blocks", "test", uniqueID.toString() );
83 | URI gotNode = d.getNodeFromIndexKey("blocks", "test", uniqueID.toString() );
84 |
85 | logger.debug("GOT NODE: " + gotNode.toString() );
86 |
87 | // the two NODE URIs should be the same
88 | assert ( firstNode.toString().compareTo( gotNode.toString() ) == 0 );
89 |
90 | }
91 |
92 | @Test
93 | public void testCypherQuery() {
94 |
95 | Database d = new Database();
96 |
97 | if ( d.isServerRunning() )
98 |
99 | logger.debug("SERVER IS RUNNING.");
100 |
101 | URI rootURI = d.cypherQueryGetSingle("START root=node(0) RETURN root"); // should just return the root node
102 |
103 | logger.debug("RETURNED: " + rootURI.toString() );
104 |
105 | assert( rootURI.toString().compareTo("http://localhost:7474/db/data/node/0") == 0 );
106 |
107 | }
108 |
109 | @Test
110 | public void testOutputQuery() {
111 |
112 | Database d = new Database();
113 |
114 | if ( d.isServerRunning() )
115 |
116 | logger.debug("SERVER IS RUNNING.");
117 |
118 | URI rootURI = d.cypherQueryGetSingle("START tx=node:transactions(hash=\"bd8cfe1837c88caa4ca37df77380d5b0af9692ed92ddbc247cd2aef388691d4f\") MATCH (tx)-[out:has_output]-(output) where out.n = 0 return output;"
119 | ); // should just return the root node
120 |
121 | logger.debug("OUTPUT RETURNED: " + rootURI.toString() );
122 |
123 |
124 |
125 | }
126 |
127 |
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/test/java/org/altchain/neo4j/JsonRPCTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package org.altchain.neo4j;
5 |
6 |
7 | import org.altchain.neo4j.bitcoind.BitcoinD;
8 | import org.altchain.neo4j.bitcoind.BitcoindNotRunningException;
9 | import org.altchain.neo4j.bitcoind.Block;
10 | import org.altchain.neo4j.bitcoind.NotCoinbaseException;
11 | import org.altchain.neo4j.bitcoind.Transaction;
12 | import org.json.simple.JSONObject;
13 | import org.junit.After;
14 | import org.junit.AfterClass;
15 | import org.junit.Before;
16 | import org.junit.BeforeClass;
17 | import org.junit.Test;
18 | import org.slf4j.Logger;
19 | import org.slf4j.LoggerFactory;
20 |
21 | /**
22 | * @author jjzeidner
23 | *
24 | */
25 | public class JsonRPCTest {
26 |
27 | final static Logger logger = LoggerFactory.getLogger(JsonRPCTest.class);
28 |
29 | /**
30 | * @throws java.lang.Exception
31 | */
32 | @BeforeClass
33 | public static void setUpBeforeClass() throws Exception {
34 | }
35 |
36 | /**
37 | * @throws java.lang.Exception
38 | */
39 | @AfterClass
40 | public static void tearDownAfterClass() throws Exception {
41 | }
42 |
43 | /**
44 | * @throws java.lang.Exception
45 | */
46 | @Before
47 | public void setUp() throws Exception {
48 | }
49 |
50 | /**
51 | * @throws java.lang.Exception
52 | */
53 | @After
54 | public void tearDown() throws Exception {
55 | }
56 |
57 | /* @Test
58 | public void getinfo() {
59 |
60 | ETL client = new ETL();
61 | JSONObject json = client.getInfo();
62 | assert( json != null );
63 |
64 | logger.debug( "TEST| getinfo(): " + json.toString() );
65 |
66 | }
67 |
68 | @Test
69 | public void getnewaddress() {
70 |
71 | ETL client = new ETL();
72 | String address = client.getNewAddress("address-label");
73 | assert( address != null );
74 |
75 | logger.debug( "TEST| getnewaddress(): " + address );
76 |
77 | }*/
78 |
79 | @Test
80 | public void getbalance() throws BitcoindNotRunningException {
81 |
82 | BitcoinD client = new BitcoinD();
83 | Double balance = client.getBalance("address-label");
84 | assert( balance == 0d );
85 |
86 | logger.debug( "TEST| getbalance(): " + balance );
87 |
88 | }
89 |
90 | @Test
91 | public void getHash() throws BitcoindNotRunningException {
92 |
93 | BitcoinD client = new BitcoinD();
94 | String hash = client.getBlockHash(10);
95 |
96 | logger.debug( "TEST| getHash(1): " + hash );
97 |
98 | }
99 |
100 | @Test
101 | public void getBlock() throws BitcoindNotRunningException {
102 |
103 | BitcoinD client = new BitcoinD();
104 | Block block = client.getBlockData(170);
105 |
106 | logger.debug( "TEST| getBlock(1): " + block.getJSONData() );
107 | logger.debug( "TEST| getBlock(1) retrieve Hash: " + block.getHash() );
108 | logger.debug( "TEST| getBlock(1) retrieve Bits: " + block.getBits() );
109 | logger.debug( "TEST| getBlock(1) retrieve MerkleRoot: " + block.getMerkleRoot() );
110 |
111 | }
112 |
113 | @Test
114 | public void getTransaction() throws BitcoindNotRunningException {
115 |
116 | BitcoinD client = new BitcoinD();
117 |
118 | Transaction t = client.getTransactionData("d3ad39fa52a89997ac7381c95eeffeaf40b66af7a57e9eba144be0a175a12b11");
119 |
120 | logger.debug( "TEST| getTransaction(): " + t.getJSONData() );
121 | logger.debug( "TEST| getTransaction() retrieve value ['blockhash']: " + t.getBlockHash() );
122 | logger.debug( "TEST| num Inputs : " + t.getInputs().length );
123 | logger.debug( "TEST| num Ouputs : " + t.getOutputs().length );
124 | logger.debug( "TEST| Output 1 value : " + t.getOutputs()[0].getValue() );
125 |
126 | try {
127 | logger.debug( "TEST| Input 1 coinbase : " + t.getInputs()[0].getCoinbase() );
128 | } catch (NotCoinbaseException e) {
129 | // TODO Auto-generated catch block
130 | e.printStackTrace();
131 | }
132 |
133 | }
134 |
135 |
136 |
137 | }
138 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/bitcoind/BitcoinD.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.bitcoind;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | import java.io.IOException;
7 | import java.util.Arrays;
8 | import java.util.List;
9 | import java.util.UUID;
10 |
11 | import org.apache.http.HttpEntity;
12 | import org.apache.http.HttpResponse;
13 | import org.apache.http.ParseException;
14 | import org.apache.http.auth.AuthScope;
15 | import org.apache.http.auth.UsernamePasswordCredentials;
16 | import org.apache.http.client.ClientProtocolException;
17 | import org.apache.http.client.methods.HttpPost;
18 | import org.apache.http.entity.StringEntity;
19 | import org.apache.http.impl.client.DefaultHttpClient;
20 | import org.apache.http.util.EntityUtils;
21 | import org.json.simple.JSONArray;
22 | import org.json.simple.JSONObject;
23 | import org.json.simple.parser.JSONParser;
24 | import org.slf4j.Logger;
25 | import org.slf4j.LoggerFactory;
26 |
27 | public class BitcoinD {
28 |
29 | // this code originally began as a code fragment I found on the net. If you recognize your code, let me know, I'll credit you here.
30 |
31 | // ideally all the code should use Apache Jersey, but currently doesn't
32 |
33 |
34 | final static Logger logger = LoggerFactory.getLogger(BitcoinD.class);
35 |
36 | DefaultHttpClient httpclient = new DefaultHttpClient();
37 |
38 | final static String bitcoindID = "generated_by_armory";
39 | final static String bitcoinPassword = "6nkugwdEacEAgqjbCvvVyrgXcZj5Cxr38vTbZ513QJrf";
40 | final static String bitcoindHost = "localhost";
41 | final static int bitcoindPort = 8332;
42 |
43 | private static final String COMMAND_GET_BALANCE = "getbalance";
44 | private static final String COMMAND_GET_INFO = "getinfo";
45 | private static final String COMMAND_GET_NEW_ADDRESS = "getnewaddress";
46 | private static final String COMMAND_GET_BLOCK_HASH = "getblockhash";
47 |
48 |
49 |
50 | private JSONObject invokeRPC(String JSONRequestString) throws BitcoindNotRunningException {
51 | DefaultHttpClient httpclient = new DefaultHttpClient();
52 |
53 |
54 | JSONObject responseJsonObj = null;
55 |
56 | try {
57 |
58 | httpclient.getCredentialsProvider().setCredentials(new AuthScope( bitcoindHost , bitcoindPort ),
59 | new UsernamePasswordCredentials("generated_by_armory", "6nkugwdEacEAgqjbCvvVyrgXcZj5Cxr38vTbZ513QJrf"));
60 |
61 | StringEntity myEntity = new StringEntity( JSONRequestString );
62 | logger.debug( "JSON Request Object: " + JSONRequestString );
63 |
64 | HttpPost httppost = new HttpPost("http://" + this.bitcoindHost + ":" + this.bitcoindPort );
65 | httppost.setEntity(myEntity);
66 |
67 | logger.debug( "executing request: " + httppost.getRequestLine() );
68 |
69 | HttpEntity entity = null;
70 |
71 | try {
72 |
73 | HttpResponse response = httpclient.execute(httppost);
74 | entity = response.getEntity();
75 |
76 | logger.debug( "HTTP response: " + response.getStatusLine() );
77 |
78 | } catch ( Exception e ){
79 | logger.error( "CANNOT CONNECT TO BITCOIND. IS BITCOIN RUNNING?" );
80 | throw new BitcoindNotRunningException();
81 | }
82 |
83 | if (entity != null) {
84 |
85 | logger.debug( "Response content length: " + entity.getContentLength() );
86 |
87 | }
88 |
89 | JSONParser parser = new JSONParser();
90 | responseJsonObj = (JSONObject) parser.parse( EntityUtils.toString(entity) );
91 |
92 | } catch (ClientProtocolException e) {
93 | e.printStackTrace();
94 | } catch (IOException e) {
95 | e.printStackTrace();
96 | } catch (ParseException e) {
97 | e.printStackTrace();
98 | } catch (org.json.simple.parser.ParseException e) {
99 | e.printStackTrace();
100 | } finally {
101 |
102 | httpclient.getConnectionManager().shutdown();
103 |
104 | }
105 |
106 | return responseJsonObj;
107 |
108 | }
109 |
110 | public Double getBalance( String account ) throws BitcoindNotRunningException {
111 |
112 | Object[] params = { account };
113 | String requestString = String.format( "{\"id\":\"%s\",\"method\":\"getbalance\",\"params\":[\"%s\"]}", UUID.randomUUID().toString(), account );
114 | JSONObject json = invokeRPC( requestString );
115 |
116 | return (Double)json.get("result");
117 |
118 | }
119 |
120 |
121 | public String getBlockHash( int index ) throws BitcoindNotRunningException {
122 |
123 | String requestString = String.format( "{\"id\":\"%s\",\"method\":\"getblockhash\",\"params\":[%s]}", UUID.randomUUID().toString(), index );
124 | JSONObject json = invokeRPC( requestString );
125 |
126 | return (String)json.get("result");
127 |
128 | }
129 |
130 |
131 | public Block getBlockData( int index ) throws BitcoindNotRunningException {
132 |
133 | String hash = getBlockHash( index );
134 | String requestString = String.format( "{\"id\":\"%s\",\"method\":\"getblock\",\"params\":[\"%s\"]}", UUID.randomUUID().toString(), hash );
135 | JSONObject json = invokeRPC( requestString );
136 |
137 | return new Block( (JSONObject)json.get("result") );
138 |
139 | }
140 |
141 |
142 | public Transaction getTransactionData( String hash ) throws BitcoindNotRunningException {
143 |
144 | String requestString = String.format( "{\"id\":\"%s\",\"method\":\"getrawtransaction\",\"params\":[\"%s\",1]}", UUID.randomUUID().toString(), hash );
145 | JSONObject json = invokeRPC( requestString );
146 |
147 | return new Transaction( (JSONObject) json.get("result") );
148 |
149 | }
150 |
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/ETL.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j;
2 |
3 | // this code is copyright 2013 Joshua Mark Zeidner and
4 | // licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | import java.net.URI;
7 |
8 | import org.altchain.neo4j.bitcoind.BitcoinD;
9 | import org.altchain.neo4j.bitcoind.BitcoindNotRunningException;
10 | import org.altchain.neo4j.bitcoind.Block;
11 | import org.altchain.neo4j.bitcoind.Input;
12 | import org.altchain.neo4j.bitcoind.Output;
13 | import org.altchain.neo4j.bitcoind.Transaction;
14 | import org.altchain.neo4j.database.Database;
15 | import org.json.simple.JSONArray;
16 | import org.json.simple.JSONObject;
17 | import org.json.simple.JSONValue;
18 | import org.slf4j.Logger;
19 | import org.slf4j.LoggerFactory;
20 |
21 | public class ETL {
22 |
23 | final static long BLOCK_INDEX_START = 0;
24 | final static long BLOCK_INDEX_END = 25000;
25 |
26 |
27 | final static Logger logger = LoggerFactory.getLogger(ETL.class);
28 |
29 | BitcoinD bitcoind;
30 | Database database;
31 |
32 | ETL() {
33 |
34 | bitcoind = new BitcoinD();
35 | database = new Database();
36 |
37 | }
38 |
39 | @SuppressWarnings("static-access")
40 | public static void main(String[] args) {
41 |
42 | ETL etl = new ETL();
43 |
44 | for (long blocknum = BLOCK_INDEX_START; blocknum < BLOCK_INDEX_END; blocknum++) {
45 |
46 | logger.info("@@@@@@@@@@@@@@@@@@ processing block #" + blocknum);
47 |
48 | Block block = null;
49 | try {
50 | block = etl.bitcoind.getBlockData((int) blocknum);
51 | } catch (BitcoindNotRunningException e1) {
52 | logger.error("BITCOIND NOT RUNNING. Run Bitcoin-qt or Armory.");
53 | System.exit(1);
54 | }
55 |
56 | URI newblock = etl.database.createNode();
57 | etl.database.addProperty(newblock, "type", "block");
58 | etl.database.addProperty(newblock, "hash", block.getHash());
59 | etl.database.addProperty(newblock, "merkleroot",
60 | block.getMerkleRoot());
61 | etl.database.addProperty(newblock, "bits", block.getBits());
62 | etl.database.addProperty(newblock, "nextblockhash",
63 | block.getNextBlockHash());
64 | etl.database.addProperty(newblock, "previousblockhash",
65 | block.getPreviousBlockHash());
66 | etl.database.addProperty(newblock, "confirmations",
67 | block.getConfirmations());
68 | etl.database.addProperty(newblock, "difficulty",
69 | block.getDifficulty());
70 | etl.database.addProperty(newblock, "index", blocknum);
71 |
72 | JSONArray txs = block.getTXArray();
73 |
74 | int numTX = 0;
75 |
76 | for (Object txO : txs) {
77 | try {
78 | Transaction tx = etl.bitcoind
79 | .getTransactionData((String) txO);
80 | URI newTX = etl.database.createNode();
81 | etl.database.addProperty(newTX, "type", "transaction");
82 | etl.database.addProperty(newTX, "blockhash",
83 | tx.getBlockHash());
84 | etl.database.addProperty(newTX, "hex", tx.getHex());
85 | etl.database.addProperty(newTX, "blocktime",
86 | tx.getBlocktime());
87 | etl.database.addProperty(newTX, "confirmations",
88 | tx.getConfirmations());
89 | etl.database.addProperty(newTX, "time", tx.getTime());
90 | etl.database.addProperty(newTX, "hash", tx.getTxID());
91 |
92 | for (Input input : tx.getInputs()) {
93 |
94 | URI newInput = etl.database.createNode();
95 | if (input.isCoinbase()) {
96 | etl.database.addProperty(newInput, "coinbase",
97 | input.getCoinbase());
98 | etl.database.addProperty(newInput, "sequence",
99 | input.getSequence());
100 | } else { // non-coinbase tx
101 |
102 | etl.database.addProperty(newInput, "scriptSigAsm",
103 | input.getScriptSigAsm());
104 | etl.database.addProperty(newInput, "scriptSigHex",
105 | input.getScriptSigHex());
106 | etl.database.addProperty(newInput, "txid",
107 | input.getTxid());
108 | etl.database.addProperty(newInput, "vout",
109 | input.getVout());
110 |
111 | String getMatchingOutputQuery = String
112 | .format("START tx=node:transactions(hash=\"%s\") MATCH (tx)-[out:has_output]-(output) where out.n = %s return output;",
113 | input.getTxid(), input.getVout());
114 | URI matchingOutput = null;
115 | try {
116 | matchingOutput = etl.database
117 | .cypherQueryGetSingle(getMatchingOutputQuery);
118 | etl.database.addRelationship(matchingOutput,
119 | newInput, "output_to_input", "{}");
120 | } catch (Exception e) {
121 | logger.error(String
122 | .format("NO CORRESPONDING OUTPUT FOR INPUT, tx %s, output # %s",
123 | input.getTxid(),
124 | input.getVout()));
125 | }
126 |
127 | }
128 | etl.database.addProperty(newInput, "type", "input");
129 | URI relationshipUri = etl.database.addRelationship(
130 | newTX, newInput, "has_input", "{}");
131 |
132 | }
133 |
134 | for (Output output : tx.getOutputs()) {
135 |
136 | URI newOutput = etl.database.createNode();
137 |
138 | // TODO: req sigs is not an integer it's an array
139 | // etl.database.addProperty( newOutput, "ReqSigs",
140 | // output.getReqSigs() );
141 |
142 | etl.database.addProperty(newOutput, "type", "output");
143 | etl.database.addProperty(newOutput, "ScriptSigAsm",
144 | output.getScriptSigAsm());
145 | etl.database.addProperty(newOutput, "ScriptSigHex",
146 | output.getScriptSigHex());
147 | etl.database.addProperty(newOutput, "outputtype",
148 | output.getType());
149 | etl.database.addProperty(newOutput, "value",
150 | output.getValue());
151 | etl.database.addProperty(newOutput, "n", output.getN());
152 | etl.database.addProperty(newOutput, "addresses",
153 | output.getAddresses());
154 | for (Object address : output.getAddresses()) {
155 |
156 | // first convert the address to a string
157 | String addressString = address.toString();
158 | // logger.info("ADDRESS STRING: "+addressString);
159 |
160 | // now check and see if this address already exists
161 | URI addressNode = etl.database.getNodeFromIndexKey(
162 | "addresses", "address", addressString);
163 |
164 | // if not add it
165 | if (addressNode == null) {
166 | addressNode = etl.database.createNode();
167 | logger.info(">>NEW ADDRESS FOUND. "
168 | + addressString);
169 |
170 | etl.database.addProperty(addressNode,
171 | "address", addressString);
172 | etl.database.addProperty(addressNode, "type",
173 | "address");
174 | etl.database.addNodeToIndex(addressNode,
175 | "addresses", "address", addressString);
176 | }
177 |
178 | // add a relationship to the output
179 | URI relationshipUri = etl.database.addRelationship(
180 | addressNode,
181 | newOutput,
182 | "address_out",
183 | "{\"txid\":\"" + tx.getTxID()
184 | + "\",\"blockindex\":\""
185 | + block.getHeight() + "\"}");
186 | // logger.info( ">>>>ADDING ADDRESS RELATION " +
187 | // addressString );
188 | }
189 |
190 | URI relationshipUri = etl.database.addRelationship(
191 | newTX, newOutput, "has_output", "{ \"n\" : "
192 | + output.getN() + " }");
193 |
194 | // TODO: make each address a node
195 | // also make special relation 'appears741 first' in the
196 | // block it appears first
197 |
198 | }
199 |
200 | etl.database.addNodeToIndex(newTX, "transactions", "hash",
201 | tx.getTxID());
202 |
203 | // now create a relationship to the block
204 |
205 | URI relationshipUri = etl.database.addRelationship(
206 | newblock, newTX, "has_transaction",
207 | "{ \"number\" : " + numTX + " }");
208 |
209 | numTX++;
210 |
211 | } catch (Exception e) {
212 | logger.error("NO TX DATA: " + (String) txO + " blocknum: "
213 | + block.getHeight() + " error " + e.getMessage());
214 | e.printStackTrace();
215 | }
216 |
217 | }
218 |
219 | etl.database.addNodeToIndex(newblock, "blocks", "hash",
220 | block.getHash());
221 | etl.database.addNodeToIndex(newblock, "blocks", "index", blocknum);
222 |
223 | }
224 |
225 | }
226 |
227 | }
228 |
--------------------------------------------------------------------------------
/main/java/org/altchain/neo4j/database/Database.java:
--------------------------------------------------------------------------------
1 | package org.altchain.neo4j.database;
2 |
3 | //this code is copyright 2013 Joshua Mark Zeidner and
4 | //licensed under the Apache 2.0 license, see LICENSE.txt
5 |
6 | import java.io.IOException;
7 | import java.net.URI;
8 |
9 | import org.altchain.neo4j.bitcoind.BitcoinD;
10 | import org.apache.http.HttpEntity;
11 | import org.apache.http.HttpResponse;
12 | import org.apache.http.ParseException;
13 | import org.apache.http.auth.AuthScope;
14 | import org.apache.http.auth.UsernamePasswordCredentials;
15 | import org.apache.http.client.ClientProtocolException;
16 | import org.apache.http.client.methods.HttpPost;
17 | import org.apache.http.entity.StringEntity;
18 | import org.apache.http.impl.client.DefaultHttpClient;
19 | import org.apache.http.util.EntityUtils;
20 | import org.json.simple.JSONArray;
21 | import org.json.simple.JSONObject;
22 | import org.json.simple.JSONValue;
23 | import org.json.simple.parser.JSONParser;
24 | import org.slf4j.Logger;
25 | import org.slf4j.LoggerFactory;
26 | import com.sun.jersey.api.client.Client;
27 | import com.sun.jersey.api.client.ClientResponse;
28 | import com.sun.jersey.api.client.WebResource;
29 |
30 | import java.net.URI;
31 | import java.net.URISyntaxException;
32 | import java.util.ArrayList;
33 |
34 | import javax.ws.rs.core.MediaType;
35 |
36 | import com.sun.jersey.api.client.Client;
37 | import com.sun.jersey.api.client.ClientResponse;
38 | import com.sun.jersey.api.client.WebResource;
39 |
40 | public class Database {
41 |
42 | final static Logger logger = LoggerFactory.getLogger(Database.class);
43 |
44 | DefaultHttpClient httpclient = new DefaultHttpClient();
45 |
46 | private static final String SERVER_ROOT_URI = "http://localhost:7474/db/data/";
47 |
48 | public boolean isServerRunning(){
49 |
50 | WebResource resource = Client.create()
51 | .resource( SERVER_ROOT_URI );
52 |
53 | ClientResponse response = resource.get( ClientResponse.class );
54 |
55 | logger.debug( String.format( "GET on [%s], status code [%d]", SERVER_ROOT_URI, response.getStatus() ) );
56 | response.close();
57 |
58 | if (response.getStatus() == 200) return true;
59 | return false;
60 |
61 | }
62 |
63 |
64 | public static void addMetadataToProperty( URI relationshipUri,
65 | String name, String value ) throws URISyntaxException
66 | {
67 | URI propertyUri = new URI( relationshipUri.toString() + "/properties" );
68 | String entity = toJsonNameValuePairCollection( name, value );
69 | WebResource resource = Client.create()
70 | .resource( propertyUri );
71 | ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
72 | .type( MediaType.APPLICATION_JSON )
73 | .entity( entity )
74 | .put( ClientResponse.class );
75 |
76 | logger.debug( String.format(
77 | "PUT [%s] to [%s], status code [%d]", entity, propertyUri,
78 | response.getStatus() ) );
79 | response.close();
80 | }
81 |
82 | public static String toJsonNameValuePairCollection( String name,
83 | String value )
84 | {
85 | return String.format( "{ \"%s\" : \"%s\" }", name, value );
86 | }
87 |
88 | public static URI createNode()
89 | {
90 | final String nodeEntryPointUri = SERVER_ROOT_URI + "node";
91 |
92 | WebResource resource = Client.create()
93 | .resource( nodeEntryPointUri );
94 | ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
95 | .type( MediaType.APPLICATION_JSON )
96 | .entity( "{}" )
97 | .post( ClientResponse.class );
98 |
99 | final URI location = response.getLocation();
100 | logger.debug( String.format(
101 | "POST to [%s], status code [%d], location header [%s]",
102 | nodeEntryPointUri, response.getStatus(), location.toString() ) );
103 | response.close();
104 |
105 | return location;
106 | }
107 |
108 | public static URI addRelationship( URI startNode, URI endNode,
109 | String relationshipType, String jsonAttributes )
110 | throws URISyntaxException
111 | {
112 | URI fromUri = new URI( startNode.toString() + "/relationships" );
113 | String relationshipJson = generateJsonRelationship( endNode,
114 | relationshipType, jsonAttributes );
115 |
116 | WebResource resource = Client.create()
117 | .resource( fromUri );
118 | ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
119 | .type( MediaType.APPLICATION_JSON )
120 | .entity( relationshipJson )
121 | .post( ClientResponse.class );
122 |
123 | final URI location = response.getLocation();
124 | logger.debug( String.format(
125 | "POST to [%s], status code [%d], location header [%s]",
126 | fromUri, response.getStatus(), location.toString() ) );
127 |
128 | response.close();
129 | return location;
130 | }
131 | // END SNIPPET: insideAddRel
132 |
133 | public static String generateJsonRelationship( URI endNode,
134 | String relationshipType, String... jsonAttributes )
135 | {
136 | StringBuilder sb = new StringBuilder();
137 | sb.append( "{ \"to\" : \"" );
138 | sb.append( endNode.toString() );
139 | sb.append( "\", " );
140 |
141 | sb.append( "\"type\" : \"" );
142 | sb.append( relationshipType );
143 | if ( jsonAttributes == null || jsonAttributes.length < 1 )
144 | {
145 | sb.append( "\"" );
146 | }
147 | else
148 | {
149 | sb.append( "\", \"data\" : " );
150 | for ( int i = 0; i < jsonAttributes.length; i++ )
151 | {
152 | sb.append( jsonAttributes[i] );
153 | if ( i < jsonAttributes.length - 1 )
154 | { // Miss off the final comma
155 | sb.append( ", " );
156 | }
157 | }
158 | }
159 |
160 | sb.append( " }" );
161 | return sb.toString();
162 | }
163 |
164 | public static void addProperty( URI nodeUri, String propertyName,
165 | Object propertyValue )
166 | {
167 | // START SNIPPET: addProp
168 | String propertyUri = nodeUri.toString() + "/properties/" + propertyName;
169 | // http://localhost:7474/db/data/node/{node_id}/properties/{property_name}
170 |
171 | WebResource resource = Client.create()
172 | .resource( propertyUri );
173 |
174 | ClientResponse response = null;
175 |
176 | if (propertyValue.getClass() == String.class) {
177 | response = resource.accept(MediaType.APPLICATION_JSON)
178 | .type(MediaType.APPLICATION_JSON)
179 | .entity("\"" + propertyValue + "\"")
180 | .put(ClientResponse.class);
181 | } else {
182 | response = resource.accept(MediaType.APPLICATION_JSON)
183 | .type(MediaType.APPLICATION_JSON)
184 | .entity(propertyValue.toString()).put(ClientResponse.class);
185 | }
186 |
187 | logger.debug( String.format( "PUT to [%s], status code [%d]",
188 | propertyUri, response.getStatus() ) );
189 | response.close();
190 | }
191 |
192 |
193 | static final String NODE_INDEX_ROOT = "http://localhost:7474/db/data/index/node/";
194 |
195 | @SuppressWarnings("unchecked")
196 | public static URI addNodeToIndex( URI nodeUri, String indexName, String key, Object value ){
197 |
198 | String indexUri = NODE_INDEX_ROOT + indexName;
199 |
200 | WebResource resource = Client.create()
201 | .resource( indexUri );
202 |
203 | JSONObject params = new JSONObject();
204 | params.put( "value", value );
205 | params.put( "uri", nodeUri.toString() );
206 | params.put( "key", key );
207 |
208 | ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
209 | .type( MediaType.APPLICATION_JSON )
210 | .entity( params.toJSONString() )
211 | .post( ClientResponse.class );
212 |
213 | response.getEntity(String.class);
214 |
215 | final URI location = response.getLocation();
216 | logger.debug( String.format(
217 | "POST to [%s], status code [%d], location header [%s], JSON: %s",
218 | indexUri, response.getStatus(), location.toString(), params.toJSONString() ) );
219 |
220 | response.close();
221 | return location;
222 |
223 | }
224 |
225 |
226 | @SuppressWarnings("unchecked")
227 | public static URI cypherQueryGetSingle( String query ) {
228 |
229 | String cypherUri = "http://localhost:7474/db/data/cypher";
230 |
231 | WebResource resource = Client.create()
232 | .resource( cypherUri );
233 |
234 | JSONObject queryObject = new JSONObject();
235 | queryObject.put( "query", query );
236 | queryObject.put( "params", new JSONObject() );
237 |
238 | //logger.info("query: "+query );
239 |
240 | ClientResponse clientResponse = resource.accept( MediaType.APPLICATION_JSON )
241 | .type( MediaType.APPLICATION_JSON )
242 | .entity( queryObject.toJSONString() )
243 | .post( ClientResponse.class );
244 |
245 | String jsonString = clientResponse.getEntity( String.class );
246 |
247 | Object JSONResponse = JSONValue.parse( jsonString );
248 | //logger.info("json repsonse: "+jsonString );
249 |
250 | String path = null;
251 | try {
252 | path = (String) ((JSONObject)((JSONArray)((JSONArray)((JSONObject)JSONResponse).get("data")).get(0)).get(0)).get("self");
253 | } catch ( Exception e ){
254 | logger.info("PROBLEM WITH CYPHER QUERY: "+query+" json response: "+JSONResponse.toString());
255 | }
256 |
257 | URI location = URI.create(path);
258 | logger.debug( String.format(
259 | "POST to [%s], status code [%d], location header [%s], JSON: %s",
260 | cypherUri, clientResponse.getStatus(), location.toString(), queryObject.toJSONString() ) );
261 |
262 | clientResponse.close();
263 |
264 | return location;
265 |
266 | }
267 |
268 | public static URI getNodeFromIndexKey( String indexName, String key, Object value ){
269 |
270 | String indexUri = NODE_INDEX_ROOT + indexName + "/" + key + "/" + value.toString();
271 |
272 | WebResource resource = Client.create()
273 | .resource( indexUri );
274 |
275 | ClientResponse clientResponse = resource.accept( MediaType.APPLICATION_JSON )
276 | .type( MediaType.APPLICATION_JSON )
277 | .get( ClientResponse.class );
278 |
279 | String jsonString = clientResponse.getEntity( String.class );
280 | Object JSONResponse = JSONValue.parse( jsonString );
281 |
282 | // now get the node URI out of the object
283 | URI location = null;
284 |
285 | try{
286 |
287 | String path = (String)((JSONObject) ((JSONArray) JSONResponse).get(0)).get("self");
288 | location = URI.create(path);
289 |
290 | } catch ( Exception e ) {
291 | // didnt find anything
292 | return null;
293 | }
294 |
295 | logger.debug( String.format(
296 | "GET from [%s], status code [%d], location header [%s]",
297 | indexUri, clientResponse.getStatus(), location.toString() ) );
298 |
299 | clientResponse.close();
300 | return location;
301 |
302 | }
303 |
304 | }
305 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------