From 0cb659ca21175bedaf4b8d0245864786b6806229 Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Wed, 29 Jun 2016 23:52:58 +0000 Subject: [PATCH 1/6] Added network parameters for Namecoin (abstract and mainnet). --- .../params/AbstractNamecoinParams.java | 291 ++++++++++++++++++ .../libdohj/params/NamecoinMainNetParams.java | 120 ++++++++ 2 files changed, 411 insertions(+) create mode 100644 core/src/main/java/org/libdohj/params/AbstractNamecoinParams.java create mode 100644 core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java diff --git a/core/src/main/java/org/libdohj/params/AbstractNamecoinParams.java b/core/src/main/java/org/libdohj/params/AbstractNamecoinParams.java new file mode 100644 index 00000000..c0d6165a --- /dev/null +++ b/core/src/main/java/org/libdohj/params/AbstractNamecoinParams.java @@ -0,0 +1,291 @@ +/* + * 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.params; + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.util.concurrent.TimeUnit; + +import com.google.common.base.Stopwatch; + +import org.bitcoinj.core.AltcoinBlock; +import org.bitcoinj.core.Block; +import org.bitcoinj.core.Coin; +import static org.bitcoinj.core.Coin.COIN; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.VerificationException; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptOpCodes; +import org.bitcoinj.store.BlockStore; +import org.bitcoinj.store.BlockStoreException; +import org.bitcoinj.utils.MonetaryFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.StoredBlock; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.core.Utils; +import org.libdohj.core.AltcoinSerializer; +import org.libdohj.core.AuxPoWNetworkParameters; + +// TODO: review this + +/** + * Common parameters for Namecoin networks. + */ +public abstract class AbstractNamecoinParams extends NetworkParameters implements AuxPoWNetworkParameters { + /** Standard format for the NMC denomination. */ + public static final MonetaryFormat NMC; + /** Standard format for the mNMC denomination. */ + public static final MonetaryFormat MNMC; + /** Standard format for the uBTC denomination. */ + public static final MonetaryFormat UNMC; + + public static final int AUXPOW_CHAIN_ID = 0x0001; // 1 + + /** Currency code for base 1 Namecoin. */ + public static final String CODE_NMC = "NMC"; + /** Currency code for base 1/1,000 Namecoin. */ + public static final String CODE_MNMC = "mNMC"; + /** Currency code for base 1/1,000,000 Namecoin. */ + public static final String CODE_UNMC = "µNMC"; + + protected int auxpowStartHeight; + + private static final int BLOCK_VERSION_FLAG_AUXPOW = 0x00000100; + + static { + NMC = MonetaryFormat.BTC.noCode() + .code(0, CODE_NMC) + .code(3, CODE_MNMC) + .code(6, CODE_UNMC); + MNMC = NMC.shift(3).minDecimals(2).optionalDecimals(2); + UNMC = NMC.shift(6).minDecimals(0).optionalDecimals(0); + } + + /** The string returned by getId() for the main, production network where people trade things. */ + public static final String ID_NMC_MAINNET = "org.namecoin.production"; + /** The string returned by getId() for the testnet. */ + public static final String ID_NMC_TESTNET = "org.namecoin.test"; + + protected Logger log = LoggerFactory.getLogger(AbstractNamecoinParams.class); + + public static final int NAMECOIN_PROTOCOL_VERSION_GETHEADERS = 38000; + + public AbstractNamecoinParams() { + super(); + genesisBlock = createGenesis(this); + interval = INTERVAL; + targetTimespan = TARGET_TIMESPAN; + maxTarget = Utils.decodeCompactBits(0x1e0fffffL); // TODO: figure out the Namecoin value of this + + // BIP 43 recommends using these values regardless of which blockchain is in use. + bip32HeaderPub = 0x0488B21E; //The 4 byte header that serializes in base58 to "xpub". + bip32HeaderPriv = 0x0488ADE4; //The 4 byte header that serializes in base58 to "xprv" + } + + private static AltcoinBlock createGenesis(NetworkParameters params) { + AltcoinBlock genesisBlock = new AltcoinBlock(params, Block.BLOCK_VERSION_GENESIS); + Transaction t = new Transaction(params); + try { + // "... choose what comes next. Lives of your own, or a return to chains. -- V" + byte[] bytes = Utils.HEX.decode + ("04ff7f001c020a024b2e2e2e2063686f6f7365207768617420636f6d6573206e6578742e20204c69766573206f6620796f7572206f776e2c206f7220612072657475726e20746f20636861696e732e202d2d2056"); + t.addInput(new TransactionInput(params, t, bytes)); + ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream(); + Script.writeBytes(scriptPubKeyBytes, Utils.HEX.decode + ("04b620369050cd899ffbbc4e8ee51e8c4534a855bb463439d63d235d4779685d8b6f4870a238cf365ac94fa13ef9a2a22cd99d0d5ee86dcabcafce36c7acf43ce5")); + scriptPubKeyBytes.write(ScriptOpCodes.OP_CHECKSIG); + t.addOutput(new TransactionOutput(params, t, COIN.multiply(50), scriptPubKeyBytes.toByteArray())); + } catch (Exception e) { + // Cannot happen. + throw new RuntimeException(e); + } + genesisBlock.addTransaction(t); + return genesisBlock; + } + + @Override + public Coin getBlockSubsidy(final int height) { + return COIN.multiply(50).shiftRight(height / getSubsidyDecreaseBlockCount()); + } + + @Override + public MonetaryFormat getMonetaryFormat() { + return NMC; + } + + @Override + public Coin getMaxMoney() { + return MAX_MONEY; + } + + // TODO: this is Bitcoin, need to figure out if it's the same for Namecoin + @Override + public Coin getMinNonDustOutput() { + return Transaction.MIN_NONDUST_OUTPUT; + } + + @Override + public String getUriScheme() { + return "namecoin"; + } + + @Override + public boolean hasMaxMoney() { + return true; + } + + // This is copied from Bitcoin + /** + * Checks if we are at a difficulty transition point. + * @param storedPrev The previous stored block + * @return If this is a difficulty transition point + */ + protected boolean isDifficultyTransitionPoint(StoredBlock storedPrev) { + return ((storedPrev.getHeight() + 1) % this.getInterval()) == 0; + } + + @Override + public void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock, BlockStore blockStore) + throws VerificationException, BlockStoreException { + // This is copied verbatim from Bitcoin except for the Namecoin changes marked accordingly + Block prev = storedPrev.getHeader(); + + // Is this supposed to be a difficulty transition point? + if (!isDifficultyTransitionPoint(storedPrev)) { + + // No ... so check the difficulty didn't actually change. + if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget()) + throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() + + ": " + Long.toHexString(nextBlock.getDifficultyTarget()) + " vs " + + Long.toHexString(prev.getDifficultyTarget())); + return; + } + + // We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every + // two weeks after the initial block chain download. + final Stopwatch watch = Stopwatch.createStarted(); + StoredBlock cursor = blockStore.get(prev.getHash()); + + // Namecoin addition + int blocksBack = this.getInterval() - 1; + if (storedPrev.getHeight() >= this.getAuxpowStartHeight() && (storedPrev.getHeight() + 1 > this.getInterval())) { + blocksBack = this.getInterval(); + } + + // Namecoin modification + //for (int i = 0; i < this.getInterval() - 1; i++) { + for (int i = 0; i < blocksBack; i++) { + if (cursor == null) { + // This should never happen. If it does, it means we are following an incorrect or busted chain. + throw new VerificationException( + "Difficulty transition point but we did not find a way back to the genesis block."); + } + cursor = blockStore.get(cursor.getHeader().getPrevBlockHash()); + } + watch.stop(); + if (watch.elapsed(TimeUnit.MILLISECONDS) > 50) + log.info("Difficulty transition traversal took {}", watch); + + Block blockIntervalAgo = cursor.getHeader(); + int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds()); + // Limit the adjustment step. + final int targetTimespan = this.getTargetTimespan(); + if (timespan < targetTimespan / 4) + timespan = targetTimespan / 4; + if (timespan > targetTimespan * 4) + timespan = targetTimespan * 4; + + BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget()); + newTarget = newTarget.multiply(BigInteger.valueOf(timespan)); + newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan)); + + if (newTarget.compareTo(this.getMaxTarget()) > 0) { + log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16)); + newTarget = this.getMaxTarget(); + } + + int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3; + long receivedTargetCompact = nextBlock.getDifficultyTarget(); + + // The calculated difficulty is to a higher precision than received, so reduce here. + BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8); + newTarget = newTarget.and(mask); + long newTargetCompact = Utils.encodeCompactBits(newTarget); + + if (newTargetCompact != receivedTargetCompact) + throw new VerificationException("Network provided difficulty bits do not match what was calculated: " + + Long.toHexString(newTargetCompact) + " vs " + Long.toHexString(receivedTargetCompact)); + } + + @Override + public int getChainID() { + return AUXPOW_CHAIN_ID; + } + + // TODO: re-add this when we introduce Testnet2 + /** + * Whether this network has special rules to enable minimum difficulty blocks + * after a long interval between two blocks (i.e. testnet). + */ + //public abstract boolean allowMinDifficultyBlocks(); + + /** + * Get the hash to use for a block. + */ + + @Override + public Sha256Hash getBlockDifficultyHash(Block block) { + return block.getHash(); + } + + @Override + public AltcoinSerializer getSerializer(boolean parseRetain) { + return new AltcoinSerializer(this, parseRetain); + } + + // TODO: look into allowing PeerGroups that don't support GetHeaders (since for full block retrieval it's not needed) + @Override + public int getProtocolVersionNum(final ProtocolVersion version) { + switch (version) { + case MINIMUM: + return NAMECOIN_PROTOCOL_VERSION_GETHEADERS; + default: + return version.getBitcoinProtocolVersion(); + } + } + + @Override + public boolean isAuxPoWBlockVersion(long version) { + return (version & BLOCK_VERSION_FLAG_AUXPOW) > 0; + } + + public int getAuxpowStartHeight() { + return auxpowStartHeight; + } + + private static class CheckpointEncounteredException extends Exception { + + private CheckpointEncounteredException() { + } + } +} diff --git a/core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java b/core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java new file mode 100644 index 00000000..0da4f6dd --- /dev/null +++ b/core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java @@ -0,0 +1,120 @@ +/* + * Copyright 2013 Google Inc, 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.params; + +import org.bitcoinj.core.Sha256Hash; +import org.spongycastle.util.encoders.Hex; + +import static com.google.common.base.Preconditions.checkState; + +// TODO: review this + +/** + * Parameters for the main Namecoin production network on which people trade + * goods and services. + */ +public class NamecoinMainNetParams extends AbstractNamecoinParams { + public static final int MAINNET_MAJORITY_WINDOW = 1000; + public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = 950; + public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 750; + + public NamecoinMainNetParams() { + super(); + dumpedPrivateKeyHeader = 180; //This is always addressHeader + 128 + addressHeader = 52; + p2shHeader = 13; + //acceptableAddressCodes = new int[] { addressHeader, p2shHeader }; + acceptableAddressCodes = new int[] { addressHeader }; // Namecoin doesn't yet enforce P2SH, so we disable it for now. + port = 8334; + packetMagic = 0xf9beb4fe; + + genesisBlock.setDifficultyTarget(0x1C007FFFL); + genesisBlock.setTime(1303000001L); + genesisBlock.setNonce(2719916434L); + id = ID_NMC_MAINNET; + subsidyDecreaseBlockCount = 210000; + spendableCoinbaseDepth = 100; + auxpowStartHeight = 19200; + + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"), + genesisHash); + // TODO: remove alert key since it's removed from Bitcoin Core / Namecoin Core + alertSigningKey = Hex.decode("04ba207043c1575208f08ea6ac27ed2aedd4f84e70b874db129acb08e6109a3bbb7c479ae22565973ebf0ac0391514511a22cb9345bdb772be20cfbd38be578b0c"); + + majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE; + majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED; + majorityWindow = MAINNET_MAJORITY_WINDOW; + + // TODO: check whether there are any non BIP30 blocks in Namecoin; add them here if they exist + // This contains (at a minimum) the blocks which are not BIP30 compliant. BIP30 changed how duplicate + // transactions are handled. Duplicated transactions could occur in the case where a coinbase had the same + // extraNonce and the same outputs but appeared at different heights, and greatly complicated re-org handling. + // Having these here simplifies block connection logic considerably. + checkpoints.put( 2016, Sha256Hash.wrap("0000000000660bad0d9fbde55ba7ee14ddf766ed5f527e3fbca523ac11460b92")); + checkpoints.put( 4032, Sha256Hash.wrap("0000000000493b5696ad482deb79da835fe2385304b841beef1938655ddbc411")); + checkpoints.put( 6048, Sha256Hash.wrap("000000000027939a2e1d8bb63f36c47da858e56d570f143e67e85068943470c9")); + checkpoints.put( 8064, Sha256Hash.wrap("000000000003a01f708da7396e54d081701ea406ed163e519589717d8b7c95a5")); + checkpoints.put( 10080, Sha256Hash.wrap("00000000000fed3899f818b2228b4f01b9a0a7eeee907abd172852df71c64b06")); + checkpoints.put( 12096, Sha256Hash.wrap("0000000000006c06988ff361f124314f9f4bb45b6997d90a7ee4cedf434c670f")); + checkpoints.put( 14112, Sha256Hash.wrap("00000000000045d95e0588c47c17d593c7b5cb4fb1e56213d1b3843c1773df2b")); + checkpoints.put( 16128, Sha256Hash.wrap("000000000001d9964f9483f9096cf9d6c6c2886ed1e5dec95ad2aeec3ce72fa9")); + checkpoints.put( 18940, Sha256Hash.wrap("00000000000087f7fc0c8085217503ba86f796fa4984f7e5a08b6c4c12906c05")); + checkpoints.put( 30240, Sha256Hash.wrap("e1c8c862ff342358384d4c22fa6ea5f669f3e1cdcf34111f8017371c3c0be1da")); + checkpoints.put( 57000, Sha256Hash.wrap("aa3ec60168a0200799e362e2b572ee01f3c3852030d07d036e0aa884ec61f203")); + checkpoints.put(112896, Sha256Hash.wrap("73f880e78a04dd6a31efc8abf7ca5db4e262c4ae130d559730d6ccb8808095bf")); + checkpoints.put(182000, Sha256Hash.wrap("d47b4a8fd282f635d66ce34ebbeb26ffd64c35b41f286646598abfd813cba6d9")); + checkpoints.put(193000, Sha256Hash.wrap("3b85e70ba7f5433049cfbcf0ae35ed869496dbedcd1c0fafadb0284ec81d7b58")); + + dnsSeeds = new String[] { + "namecoindnsseed.digi-masters.com", // George Lloyd + "namecoindnsseed.digi-masters.uk", // George Lloyd + "seed.namecoin.domob.eu", // Daniel Kraft + "nmc.seed.quisquis.de", // Peter Conrad + "dnsseed.namecoin.webbtc.com", // Marius Hanne + }; + + // TODO: look into HTTP seeds or Addr seeds as is done for Bitcoin + } + + private static NamecoinMainNetParams instance; + public static synchronized NamecoinMainNetParams get() { + if (instance == null) { + instance = new NamecoinMainNetParams(); + } + return instance; + } + + // TODO: re-add this when we introduce Testnet2 + /* + @Override + public boolean allowMinDifficultyBlocks() { + return false; + } + */ + + @Override + public String getPaymentProtocolId() { + // TODO: CHANGE THIS (comment from Dogecoin) + return ID_NMC_MAINNET; + } + + @Override + public boolean isTestNet() { + return false; + } +} From f8186d9aee0c5a04350e57be98631ae1b335107a Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Wed, 29 Jun 2016 23:54:24 +0000 Subject: [PATCH 2/6] Added (incomplete) support for parsing name scripts. --- .../java/org/libdohj/script/NameScript.java | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 core/src/main/java/org/libdohj/script/NameScript.java diff --git a/core/src/main/java/org/libdohj/script/NameScript.java b/core/src/main/java/org/libdohj/script/NameScript.java new file mode 100644 index 00000000..c545c45f --- /dev/null +++ b/core/src/main/java/org/libdohj/script/NameScript.java @@ -0,0 +1,181 @@ +/* + * 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.script; + +import org.bitcoinj.core.ScriptException; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptBuilder; +import org.bitcoinj.script.ScriptChunk; +import static org.bitcoinj.script.ScriptOpCodes.*; + +import java.util.ArrayList; +import java.util.List; + +// TODO: review this + +public class NameScript { + + public static final int OP_NAME_NEW = OP_1; + public static final int OP_NAME_FIRSTUPDATE = OP_2; + public static final int OP_NAME_UPDATE = OP_3; + + protected int op; + + protected ArrayList args; + + protected Script address; + + public NameScript(Script baseScript) { + op = OP_NOP; + args = new ArrayList(); + address = baseScript; + + ScriptChunk nameOp; + int pc = 0; + + List chunks = baseScript.getChunks(); + + try { + nameOp = chunks.get(pc); + } catch (IndexOutOfBoundsException e) { + return; + } + pc++; + + while(true) { + ScriptChunk arg; + + try { + arg = chunks.get(pc); + } catch (IndexOutOfBoundsException e) { + return; + } + pc++; + + if(arg.opcode == OP_DROP || arg.opcode == OP_2DROP || arg.opcode == OP_NOP) { + break; + } + if( ! (arg.opcode >= 0 && arg.opcode <= OP_PUSHDATA4) ) { + return; + } + + args.add(arg); + } + + // Move the pc to after any DROP or NOP. + try { + while(chunks.get(pc).opcode == OP_DROP || chunks.get(pc).opcode == OP_2DROP || chunks.get(pc).opcode == OP_NOP) { + pc++; + } + } catch (IndexOutOfBoundsException e) { + } + + /* Now, we have the args and the operation. Check if we have indeed + a valid name operation and valid argument counts. Only now set the + op and address members, if everything is valid. */ + + switch (nameOp.opcode) { + case OP_NAME_NEW: + if(args.size() != 1) { + return; + } + break; + case OP_NAME_FIRSTUPDATE: + if(args.size() != 3) { + return; + } + break; + case OP_NAME_UPDATE: + if(args.size() != 2) { + return; + } + break; + default: + return; + } + + op = nameOp.opcode; + + ScriptBuilder addressBuilder = new ScriptBuilder(); + while(pc < chunks.size()) { + addressBuilder.addChunk(chunks.get(pc)); + pc++; + } + address = addressBuilder.build(); + } + + public boolean isNameOp() { + switch(op) { + case OP_NAME_NEW: + case OP_NAME_FIRSTUPDATE: + case OP_NAME_UPDATE: + return true; + + case OP_NOP: + return false; + + default: + throw new ScriptException("Invalid name op"); + } + } + + public Script getAddress() { + return address; + } + + // TODO: getNameOp + + public boolean isAnyUpdate() { + switch(op) { + case OP_NAME_NEW: + return false; + + case OP_NAME_FIRSTUPDATE: + case OP_NAME_UPDATE: + return true; + + default: + throw new ScriptException("Not a name op"); + } + } + + public ScriptChunk getOpName() { + switch(op) { + case OP_NAME_FIRSTUPDATE: + case OP_NAME_UPDATE: + return args.get(0); + + default: + throw new ScriptException("Not an AnyUpdate op"); + } + } + + public ScriptChunk getOpValue() { + switch(op) { + case OP_NAME_FIRSTUPDATE: + return args.get(2); + + case OP_NAME_UPDATE: + return args.get(1); + + default: + throw new ScriptException("Not an AnyUpdate op"); + } + } + + // TODO: getOpRand, getOpHash, isNameScript, buildNameNew, buildNameFirstupdate, buildNameUpdate +} \ No newline at end of file From a9fa671988dc38b2010ec047ee27516d992444cc Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Wed, 29 Jun 2016 23:55:56 +0000 Subject: [PATCH 3/6] Added tests for parsing name scripts. --- .../org/libdohj/script/NameScriptTest.java | 355 ++++++++++++++++++ .../namecoin_name_firstupdate_d_bitcoin.bin | Bin 0 -> 476 bytes .../script/namecoin_name_new_d_bitcoin.bin | Bin 0 -> 257 bytes .../script/namecoin_name_update_d_bitcoin.bin | Bin 0 -> 635 bytes .../org/libdohj/script/namecoin_p2pkh.bin | Bin 0 -> 193 bytes 5 files changed, 355 insertions(+) create mode 100644 core/src/test/java/org/libdohj/script/NameScriptTest.java create mode 100644 core/src/test/resources/org/libdohj/script/namecoin_name_firstupdate_d_bitcoin.bin create mode 100644 core/src/test/resources/org/libdohj/script/namecoin_name_new_d_bitcoin.bin create mode 100644 core/src/test/resources/org/libdohj/script/namecoin_name_update_d_bitcoin.bin create mode 100644 core/src/test/resources/org/libdohj/script/namecoin_p2pkh.bin diff --git a/core/src/test/java/org/libdohj/script/NameScriptTest.java b/core/src/test/java/org/libdohj/script/NameScriptTest.java new file mode 100644 index 00000000..1b216e95 --- /dev/null +++ b/core/src/test/java/org/libdohj/script/NameScriptTest.java @@ -0,0 +1,355 @@ +/* + * 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.script; + +import org.libdohj.params.AbstractNamecoinParams; +import org.libdohj.params.NamecoinMainNetParams; + +import org.bitcoinj.core.Context; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.core.Util; +import org.bitcoinj.script.Script; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.rules.ExpectedException; + +import java.io.IOException; + +/** + * + * @author Jeremy Rand + */ +public class NameScriptTest { + private static final AbstractNamecoinParams params = NamecoinMainNetParams.get(); + + @Before + public void setUp() throws Exception { + Context context = new Context(params); + } + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + @Test + public void nameNewIsNameOp() throws IOException { + final NameScript ns = getNameNewNameScript(); + + assertTrue(ns.isNameOp()); + } + + @Test + public void nameNewGetAddress() throws IOException { + final NameScript ns = getNameNewNameScript(); + + assertEquals("MyVbKbD4MYNUMEpdNAm3Jd3nbr5t8djALC", ns.getAddress().getToAddress(params).toString()); + } + + // TODO: getNameOp when it's implemented + + @Test + public void nameNewIsAnyUpdate() throws IOException { + final NameScript ns = getNameNewNameScript(); + + assertFalse(ns.isAnyUpdate()); + } + + @Test + public void nameNewGetOpName() throws IOException { + final NameScript ns = getNameNewNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Not an AnyUpdate op"); + ns.getOpName(); + } + + @Test + public void nameNewGetOpValue() throws IOException { + final NameScript ns = getNameNewNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Not an AnyUpdate op"); + ns.getOpValue(); + + } + + // TODO: getOpRand, getOpHash, isNameScript when they're implemented + + @Test + public void nameFirstUpdateIsNameOp() throws IOException { + final NameScript ns = getNameFirstUpdateNameScript(); + + assertTrue(ns.isNameOp()); + } + + @Test + public void nameFirstUpdateGetAddress() throws IOException { + final NameScript ns = getNameFirstUpdateNameScript(); + + assertEquals("NGcTVLgw6cgdavaE7C9QvWaY7gKiWbLrjP", ns.getAddress().getToAddress(params).toString()); + } + + // TODO: getNameOp when it's implemented + + @Test + public void nameFirstUpdateIsAnyUpdate() throws IOException { + final NameScript ns = getNameFirstUpdateNameScript(); + + assertTrue(ns.isAnyUpdate()); + } + + @Test + public void nameFirstUpdateGetOpName() throws IOException { + final NameScript ns = getNameFirstUpdateNameScript(); + + assertEquals("d/bitcoin", new String(ns.getOpName().data, "ISO-8859-1")); + } + + @Test + public void nameFirstUpdateGetOpValue() throws IOException { + final NameScript ns = getNameFirstUpdateNameScript(); + + assertEquals("webpagedeveloper.me/namecoin", new String(ns.getOpValue().data, "ISO-8859-1")); + } + + // TODO: getOpRand, getOpHash, isNameScript when they're implemented + + @Test + public void nameUpdateIsNameOp() throws IOException { + final NameScript ns = getNameUpdateNameScript(); + + assertTrue(ns.isNameOp()); + } + + @Test + public void nameUpdateGetAddress() throws IOException { + final NameScript ns = getNameUpdateNameScript(); + + assertEquals("N9dLs1zHRfZr5cJNjSrvhWrrUcmNSthdmz", ns.getAddress().getToAddress(params).toString()); + } + + // TODO: getNameOp when it's implemented + + @Test + public void nameUpdateIsAnyUpdate() throws IOException { + final NameScript ns = getNameUpdateNameScript(); + + assertTrue(ns.isAnyUpdate()); + } + + @Test + public void nameUpdateGetOpName() throws IOException { + final NameScript ns = getNameUpdateNameScript(); + + assertEquals("d/bitcoin", new String(ns.getOpName().data, "ISO-8859-1")); + } + + @Test + public void nameUpdateGetOpValue() throws IOException { + final NameScript ns = getNameUpdateNameScript(); + + assertEquals("{\"info\":{\"registrar\":\"http://register.dot-bit.org\"},\"email\": \"register@dot-bit.org\",\"ns\":[\"ns0.web-sweet-web.net\",\"ns1.web-sweet-web.net\"],\"map\":{\"\":{\"ns\":[\"ns0.web-sweet-web.net\",\"ns1.web-sweet-web.net\"]}}}", new String(ns.getOpValue().data, "ISO-8859-1")); + } + + // TODO: getOpRand, getOpHash, isNameScript when they're implemented + + @Test + public void currencyIsNameOp() throws IOException { + final NameScript ns = getCurrencyNameScript(); + + assertFalse(ns.isNameOp()); + } + + @Test + public void currencyGetAddress() throws IOException { + final NameScript ns = getCurrencyNameScript(); + + assertEquals("NCMmrGC7uaJ3uv8feLgBTtwGLQSWfmxMCk", ns.getAddress().getToAddress(params).toString()); + } + + // TODO: getNameOp when it's implemented + + @Test + public void currencyIsAnyUpdate() throws IOException { + final NameScript ns = getCurrencyNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Not a name op"); + ns.isAnyUpdate(); + } + + @Test + public void currencyGetOpName() throws IOException { + final NameScript ns = getCurrencyNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Not an AnyUpdate op"); + ns.getOpName(); + } + + @Test + public void currencyGetOpValue() throws IOException { + final NameScript ns = getCurrencyNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Not an AnyUpdate op"); + ns.getOpValue(); + } + + // TODO: getOpRand, getOpHash, isNameScript when they're implemented + + @Test + public void returnIsNameOp() throws IOException { + final NameScript ns = getReturnNameScript(); + + assertFalse(ns.isNameOp()); + } + + @Test + public void returnGetAddress() throws IOException { + final NameScript ns = getReturnNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Cannot cast this script to a pay-to-address type"); + ns.getAddress().getToAddress(params).toString(); + } + + // TODO: getNameOp when it's implemented + + @Test + public void returnIsAnyUpdate() throws IOException { + final NameScript ns = getReturnNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Not a name op"); + ns.isAnyUpdate(); + } + + @Test + public void returnGetOpName() throws IOException { + final NameScript ns = getReturnNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Not an AnyUpdate op"); + ns.getOpName(); + } + + @Test + public void returnGetOpValue() throws IOException { + final NameScript ns = getReturnNameScript(); + + expectedEx.expect(org.bitcoinj.core.ScriptException.class); + expectedEx.expectMessage("Not an AnyUpdate op"); + ns.getOpValue(); + } + + // TODO: getOpRand, getOpHash, isNameScript when they're implemented + + NameScript getNameNewNameScript() throws IOException { + byte[] payload; + final Transaction tx; + final TransactionOutput out; + final Script outScript; + final NameScript ns; + + // https://namecoin.webbtc.com/tx/6047ce28a076118403aa960909c9c4d0056f97ee0da4d37d109515f8367e2ccb + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_name_new_d_bitcoin.bin")); + tx = new Transaction(params, payload); + out = tx.getOutputs().get(1); + outScript = out.getScriptPubKey(); + ns = new NameScript(outScript); + + return ns; + } + + NameScript getNameFirstUpdateNameScript() throws IOException { + byte[] payload; + final Transaction tx; + final TransactionOutput out; + final Script outScript; + final NameScript ns; + + // https://namecoin.webbtc.com/tx/ab1207bd605af57ed0b5325ac94d19578cff3bce668ebe8dda2f42a00b001f5d + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_name_firstupdate_d_bitcoin.bin")); + tx = new Transaction(params, payload); + out = tx.getOutputs().get(1); + outScript = out.getScriptPubKey(); + ns = new NameScript(outScript); + + return ns; + } + + NameScript getNameUpdateNameScript() throws IOException { + byte[] payload; + final Transaction tx; + final TransactionOutput out; + final Script outScript; + final NameScript ns; + + // https://namecoin.webbtc.com/tx/3376c5e0e5b69d0a104863de8432d7c13f891065e7628a72487b770c6418d397 + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_name_update_d_bitcoin.bin")); + tx = new Transaction(params, payload); + out = tx.getOutputs().get(1); + outScript = out.getScriptPubKey(); + ns = new NameScript(outScript); + + return ns; + } + + NameScript getCurrencyNameScript() throws IOException { + byte[] payload; + final Transaction tx; + final TransactionOutput out; + final Script outScript; + final NameScript ns; + + // https://namecoin.webbtc.com/tx/4ea5d679d63ef46449a44ca056584a986412676641bdaf13d44a7c7c2e32cca1 + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_p2pkh.bin")); + tx = new Transaction(params, payload); + out = tx.getOutputs().get(0); + outScript = out.getScriptPubKey(); + ns = new NameScript(outScript); + + return ns; + } + + NameScript getReturnNameScript() throws IOException { + byte[] payload; + final Transaction tx; + final TransactionOutput out; + final Script outScript; + final NameScript ns; + + // https://namecoin.webbtc.com/tx/ab1207bd605af57ed0b5325ac94d19578cff3bce668ebe8dda2f42a00b001f5d + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_name_firstupdate_d_bitcoin.bin")); + tx = new Transaction(params, payload); + out = tx.getOutputs().get(2); + outScript = out.getScriptPubKey(); + ns = new NameScript(outScript); + + return ns; + } +} diff --git a/core/src/test/resources/org/libdohj/script/namecoin_name_firstupdate_d_bitcoin.bin b/core/src/test/resources/org/libdohj/script/namecoin_name_firstupdate_d_bitcoin.bin new file mode 100644 index 0000000000000000000000000000000000000000..b58e7ffdfc8b5bf3f607f1c628151079982d41bd GIT binary patch literal 476 zcmV<20VDnZaR2}U%Pf92_!X59ebb~3?w4-`(8S3J36`n@gb{Y2D9%S<0RR91j7cy? z0wDnF!ukV6Otc=?+wMJ0gv8#|M6duz2lo!&V1X)l`sApoYY&C}OOFSh;IZ=`SH zbDuhE>`JG$c9G?ye3Ag1|9AmG1P1deXm-?J8pT3WzVq z;aC8z_%|NjF3Q6&NZ z0000(K?E?InH*2H!%a6L%6E2o3{YyvV$^CYsurHHPT0wy$-SA6SAo033H^ zVsK$+Wn^V`Wo&P7WpXZUWiM`FZDnI`X>M(8cBvHG$HMy1nSe-rq=et>V>zEW0!>u*UBYRA;6%R;ea!47> zVU3MlWX*N_q2)47etj-)Mn{%QNlmV2|KyyHD$_NvUa^q-s&8b?-_sw~SSas$mbN&1 zyUSabMHc=}L2>3=H;VEED;9RJ9r)=u!PzXL_nOL=bCdrA!Or76Pj>Hr+P}0VZ)X(C zD)WuIKcA6OeYxLuW+G$57RFCN7c+Qz7`QSiq|MGaw0*1JDrKjT<@=c(7(FchT-zY~ zXT>Cj)gn_KZNJW>$gqWpIlDQ@Yr>~@-!@+^TF=vY*2Ll*Pwc;f(`SWRk}jNK1lh<$URu7A zRkc!4YIrS4gh$Zt&)KswXsS{t=)GAOHY$tW6UD literal 0 HcmV?d00001 From 7f873b69a649b96160149c28dcced406fff3953d Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Wed, 29 Jun 2016 23:56:37 +0000 Subject: [PATCH 4/6] Added util class for processing name transactions. --- .../libdohj/names/NameTransactionUtils.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 core/src/main/java/org/libdohj/names/NameTransactionUtils.java diff --git a/core/src/main/java/org/libdohj/names/NameTransactionUtils.java b/core/src/main/java/org/libdohj/names/NameTransactionUtils.java new file mode 100644 index 00000000..fca9bdae --- /dev/null +++ b/core/src/main/java/org/libdohj/names/NameTransactionUtils.java @@ -0,0 +1,76 @@ +/* + * 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.libdohj.script.NameScript; + +import org.bitcoinj.core.ScriptException; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.script.Script; + +import java.io.UnsupportedEncodingException; + +// TODO: document this + +// TODO: look into allowing name and value to be ArrayList as an alternative to String. + +public class NameTransactionUtils { + + // Providing the name is, in theory, superfluous, since only 1 name output can exist per transaction. + // However, this might be changed in the future, to allow atomic updates of multiple names. + // This could enable things like CoinJoin for names. + public static TransactionOutput getNameAnyUpdateOutput(Transaction tx, String name) { + for (TransactionOutput output : tx.getOutputs()) { + try { + Script scriptPubKey = output.getScriptPubKey(); + NameScript ns = new NameScript(scriptPubKey); + if(ns.isNameOp() && ns.isAnyUpdate() && new String(ns.getOpName().data, "ISO-8859-1").equals(name)) { + return output; + } + } catch (ScriptException e) { + continue; + } catch (UnsupportedEncodingException e) { + continue; + } + } + + // No such output was found. + return null; + } + + public static NameScript getNameAnyUpdateScript(Transaction tx, String name) { + TransactionOutput output = getNameAnyUpdateOutput(tx, name); + + if (output == null) { + return null; + } + + Script scriptPubKey = output.getScriptPubKey(); + return new NameScript(scriptPubKey); + } + + public static String getNameValueAsString(Transaction tx, String name) throws UnsupportedEncodingException { + NameScript ns = getNameAnyUpdateScript(tx, name); + + if (ns == null) { + return null; + } + + return new String(ns.getOpValue().data, "ISO-8859-1"); + } +} From 80faef7303fe9541f52d92cbc60f412a465d303c Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Wed, 29 Jun 2016 23:57:55 +0000 Subject: [PATCH 5/6] Added tests for name transaction utils. --- .../names/NameTransactionUtilsTest.java | 127 ++++++++++++++++++ .../namecoin_name_firstupdate_d_bitcoin.bin | Bin 0 -> 476 bytes .../names/namecoin_name_new_d_bitcoin.bin | Bin 0 -> 257 bytes .../names/namecoin_name_update_d_bitcoin.bin | Bin 0 -> 635 bytes .../org/libdohj/names/namecoin_p2pkh.bin | Bin 0 -> 193 bytes 5 files changed, 127 insertions(+) create mode 100644 core/src/test/java/org/libdohj/names/NameTransactionUtilsTest.java create mode 100644 core/src/test/resources/org/libdohj/names/namecoin_name_firstupdate_d_bitcoin.bin create mode 100644 core/src/test/resources/org/libdohj/names/namecoin_name_new_d_bitcoin.bin create mode 100644 core/src/test/resources/org/libdohj/names/namecoin_name_update_d_bitcoin.bin create mode 100644 core/src/test/resources/org/libdohj/names/namecoin_p2pkh.bin diff --git a/core/src/test/java/org/libdohj/names/NameTransactionUtilsTest.java b/core/src/test/java/org/libdohj/names/NameTransactionUtilsTest.java new file mode 100644 index 00000000..f9d22d2a --- /dev/null +++ b/core/src/test/java/org/libdohj/names/NameTransactionUtilsTest.java @@ -0,0 +1,127 @@ +/* + * 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.libdohj.params.AbstractNamecoinParams; +import org.libdohj.params.NamecoinMainNetParams; + +import org.bitcoinj.core.Context; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.Util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +/** + * + * @author Jeremy Rand + */ +public class NameTransactionUtilsTest { + private static final AbstractNamecoinParams params = NamecoinMainNetParams.get(); + + @Before + public void setUp() throws Exception { + Context context = new Context(params); + } + + @Test + public void nameNewGetValueAsString() throws IOException { + final Transaction tx = getNameNewTransaction(); + + assertNull(NameTransactionUtils.getNameValueAsString(tx, "d/bitcoin")); + + assertNull(NameTransactionUtils.getNameValueAsString(tx, "wrongname")); + } + + @Test + public void nameFirstUpdateGetValueAsString() throws IOException { + final Transaction tx = getNameFirstUpdateTransaction(); + + assertEquals("webpagedeveloper.me/namecoin", NameTransactionUtils.getNameValueAsString(tx, "d/bitcoin")); + + assertNull(NameTransactionUtils.getNameValueAsString(tx, "wrongname")); + } + + @Test + public void nameUpdateGetValueAsString() throws IOException { + final Transaction tx = getNameUpdateTransaction(); + + assertEquals("{\"info\":{\"registrar\":\"http://register.dot-bit.org\"},\"email\": \"register@dot-bit.org\",\"ns\":[\"ns0.web-sweet-web.net\",\"ns1.web-sweet-web.net\"],\"map\":{\"\":{\"ns\":[\"ns0.web-sweet-web.net\",\"ns1.web-sweet-web.net\"]}}}", NameTransactionUtils.getNameValueAsString(tx, "d/bitcoin")); + + assertNull(NameTransactionUtils.getNameValueAsString(tx, "wrongname")); + } + + @Test + public void currencyGetValueAsString() throws IOException { + final Transaction tx = getCurrencyTransaction(); + + assertNull(NameTransactionUtils.getNameValueAsString(tx, "d/bitcoin")); + + assertNull(NameTransactionUtils.getNameValueAsString(tx, "wrongname")); + } + + Transaction getNameNewTransaction() throws IOException { + byte[] payload; + final Transaction tx; + + // https://namecoin.webbtc.com/tx/6047ce28a076118403aa960909c9c4d0056f97ee0da4d37d109515f8367e2ccb + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_name_new_d_bitcoin.bin")); + tx = new Transaction(params, payload); + + return tx; + } + + Transaction getNameFirstUpdateTransaction() throws IOException { + byte[] payload; + final Transaction tx; + + // https://namecoin.webbtc.com/tx/ab1207bd605af57ed0b5325ac94d19578cff3bce668ebe8dda2f42a00b001f5d + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_name_firstupdate_d_bitcoin.bin")); + tx = new Transaction(params, payload); + + return tx; + } + + Transaction getNameUpdateTransaction() throws IOException { + byte[] payload; + final Transaction tx; + + // https://namecoin.webbtc.com/tx/3376c5e0e5b69d0a104863de8432d7c13f891065e7628a72487b770c6418d397 + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_name_update_d_bitcoin.bin")); + tx = new Transaction(params, payload); + + return tx; + } + + Transaction getCurrencyTransaction() throws IOException { + byte[] payload; + final Transaction tx; + + // https://namecoin.webbtc.com/tx/4ea5d679d63ef46449a44ca056584a986412676641bdaf13d44a7c7c2e32cca1 + + payload = Util.getBytes(getClass().getResourceAsStream("namecoin_p2pkh.bin")); + tx = new Transaction(params, payload); + + return tx; + } +} diff --git a/core/src/test/resources/org/libdohj/names/namecoin_name_firstupdate_d_bitcoin.bin b/core/src/test/resources/org/libdohj/names/namecoin_name_firstupdate_d_bitcoin.bin new file mode 100644 index 0000000000000000000000000000000000000000..b58e7ffdfc8b5bf3f607f1c628151079982d41bd GIT binary patch literal 476 zcmV<20VDnZaR2}U%Pf92_!X59ebb~3?w4-`(8S3J36`n@gb{Y2D9%S<0RR91j7cy? z0wDnF!ukV6Otc=?+wMJ0gv8#|M6duz2lo!&V1X)l`sApoYY&C}OOFSh;IZ=`SH zbDuhE>`JG$c9G?ye3Ag1|9AmG1P1deXm-?J8pT3WzVq z;aC8z_%|NjF3Q6&NZ z0000(K?E?InH*2H!%a6L%6E2o3{YyvV$^CYsurHHPT0wy$-SA6SAo033H^ zVsK$+Wn^V`Wo&P7WpXZUWiM`FZDnI`X>M(8cBvHG$HMy1nSe-rq=et>V>zEW0!>u*UBYRA;6%R;ea!47> zVU3MlWX*N_q2)47etj-)Mn{%QNlmV2|KyyHD$_NvUa^q-s&8b?-_sw~SSas$mbN&1 zyUSabMHc=}L2>3=H;VEED;9RJ9r)=u!PzXL_nOL=bCdrA!Or76Pj>Hr+P}0VZ)X(C zD)WuIKcA6OeYxLuW+G$57RFCN7c+Qz7`QSiq|MGaw0*1JDrKjT<@=c(7(FchT-zY~ zXT>Cj)gn_KZNJW>$gqWpIlDQ@Yr>~@-!@+^TF=vY*2Ll*Pwc;f(`SWRk}jNK1lh<$URu7A zRkc!4YIrS4gh$Zt&)KswXsS{t=)GAOHY$tW6UD literal 0 HcmV?d00001 From ca28f2a0bac772eac61ac926cb297351e56a5f1a Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Thu, 30 Jun 2016 03:01:08 +0000 Subject: [PATCH 6/6] Add classes for name lookups with SPV verification. --- .gitignore | 1 + namecoin/pom.xml | 112 ++++++++++ .../libdohj/names/NameLookupByBlockHash.java | 29 +++ .../NameLookupByBlockHashOneFullBlock.java | 61 ++++++ .../names/NameLookupByBlockHeight.java | 27 +++ .../NameLookupByBlockHeightHashCache.java | 108 ++++++++++ .../org/libdohj/names/NameLookupLatest.java | 27 +++ .../names/NameLookupLatestRestHeightApi.java | 141 +++++++++++++ .../names/NameLookupLatestRestMerkleApi.java | 191 ++++++++++++++++++ ...NameLookupLatestRestMerkleApiSingleTx.java | 55 +++++ 10 files changed, 752 insertions(+) create mode 100644 namecoin/pom.xml create mode 100644 namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHash.java create mode 100644 namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHashOneFullBlock.java create mode 100644 namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHeight.java create mode 100644 namecoin/src/main/java/org/libdohj/names/NameLookupByBlockHeightHashCache.java create mode 100644 namecoin/src/main/java/org/libdohj/names/NameLookupLatest.java create mode 100644 namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestHeightApi.java create mode 100644 namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestMerkleApi.java create mode 100644 namecoin/src/main/java/org/libdohj/names/NameLookupLatestRestMerkleApiSingleTx.java 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; + } + +}