diff --git a/.gitignore b/.gitignore
index f6135466..b707fe81 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
core/target
+namecoin/target
.project
.classpath
.settings
diff --git a/namecoin/pom.xml b/namecoin/pom.xml
new file mode 100644
index 00000000..c6932134
--- /dev/null
+++ b/namecoin/pom.xml
@@ -0,0 +1,112 @@
+
+
+ 4.0.0
+ org.libdohj
+ libdohj-namecoin
+ 0.14-SNAPSHOT
+ jar
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+
+
+ The libdohj team.
+ info@dogecoin.com
+
+
+
+
+ update-protobuf
+
+
+ updateProtobuf
+ true
+
+
+
+
+
+ maven-antrun-plugin
+
+
+ compile-protoc
+ generate-sources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.7
+
+
+ junit
+ junit
+ 4.12
+ test
+ jar
+
+
+ com.google.protobuf
+ protobuf-java
+ 2.5.0
+
+
+ com.lambdaworks
+ scrypt
+ 1.4.0
+
+
+ org.bitcoinj
+ bitcoinj-core
+ 0.14.2
+
+
+ org.libdohj
+ libdohj-core
+ ${project.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.7.5
+
+
+
+ UTF-8
+ 1.6
+ 1.6
+
+ libdohj
+
diff --git a/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHash.java b/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHash.java
new file mode 100644
index 00000000..48803e97
--- /dev/null
+++ b/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHash.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Jeremy Rand.
+ *
+ * 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.libdohj.names;
+
+import org.bitcoinj.core.Sha256Hash;
+import org.bitcoinj.core.Transaction;
+
+// TODO: Document this.
+
+// identity is used for things like Tor stream isolation
+public interface NameLookupByBlockHash {
+
+ public Transaction getNameTransaction(String name, Sha256Hash blockHash, String identity) throws Exception;
+
+}
diff --git a/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHashOneFullBlock.java b/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHashOneFullBlock.java
new file mode 100644
index 00000000..a0a87c3a
--- /dev/null
+++ b/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHashOneFullBlock.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 Jeremy Rand.
+ *
+ * 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.libdohj.names;
+
+import org.bitcoinj.core.Block;
+import org.bitcoinj.core.PeerGroup;
+import org.bitcoinj.core.Sha256Hash;
+import org.bitcoinj.core.Transaction;
+import org.bitcoinj.core.TransactionOutput;
+
+import java.util.EnumSet;
+
+// TODO: document this
+
+public class NameLookupByBlockHashOneFullBlock implements NameLookupByBlockHash {
+
+ protected PeerGroup peerGroup;
+
+ public NameLookupByBlockHashOneFullBlock (PeerGroup peerGroup) {
+ this.peerGroup = peerGroup;
+ }
+
+ @Override
+ public Transaction getNameTransaction(String name, Sha256Hash blockHash, String identity) throws Exception {
+
+ Block nameFullBlock = peerGroup.getDownloadPeer().getBlock(blockHash).get();
+
+ // The full block hasn't been verified in any way!
+ // So let's do that now.
+
+ final EnumSet flags = EnumSet.noneOf(Block.VerifyFlag.class);
+ nameFullBlock.verify(-1, flags);
+
+ // Now we know that the block is internally valid (including the merkle root).
+ // We haven't verified signature validity, but our threat model is SPV.
+
+ for (Transaction tx : nameFullBlock.getTransactions()) {
+ if (NameTransactionUtils.getNameAnyUpdateOutput(tx, name) != null) {
+ return tx;
+ }
+ }
+
+ // The name wasn't found.
+ return null;
+ }
+
+}
diff --git a/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHeight.java b/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHeight.java
new file mode 100644
index 00000000..544f704e
--- /dev/null
+++ b/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHeight.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 Jeremy Rand.
+ *
+ * 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.libdohj.names;
+
+import org.bitcoinj.core.Transaction;
+
+// TODO: document this
+
+public interface NameLookupByBlockHeight {
+
+ public Transaction getNameTransaction(String name, int height, String identity) throws Exception;
+
+}
diff --git a/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHeightHashCache.java b/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHeightHashCache.java
new file mode 100644
index 00000000..45cb9211
--- /dev/null
+++ b/namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHeightHashCache.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 Jeremy Rand.
+ *
+ * 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.libdohj.names;
+
+import org.bitcoinj.core.BlockChain;
+import org.bitcoinj.core.Sha256Hash;
+import org.bitcoinj.core.StoredBlock;
+import org.bitcoinj.core.Transaction;
+import org.bitcoinj.store.BlockStore;
+import org.bitcoinj.store.BlockStoreException;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+// TODO: breakout the 36000 expiration time into NetworkParameters.
+
+// TODO: breakout the hash cache into its own class
+
+// TODO: update blockHashCache with new blocks as they come into the chain
+
+// TODO: document this
+
+public class NameLookupByBlockHeightHashCache implements NameLookupByBlockHeight {
+
+ protected BlockChain chain;
+ protected BlockStore store;
+
+ protected NameLookupByBlockHash hashLookup;
+
+ protected ConcurrentHashMap blockHashCache;
+
+ public NameLookupByBlockHeightHashCache (BlockChain chain, NameLookupByBlockHash hashLookup) throws Exception {
+ this.chain = chain;
+ this.store = chain.getBlockStore();
+
+ this.hashLookup = hashLookup;
+
+ initBlockHashCache();
+ }
+
+ protected void initBlockHashCache() throws BlockStoreException {
+ blockHashCache = new ConcurrentHashMap(72000);
+
+ StoredBlock blockPointer = chain.getChainHead();
+
+ int headHeight = blockPointer.getHeight();
+ int reorgSafety = 120;
+ int newestHeight = headHeight - reorgSafety;
+ int oldestHeight = headHeight - 36000 - reorgSafety; // 36000 = name expiration
+
+ while (blockPointer.getHeight() >= oldestHeight) {
+
+ if (blockPointer.getHeight() <= newestHeight) {
+ blockHashCache.put(new Integer(blockPointer.getHeight()), blockPointer.getHeader().getHash());
+ }
+
+ blockPointer = blockPointer.getPrev(store);
+ }
+ }
+
+ @Override
+ public Transaction getNameTransaction(String name, int height, String identity) throws Exception {
+
+ Sha256Hash blockHash = getBlockHash(height);
+
+ Transaction tx = hashLookup.getNameTransaction(name, blockHash, identity);
+
+ tx.getConfidence().setAppearedAtChainHeight(height); // TODO: test this line
+ tx.getConfidence().setDepthInBlocks(chain.getChainHead().getHeight() - height + 1);
+
+ return tx;
+
+ }
+
+ public Sha256Hash getBlockHash(int height) throws BlockStoreException {
+ Sha256Hash maybeResult = blockHashCache.get(new Integer(height));
+
+ if (maybeResult != null) {
+ return maybeResult;
+ }
+
+ // If we got this far, the block height is uncached.
+ // This could be because the block is immature,
+ // or it could be because the cache is only initialized on initial startup.
+
+ StoredBlock blockPointer = chain.getChainHead();
+
+ while (blockPointer.getHeight() != height) {
+ blockPointer = blockPointer.getPrev(store);
+ }
+
+ return blockPointer.getHeader().getHash();
+ }
+
+}
diff --git a/namecoin/src/main/java/org/libdohj/names/NameLookupLatest.java b/namecoin/src/main/java/org/libdohj/names/NameLookupLatest.java
new file mode 100644
index 00000000..03159d84
--- /dev/null
+++ b/namecoin/src/main/java/org/libdohj/names/NameLookupLatest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 Jeremy Rand.
+ *
+ * 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.libdohj.names;
+
+import org.bitcoinj.core.Transaction;
+
+// TODO: document this
+
+public interface NameLookupLatest {
+
+ public Transaction getNameTransaction(String name, String identity) throws Exception;
+
+}
diff --git a/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestHeightApi.java b/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestHeightApi.java
new file mode 100644
index 00000000..d3055d98
--- /dev/null
+++ b/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestHeightApi.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2016 Jeremy Rand.
+ *
+ * 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.libdohj.names;
+
+import org.bitcoinj.core.BlockChain;
+import org.bitcoinj.core.Transaction;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+// TODO: document this
+
+public class NameLookupLatestRestHeightApi implements NameLookupLatest {
+
+ protected BlockChain chain;
+ protected NameLookupByBlockHeight heightLookup;
+ protected String restUrlPrefix;
+ protected String restUrlSuffix;
+
+ public NameLookupLatestRestHeightApi (String restUrlPrefix, String restUrlSuffix, BlockChain chain, NameLookupByBlockHeight heightLookup) {
+ this.restUrlPrefix = restUrlPrefix;
+ this.restUrlSuffix = restUrlSuffix;
+ this.chain = chain;
+ this.heightLookup = heightLookup;
+ }
+
+ // TODO: make a new Exception class
+ @Override
+ public Transaction getNameTransaction(String name, String identity) throws Exception {
+
+ int height = getHeight(name);
+
+ return heightLookup.getNameTransaction(name, height, identity);
+
+ }
+
+ // TODO: break out the getHeight into its own class + interface
+ // TODO: add identity isolation
+ // TODO: use an older height if the newest height has insufficient confirmations, instead of throwing an Exception
+ // NOTE: this might fail if special characters are in the name, since it's not URL-escaping them.
+ public int getHeight(String name) throws Exception {
+ ArrayList untrustedNameHistory = getUntrustedNameHistory(name);
+
+ int height;
+
+ int index;
+
+ for (index = untrustedNameHistory.size() - 1; index >= 0; index--) {
+
+ height = untrustedNameHistory.get(index).height;
+ try {
+ verifyHeightTrustworthy(height);
+ return height;
+ }
+ catch (Exception e) {
+ continue;
+ }
+ }
+
+ throw new Exception("Height not trustworthy or name does not exist.");
+ }
+
+ // TODO: add identity isolation
+ protected ArrayList getUntrustedNameHistory(String name) throws Exception {
+ URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ ArrayList untrustedNameHistory = new ArrayList(Arrays.asList(mapper.readValue(nameUrl, NameData[].class)));
+
+ return untrustedNameHistory;
+ }
+
+ protected void verifyHeightTrustworthy(int height) throws Exception {
+ if (height < 1) {
+ throw new Exception("Nonpositive block height; not trustworthy!");
+ }
+
+ int headHeight = chain.getChainHead().getHeight();
+
+ int confirmations = headHeight - height + 1;
+
+ // TODO: optionally use transaction chains (with signature checks) to verify transactions without 12 confirmations
+ // TODO: the above needs to be optional, because some applications (e.g. cert transparency) require confirmations
+ if (confirmations < 12) {
+ throw new Exception("Block does not yet have 12 confirmations; not trustworthy!");
+ }
+
+ // TODO: check for off-by-one errors on this line
+ if (confirmations >= 36000) {
+ throw new Exception("Block has expired; not trustworthy!");
+ }
+ }
+
+ static protected class NameData {
+
+ public String name;
+ public String value;
+ public String txid;
+ public String address;
+ public int expires_in;
+ public int height;
+
+ @JsonCreator
+ public NameData(@JsonProperty("name") String name,
+ @JsonProperty("value") String value,
+ @JsonProperty("txid") String txid,
+ @JsonProperty("address") String address,
+ @JsonProperty("expires_in") int expires_in,
+ @JsonProperty("height") int height) {
+ this.name = name;
+ this.value = value;
+ this.txid = txid;
+ this.address = address;
+ this.expires_in = expires_in;
+ this.height = height;
+ }
+ }
+
+}
diff --git a/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestMerkleApi.java b/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestMerkleApi.java
new file mode 100644
index 00000000..a8f8f40d
--- /dev/null
+++ b/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestMerkleApi.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2016 Jeremy Rand.
+ *
+ * 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.libdohj.names;
+
+import org.bitcoinj.core.Block;
+import org.bitcoinj.core.BlockChain;
+import org.bitcoinj.core.MerkleBranch;
+import org.bitcoinj.core.NetworkParameters;
+import org.bitcoinj.core.Sha256Hash;
+import org.bitcoinj.core.Transaction;
+import org.bitcoinj.core.Utils;
+import org.bitcoinj.store.BlockStore;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+// TODO: document this
+
+public class NameLookupLatestRestMerkleApi implements NameLookupLatest {
+
+ protected NetworkParameters params;
+ protected BlockChain chain;
+ protected BlockStore store;
+ protected NameLookupByBlockHeightHashCache heightLookup; // only needed for the hash cache
+ protected String restUrlPrefix;
+ protected String restUrlSuffix;
+
+ // TODO: break out the hash cache into its own class so that we don't need the NameLookup features.
+ public NameLookupLatestRestMerkleApi (NetworkParameters params, String restUrlPrefix, String restUrlSuffix, BlockChain chain, BlockStore store, NameLookupByBlockHeightHashCache heightLookup) {
+ this.params = params;
+ this.restUrlPrefix = restUrlPrefix;
+ this.restUrlSuffix = restUrlSuffix;
+ this.chain = chain;
+ this.store = store;
+ this.heightLookup = heightLookup;
+ }
+
+ // TODO: make a new Exception class
+ @Override
+ public Transaction getNameTransaction(String name, String identity) throws Exception {
+
+ NameData data = getLatestUntrustedNameData(name);
+
+ Sha256Hash blockHash = heightLookup.getBlockHash(data.height);
+
+ Block blockHeader = store.get(blockHash).getHeader();
+
+ // Convert merkle hashes from String to Sha256Hash
+ ArrayList merkleHashes = new ArrayList(data.mrkl_branch.size());
+ for (String merkleHashString : data.mrkl_branch) {
+ merkleHashes.add(Sha256Hash.wrap(merkleHashString));
+ }
+
+ long merkleBranchSideMask = data.tx_idx;
+
+ MerkleBranch branch = new MerkleBranch(params, null, merkleHashes, merkleBranchSideMask);
+
+ Transaction tx = new Transaction(params, Utils.HEX.decode(data.rawtx));
+
+ Sha256Hash txId = tx.getHash();
+
+ if(! blockHeader.getMerkleRoot().equals(branch.calculateMerkleRoot(txId))) {
+ throw new Exception("Merkle proof failed to verify!");
+ }
+
+ tx.getConfidence().setAppearedAtChainHeight(data.height); // TODO: test this line
+ tx.getConfidence().setDepthInBlocks(chain.getChainHead().getHeight() - data.height + 1);
+
+ if (NameTransactionUtils.getNameAnyUpdateOutput(tx, name) == null) {
+ throw new Exception("Not a name_anyupdate transaction or wrong name!");
+ }
+
+ return tx;
+
+ }
+
+ // TODO: break out the getHeight into its own class + interface
+ // TODO: add identity isolation
+ // TODO: use an older height if the newest height has insufficient confirmations, instead of throwing an Exception
+ // NOTE: this might fail if special characters are in the name, since it's not URL-escaping them.
+ public NameData getLatestUntrustedNameData(String name) throws Exception {
+ ArrayList untrustedNameHistory = getUntrustedNameHistory(name);
+
+ int height;
+
+ int index;
+
+ for (index = untrustedNameHistory.size() - 1; index >= 0; index--) {
+
+ NameData candidate = untrustedNameHistory.get(index);
+ try {
+ verifyHeightTrustworthy(candidate.height);
+ return candidate;
+ }
+ catch (Exception e) {
+ continue;
+ }
+ }
+
+ throw new Exception("Height not trustworthy or name does not exist.");
+ }
+
+ // TODO: add identity isolation
+ protected ArrayList getUntrustedNameHistory(String name) throws Exception {
+ URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ ArrayList untrustedNameHistory = new ArrayList(Arrays.asList(mapper.readValue(nameUrl, NameData[].class)));
+
+ return untrustedNameHistory;
+ }
+
+ protected void verifyHeightTrustworthy(int height) throws Exception {
+ if (height < 1) {
+ throw new Exception("Nonpositive block height; not trustworthy!");
+ }
+
+ int headHeight = chain.getChainHead().getHeight();
+
+ int confirmations = headHeight - height + 1;
+
+ // TODO: optionally use transaction chains (with signature checks) to verify transactions without 12 confirmations
+ // TODO: the above needs to be optional, because some applications (e.g. cert transparency) require confirmations
+ if (confirmations < 12) {
+ throw new Exception("Block does not yet have 12 confirmations; not trustworthy!");
+ }
+
+ // TODO: check for off-by-one errors on this line
+ if (confirmations >= 36000) {
+ throw new Exception("Block has expired; not trustworthy!");
+ }
+ }
+
+ // TODO: break this out into its own class; add the extra fields to bitcoinj-addons too
+ static protected class NameData {
+
+ public String name;
+ public String value;
+ public String txid;
+ public String address;
+ public int expires_in;
+ public int height;
+ public long tx_idx;
+ public ArrayList mrkl_branch;
+ public String rawtx;
+
+ @JsonCreator
+ public NameData(@JsonProperty("name") String name,
+ @JsonProperty("value") String value,
+ @JsonProperty("txid") String txid,
+ @JsonProperty("address") String address,
+ @JsonProperty("expires_in") int expires_in,
+ @JsonProperty("height") int height,
+ @JsonProperty("tx_idx") long tx_idx,
+ @JsonProperty("mrkl_branch") ArrayList mrkl_branch,
+ @JsonProperty("rawtx") String rawtx) {
+ this.name = name;
+ this.value = value;
+ this.txid = txid;
+ this.address = address;
+ this.expires_in = expires_in;
+ this.height = height;
+ this.tx_idx = tx_idx;
+ this.mrkl_branch = mrkl_branch;
+ this.rawtx = rawtx;
+ }
+ }
+
+}
diff --git a/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestMerkleApiSingleTx.java b/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestMerkleApiSingleTx.java
new file mode 100644
index 00000000..6b4ade96
--- /dev/null
+++ b/namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestMerkleApiSingleTx.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Jeremy Rand.
+ *
+ * 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.libdohj.names;
+
+import org.bitcoinj.core.BlockChain;
+import org.bitcoinj.core.NetworkParameters;
+import org.bitcoinj.store.BlockStore;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+// This lookup client only downloads a single transaction from the API rather than a history.
+// This means that it's usually faster, but the API has to be careful to choose the correct transaction.
+// As of writing (2016 Jun 26), webbtc does *not* always make the correct choice.
+// That means that using this lookup client will result in an incorrect "nonexistent" result
+// if the latest name_update for the targeted name has a depth between 1 and 11 (inclusive).
+// I'm engaging with Marius from webbtc and hope to have a solution soon.
+// -- Jeremy
+
+public class NameLookupLatestRestMerkleApiSingleTx extends NameLookupLatestRestMerkleApi {
+
+ public NameLookupLatestRestMerkleApiSingleTx (NetworkParameters params, String restUrlPrefix, String restUrlSuffix, BlockChain chain, BlockStore store, NameLookupByBlockHeightHashCache heightLookup) {
+ super(params, restUrlPrefix, restUrlSuffix, chain, store, heightLookup);
+ }
+
+ @Override
+ protected ArrayList getUntrustedNameHistory(String name) throws Exception {
+ URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ NameData[] untrustedNameSingleEntry = {mapper.readValue(nameUrl, NameData.class)};
+ ArrayList untrustedNameHistory = new ArrayList(Arrays.asList(untrustedNameSingleEntry));
+
+ return untrustedNameHistory;
+ }
+
+}