3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-30 14:52:16 +00:00

Removed namecoin stuff

This commit is contained in:
jjos2372 2020-02-26 09:04:01 -03:00
parent 889ff26ebf
commit 9159a629a2
10 changed files with 0 additions and 1181 deletions

View File

@ -1,117 +0,0 @@
<?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.9.8</version>
</dependency>
<dependency>
<groupId>org.fusesource.leveldbjni</groupId>
<artifactId>leveldbjni-all</artifactId>
<version>1.8</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>

View File

@ -1,29 +0,0 @@
/*
* 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;
}

View File

@ -1,68 +0,0 @@
/*
* 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.
if (! nameFullBlock.getHash().equals(blockHash)) {
throw new Exception("Block hash mismatch!");
}
// Now we know that the received block actually does have a header that matches the hash that we requested.
// However, that doesn't mean that the block's contents are valid.
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;
}
}

View File

@ -1,27 +0,0 @@
/*
* 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;
}

View File

@ -1,108 +0,0 @@
/*
* 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();
}
}

View File

@ -1,27 +0,0 @@
/*
* 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;
}

View File

@ -1,418 +0,0 @@
/*
* Copyright 2016-2017 Jeremy Rand.
* Based on LevelDBBlockStore.java, copyright the BitcoinJ authors.
*
* 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.libdohj.script.NameScript;
import org.bitcoinj.core.AbstractBlockChain;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.PeerGroup;
import org.bitcoinj.core.ScriptException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.NewBestBlockListener;
import org.bitcoinj.core.listeners.ReorganizeListener;
import org.bitcoinj.core.listeners.TransactionReceivedInBlockListener;
import org.bitcoinj.script.Script;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.utils.Threading;
import org.fusesource.leveldbjni.*;
import org.iq80.leveldb.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import java.io.*;
import java.nio.*;
import java.util.Arrays;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
// TODO: dead blocks' name transactions are stored in memory indefinitely. We should probably fix that, although doing so will slow down processing of reorgs.
public class NameLookupLatestLevelDBTransactionCache implements NameLookupLatest, NewBestBlockListener, ReorganizeListener, TransactionReceivedInBlockListener {
protected static final byte[] CHAIN_HEAD_KEY = "Head".getBytes();
protected static final byte[] HEIGHT_KEY = "Height".getBytes();
protected BlockChain chain;
protected BlockStore store;
protected PeerGroup peerGroup;
protected Context context;
protected NetworkParameters params;
protected File path;
protected DB db;
protected SetMultimap<Sha256Hash, Transaction> pendingBlockTransactions = Multimaps.synchronizedSetMultimap(HashMultimap.<Sha256Hash, Transaction>create());
protected Logger log = LoggerFactory.getLogger(NameLookupLatestLevelDBTransactionCache.class);
public NameLookupLatestLevelDBTransactionCache (Context context, File directory, BlockChain chain, BlockStore store, PeerGroup peerGroup) throws IOException {
this(context, directory, JniDBFactory.factory, chain, store, peerGroup);
}
public NameLookupLatestLevelDBTransactionCache (Context context, File directory, DBFactory dbFactory, BlockChain chain, BlockStore store, PeerGroup peerGroup) throws IOException {
this.chain = chain;
this.store = store;
this.peerGroup = peerGroup;
this.context = context;
this.params = context.getParams();
this.path = directory;
Options options = new Options();
options.createIfMissing();
try {
tryOpen(directory, dbFactory, options);
} catch (IOException e) {
dbFactory.repair(directory, options);
tryOpen(directory, dbFactory, options);
}
chain.addNewBestBlockListener(Threading.SAME_THREAD, this);
chain.addReorganizeListener(Threading.SAME_THREAD, this);
chain.addTransactionReceivedListener(Threading.SAME_THREAD, this);
}
protected void tryOpen(File directory, DBFactory dbFactory, Options options) throws IOException {
db = dbFactory.open(directory, options);
initStoreIfNeeded();
}
protected synchronized void initStoreIfNeeded() {
if (db.get(CHAIN_HEAD_KEY) != null)
return; // Already initialised.
setChainHead(0);
}
protected StoredBlock getSafeBlock(StoredBlock block) throws BlockStoreException {
StoredBlock result = block;
int safetyCount;
for (safetyCount = 0; safetyCount < 12; safetyCount++) {
result = result.getPrev(store);
}
return result;
}
protected synchronized void putBlockChain(StoredBlock block) throws Exception {
// TODO: use BIP 113 timestamps
if ( (new Date().getTime() / 1000 ) - block.getHeader().getTimeSeconds() > 366 * 24 * 60 * 60) {
log.debug("NameDB halting walkbalk due to timestamp expiration, height " + block.getHeight());
return;
}
if (block.getHeight() > getChainHead() + 1) {
putBlockChain(block.getPrev(store));
}
putBlock(block);
}
// TODO: try a different peer if downloading a block fails, otherwise we're likely to stall the syncup
protected synchronized void putBlock(StoredBlock block) throws Exception {
Sha256Hash blockHash = block.getHeader().getHash();
// We might not have the block's transactions already; if we don't, we have to download the block again.
// This should be very rare; I'm not actually certain what circumstances would trigger it.
// (I guess it would happen if block.transactions is null?)
if (! pendingBlockTransactions.containsKey(block.getHeader().getHash())) {
log.warn("Transactions missing from block " + blockHash + "; re-downloading block...");
Block nameFullBlock = peerGroup.getDownloadPeer().getBlock(blockHash).get();
// The full block hasn't been verified in any way!
// So let's do that now.
if (! nameFullBlock.getHash().equals(blockHash)) {
throw new VerificationException("Block hash mismatch!");
}
// Now we know that the received block actually does match the hash that we requested.
// However, that doesn't mean that the block's contents are valid.
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()) {
for (TransactionOutput output : tx.getOutputs()) {
try {
Script scriptPubKey = output.getScriptPubKey();
NameScript ns = new NameScript(scriptPubKey);
if(ns.isNameOp() && ns.isAnyUpdate() ) {
pendingBlockTransactions.put(block.getHeader().getHash(), tx);
}
} catch (ScriptException e) {
// Our threat model is lightweight SPV, which means we
// don't attempt to reject a blockchain due to a single
// invalid transaction. As such, if we see a
// ScriptException, we just discard the transaction
// (and log a warning) rather than rejecting the block.
log.warn("Error checking TransactionOutput for name_anyupdate script!", e);
continue;
}
}
}
}
int height = block.getHeight();
// See thread safety warning:
// https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/collect/Multimaps.html#synchronizedMultimap%28com.google.common.collect.Multimap%29
synchronized (pendingBlockTransactions) {
for (Transaction tx : pendingBlockTransactions.get(block.getHeader().getHash())) {
for (TransactionOutput output : tx.getOutputs()) {
try {
Script scriptPubKey = output.getScriptPubKey();
NameScript ns = new NameScript(scriptPubKey);
if(ns.isNameOp() && ns.isAnyUpdate() ) {
putNameScript(scriptPubKey, ns, height);
}
} catch (ScriptException e) {
continue;
}
}
}
}
pendingBlockTransactions.removeAll(block.getHeader().getHash());
setChainHead(block.getHeight());
}
protected synchronized void putNameScript(Script scriptPubKey, NameScript ns, int height) throws UnsupportedEncodingException {
// TODO: check if name is relevant (e.g. namespace is id/, has zeronet field)
// key format:
byte[] headerBytes = "NamScr".getBytes("ISO-8859-1");
byte[] nameBytes = ns.getOpName().data;
// record format:
// height goes here
byte[] scriptBytes = scriptPubKey.getProgram();
ByteBuffer keyBuffer = ByteBuffer.allocate(headerBytes.length + nameBytes.length);
ByteBuffer recordBuffer = ByteBuffer.allocate(4 + scriptBytes.length);
keyBuffer.put(headerBytes).put(nameBytes);
recordBuffer.putInt(height).put(scriptBytes);
db.put(keyBuffer.array(), recordBuffer.array());
}
// TODO: stop duplicating code from the other NameLookupLatest implementations
protected void verifyHeightTrustworthy(int height) throws IllegalArgumentException, VerificationException {
if (height < 1) {
throw new IllegalArgumentException("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 VerificationException("Block does not yet have 12 confirmations; not trustworthy!");
}
// TODO: check for off-by-one errors on this line
if (confirmations >= 36000) {
throw new VerificationException("Block has expired; not trustworthy!");
}
}
// TODO: make a new Exception class
@Override
public Transaction getNameTransaction(String name, String identity) throws Exception {
byte[] headerBytes = "NamScr".getBytes("ISO-8859-1");
byte[] nameBytes = name.getBytes("ISO-8859-1");
// name goes here
ByteBuffer keyBuffer = ByteBuffer.allocate(headerBytes.length + nameBytes.length);
keyBuffer.put(headerBytes).put(nameBytes);
byte[] recordBytes = db.get(keyBuffer.array());
if (recordBytes == null)
return null;
ByteBuffer recordBuffer = ByteBuffer.wrap(recordBytes);
int height = recordBuffer.getInt();
verifyHeightTrustworthy(height);
byte[] scriptPubKeyBytes = Arrays.copyOfRange(recordBytes, 4, recordBytes.length);
Transaction tx = new Transaction(params);
Script scriptPubKey = new Script(scriptPubKeyBytes);
tx.addOutput(Coin.CENT, scriptPubKey);
tx.getConfidence().setAppearedAtChainHeight(height); // TODO: test this line
tx.getConfidence().setDepthInBlocks(chain.getChainHead().getHeight() - height + 1);
return tx;
}
protected synchronized int getChainHead() {
return ByteBuffer.wrap(db.get(CHAIN_HEAD_KEY)).getInt();
}
protected synchronized void setChainHead(int chainHead) {
db.put(CHAIN_HEAD_KEY, ByteBuffer.allocate(4).putInt(chainHead).array());
}
public synchronized void close() throws IOException {
db.close();
}
/** Erases the contents of the database (but NOT the underlying files themselves) and then reinitialises with the genesis block. */
protected synchronized void reset() throws IOException {
WriteBatch batch = db.createWriteBatch();
try {
DBIterator it = db.iterator();
try {
it.seekToFirst();
while (it.hasNext())
batch.delete(it.next().getKey());
db.write(batch);
} finally {
it.close();
}
} finally {
batch.close();
}
initStoreIfNeeded();
}
protected synchronized void destroy() throws IOException {
JniDBFactory.factory.destroy(path, new Options());
}
@Override
public void notifyNewBestBlock (StoredBlock block) throws VerificationException {
// TODO: use BIP 113 timestamps
if ( (new Date().getTime() / 1000 ) - block.getHeader().getTimeSeconds() > 366 * 24 * 60 * 60) {
log.debug("NameDB skipping block at height " + block.getHeight() + " due to timestamp " + block.getHeader().getTimeSeconds());
return;
}
log.debug("NameDB started processing new best block at height " + block.getHeight());
try {
putBlockChain(getSafeBlock(block));
}
catch (Exception e) {
log.error("NameDB Exception while processing new best block", e);
throw new VerificationException(e);
}
log.debug("NameDB finished processing new best block at height " + block.getHeight());
}
// WARNING: in a reorg that is at least 12 blocks deep, any names updated in the old blocks that aren't updated in the new blocks will remain in their old state in the database.
// That is incorrect behavior, but it usually isn't advantageous to an attacker.
// In certain applications where proof of existence is used, this incorrect behavior could allow a true existence claim to be accepted,
// even though the rest of the network will incorrectly reject it.
// I don't see any other significant attacks here. Have I missed something?
// If we're really worried about this, the "right" solution is to either store name history in the database,
// or redownload all of the last 36 kiloblocks.
@Override
public void reorganize(StoredBlock splitPoint, List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks) throws VerificationException {
// TODO: use BIP 113 timestamps
if ( (new Date().getTime() / 1000 ) - newBlocks.get(0).getHeader().getTimeSeconds() > 366 * 24 * 60 * 60) {
return;
}
setChainHead(splitPoint.getHeight() - 12);
try {
putBlockChain(getSafeBlock(newBlocks.get(0)));
}
catch (Exception e) {
log.error("Exception during NameDB reorganize", e);
throw new VerificationException(e);
}
log.warn("Finished NameDB reorganize, height " + newBlocks.get(0).getHeight());
}
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block, AbstractBlockChain.NewBlockType blockType, int relativityOffset) throws VerificationException {
// TODO: use BIP 113 timestamps
if ( (new Date().getTime() / 1000 ) - block.getHeader().getTimeSeconds() > 366 * 24 * 60 * 60) {
log.debug("NameDB skipping new transaction at height " + block.getHeight() + " due to timestamp " + block.getHeader().getTimeSeconds());
return;
}
for (TransactionOutput output : tx.getOutputs()) {
try {
Script scriptPubKey = output.getScriptPubKey();
NameScript ns = new NameScript(scriptPubKey);
// Always save the coinbase, because it lets us identify that we've received the contents of the block, even if it has no name_anyupdate operations.
// TODO: maybe save a null reference instead of the actual coinbase tx, since this would cut down on memory usage very slightly.
if(tx.isCoinBase() || ( ns.isNameOp() && ns.isAnyUpdate() ) ) {
log.debug("NameDB temporarily storing name transaction until it gets more confirmations.");
pendingBlockTransactions.put(block.getHeader().getHash(), tx);
}
} catch (ScriptException e) {
// Our threat model is lightweight SPV, which means we
// don't attempt to reject a blockchain due to a single
// invalid transaction. As such, if we see a
// ScriptException, we just discard the transaction
// (and log a warning) rather than rejecting the block.
log.warn("Error checking TransactionOutput for name_anyupdate script!", e);
continue;
}
}
}
// TODO: add optional FilteredBlock support
@Override
public boolean notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, AbstractBlockChain.NewBlockType blockType, int relativityOffset) throws VerificationException {
return false;
}
}

View File

@ -1,141 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,191 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,55 +0,0 @@
/*
* 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;
}
}