mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-01 07:42:17 +00:00
Add classes for name lookups with SPV verification.
This commit is contained in:
parent
80faef7303
commit
ca28f2a0ba
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
core/target
|
||||
namecoin/target
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
|
112
namecoin/pom.xml
Normal file
112
namecoin/pom.xml
Normal file
@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.libdohj</groupId>
|
||||
<artifactId>libdohj-namecoin</artifactId>
|
||||
<version>0.14-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<!-- Dummy block to make Maven Central happy: authors list is in AUTHORS -->
|
||||
<developers>
|
||||
<developer>
|
||||
<name>The libdohj team.</name>
|
||||
<email>info@dogecoin.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>update-protobuf</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>updateProtobuf</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile-protoc</id>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<path id="proto.path">
|
||||
<fileset dir="src">
|
||||
<include name="**/*.proto"/>
|
||||
</fileset>
|
||||
</path>
|
||||
<pathconvert pathsep=" " property="proto.files" refid="proto.path"/>
|
||||
<exec executable="protoc" failonerror="true">
|
||||
<arg value="--java_out=${project.basedir}/src/main/java"/>
|
||||
<arg value="-I${project.basedir}/src"/>
|
||||
<arg line="${proto.files}"/>
|
||||
</exec>
|
||||
</tasks>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lambdaworks</groupId>
|
||||
<artifactId>scrypt</artifactId>
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bitcoinj</groupId>
|
||||
<artifactId>bitcoinj-core</artifactId>
|
||||
<version>0.14.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.libdohj</groupId>
|
||||
<artifactId>libdohj-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.7.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.6</maven.compiler.source>
|
||||
<maven.compiler.target>1.6</maven.compiler.target>
|
||||
</properties>
|
||||
<name>libdohj</name>
|
||||
</project>
|
@ -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;
|
||||
|
||||
}
|
@ -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<Block.VerifyFlag> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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<Integer, Sha256Hash> 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<Integer, Sha256Hash>(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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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<NameData> 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<NameData> getUntrustedNameHistory(String name) throws Exception {
|
||||
URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
ArrayList<NameData> untrustedNameHistory = new ArrayList<NameData>(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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<Sha256Hash> merkleHashes = new ArrayList<Sha256Hash>(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<NameData> 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<NameData> getUntrustedNameHistory(String name) throws Exception {
|
||||
URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
ArrayList<NameData> untrustedNameHistory = new ArrayList<NameData>(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<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<NameData> getUntrustedNameHistory(String name) throws Exception {
|
||||
URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
NameData[] untrustedNameSingleEntry = {mapper.readValue(nameUrl, NameData.class)};
|
||||
ArrayList<NameData> untrustedNameHistory = new ArrayList<NameData>(Arrays.asList(untrustedNameSingleEntry));
|
||||
|
||||
return untrustedNameHistory;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user