mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-01 07:42:17 +00:00
Merge pull request #18 from JeremyRand/namecoin-initial-pull-request
Initial Namecoin support
This commit is contained in:
commit
faadcca7ad
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
core/target
|
||||
namecoin/target
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
|
@ -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<byte> 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");
|
||||
}
|
||||
}
|
@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
120
core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java
Normal file
120
core/src/main/java/org/libdohj/params/NamecoinMainNetParams.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
181
core/src/main/java/org/libdohj/script/NameScript.java
Normal file
181
core/src/main/java/org/libdohj/script/NameScript.java
Normal file
@ -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<ScriptChunk> args;
|
||||
|
||||
protected Script address;
|
||||
|
||||
public NameScript(Script baseScript) {
|
||||
op = OP_NOP;
|
||||
args = new ArrayList<ScriptChunk>();
|
||||
address = baseScript;
|
||||
|
||||
ScriptChunk nameOp;
|
||||
int pc = 0;
|
||||
|
||||
List<ScriptChunk> 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
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
355
core/src/test/java/org/libdohj/script/NameScriptTest.java
Normal file
355
core/src/test/java/org/libdohj/script/NameScriptTest.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/src/test/resources/org/libdohj/names/namecoin_p2pkh.bin
Normal file
BIN
core/src/test/resources/org/libdohj/names/namecoin_p2pkh.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/src/test/resources/org/libdohj/script/namecoin_p2pkh.bin
Normal file
BIN
core/src/test/resources/org/libdohj/script/namecoin_p2pkh.bin
Normal file
Binary file not shown.
112
namecoin/pom.xml
Normal file
112
namecoin/pom.xml
Normal file
@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.libdohj</groupId>
|
||||
<artifactId>libdohj-namecoin</artifactId>
|
||||
<version>0.14-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<!-- Dummy block to make Maven Central happy: authors list is in AUTHORS -->
|
||||
<developers>
|
||||
<developer>
|
||||
<name>The libdohj team.</name>
|
||||
<email>info@dogecoin.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>update-protobuf</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>updateProtobuf</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile-protoc</id>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<path id="proto.path">
|
||||
<fileset dir="src">
|
||||
<include name="**/*.proto"/>
|
||||
</fileset>
|
||||
</path>
|
||||
<pathconvert pathsep=" " property="proto.files" refid="proto.path"/>
|
||||
<exec executable="protoc" failonerror="true">
|
||||
<arg value="--java_out=${project.basedir}/src/main/java"/>
|
||||
<arg value="-I${project.basedir}/src"/>
|
||||
<arg line="${proto.files}"/>
|
||||
</exec>
|
||||
</tasks>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lambdaworks</groupId>
|
||||
<artifactId>scrypt</artifactId>
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bitcoinj</groupId>
|
||||
<artifactId>bitcoinj-core</artifactId>
|
||||
<version>0.14.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.libdohj</groupId>
|
||||
<artifactId>libdohj-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.7.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.6</maven.compiler.source>
|
||||
<maven.compiler.target>1.6</maven.compiler.target>
|
||||
</properties>
|
||||
<name>libdohj</name>
|
||||
</project>
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2016 Jeremy Rand.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.libdohj.names;
|
||||
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
// TODO: Document this.
|
||||
|
||||
// identity is used for things like Tor stream isolation
|
||||
public interface NameLookupByBlockHash {
|
||||
|
||||
public Transaction getNameTransaction(String name, Sha256Hash blockHash, String identity) throws Exception;
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2016 Jeremy Rand.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.libdohj.names;
|
||||
|
||||
import org.bitcoinj.core.Block;
|
||||
import org.bitcoinj.core.PeerGroup;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
// TODO: document this
|
||||
|
||||
public class NameLookupByBlockHashOneFullBlock implements NameLookupByBlockHash {
|
||||
|
||||
protected PeerGroup peerGroup;
|
||||
|
||||
public NameLookupByBlockHashOneFullBlock (PeerGroup peerGroup) {
|
||||
this.peerGroup = peerGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction getNameTransaction(String name, Sha256Hash blockHash, String identity) throws Exception {
|
||||
|
||||
Block nameFullBlock = peerGroup.getDownloadPeer().getBlock(blockHash).get();
|
||||
|
||||
// The full block hasn't been verified in any way!
|
||||
// So let's do that now.
|
||||
|
||||
final EnumSet<Block.VerifyFlag> flags = EnumSet.noneOf(Block.VerifyFlag.class);
|
||||
nameFullBlock.verify(-1, flags);
|
||||
|
||||
// Now we know that the block is internally valid (including the merkle root).
|
||||
// We haven't verified signature validity, but our threat model is SPV.
|
||||
|
||||
for (Transaction tx : nameFullBlock.getTransactions()) {
|
||||
if (NameTransactionUtils.getNameAnyUpdateOutput(tx, name) != null) {
|
||||
return tx;
|
||||
}
|
||||
}
|
||||
|
||||
// The name wasn't found.
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2016 Jeremy Rand.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.libdohj.names;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
// TODO: document this
|
||||
|
||||
public interface NameLookupByBlockHeight {
|
||||
|
||||
public Transaction getNameTransaction(String name, int height, String identity) throws Exception;
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2016 Jeremy Rand.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.libdohj.names;
|
||||
|
||||
import org.bitcoinj.core.BlockChain;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.StoredBlock;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
// TODO: breakout the 36000 expiration time into NetworkParameters.
|
||||
|
||||
// TODO: breakout the hash cache into its own class
|
||||
|
||||
// TODO: update blockHashCache with new blocks as they come into the chain
|
||||
|
||||
// TODO: document this
|
||||
|
||||
public class NameLookupByBlockHeightHashCache implements NameLookupByBlockHeight {
|
||||
|
||||
protected BlockChain chain;
|
||||
protected BlockStore store;
|
||||
|
||||
protected NameLookupByBlockHash hashLookup;
|
||||
|
||||
protected ConcurrentHashMap<Integer, Sha256Hash> blockHashCache;
|
||||
|
||||
public NameLookupByBlockHeightHashCache (BlockChain chain, NameLookupByBlockHash hashLookup) throws Exception {
|
||||
this.chain = chain;
|
||||
this.store = chain.getBlockStore();
|
||||
|
||||
this.hashLookup = hashLookup;
|
||||
|
||||
initBlockHashCache();
|
||||
}
|
||||
|
||||
protected void initBlockHashCache() throws BlockStoreException {
|
||||
blockHashCache = new ConcurrentHashMap<Integer, Sha256Hash>(72000);
|
||||
|
||||
StoredBlock blockPointer = chain.getChainHead();
|
||||
|
||||
int headHeight = blockPointer.getHeight();
|
||||
int reorgSafety = 120;
|
||||
int newestHeight = headHeight - reorgSafety;
|
||||
int oldestHeight = headHeight - 36000 - reorgSafety; // 36000 = name expiration
|
||||
|
||||
while (blockPointer.getHeight() >= oldestHeight) {
|
||||
|
||||
if (blockPointer.getHeight() <= newestHeight) {
|
||||
blockHashCache.put(new Integer(blockPointer.getHeight()), blockPointer.getHeader().getHash());
|
||||
}
|
||||
|
||||
blockPointer = blockPointer.getPrev(store);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction getNameTransaction(String name, int height, String identity) throws Exception {
|
||||
|
||||
Sha256Hash blockHash = getBlockHash(height);
|
||||
|
||||
Transaction tx = hashLookup.getNameTransaction(name, blockHash, identity);
|
||||
|
||||
tx.getConfidence().setAppearedAtChainHeight(height); // TODO: test this line
|
||||
tx.getConfidence().setDepthInBlocks(chain.getChainHead().getHeight() - height + 1);
|
||||
|
||||
return tx;
|
||||
|
||||
}
|
||||
|
||||
public Sha256Hash getBlockHash(int height) throws BlockStoreException {
|
||||
Sha256Hash maybeResult = blockHashCache.get(new Integer(height));
|
||||
|
||||
if (maybeResult != null) {
|
||||
return maybeResult;
|
||||
}
|
||||
|
||||
// If we got this far, the block height is uncached.
|
||||
// This could be because the block is immature,
|
||||
// or it could be because the cache is only initialized on initial startup.
|
||||
|
||||
StoredBlock blockPointer = chain.getChainHead();
|
||||
|
||||
while (blockPointer.getHeight() != height) {
|
||||
blockPointer = blockPointer.getPrev(store);
|
||||
}
|
||||
|
||||
return blockPointer.getHeader().getHash();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2016 Jeremy Rand.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.libdohj.names;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
// TODO: document this
|
||||
|
||||
public interface NameLookupLatest {
|
||||
|
||||
public Transaction getNameTransaction(String name, String identity) throws Exception;
|
||||
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2016 Jeremy Rand.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.libdohj.names;
|
||||
|
||||
import org.bitcoinj.core.BlockChain;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO: document this
|
||||
|
||||
public class NameLookupLatestRestHeightApi implements NameLookupLatest {
|
||||
|
||||
protected BlockChain chain;
|
||||
protected NameLookupByBlockHeight heightLookup;
|
||||
protected String restUrlPrefix;
|
||||
protected String restUrlSuffix;
|
||||
|
||||
public NameLookupLatestRestHeightApi (String restUrlPrefix, String restUrlSuffix, BlockChain chain, NameLookupByBlockHeight heightLookup) {
|
||||
this.restUrlPrefix = restUrlPrefix;
|
||||
this.restUrlSuffix = restUrlSuffix;
|
||||
this.chain = chain;
|
||||
this.heightLookup = heightLookup;
|
||||
}
|
||||
|
||||
// TODO: make a new Exception class
|
||||
@Override
|
||||
public Transaction getNameTransaction(String name, String identity) throws Exception {
|
||||
|
||||
int height = getHeight(name);
|
||||
|
||||
return heightLookup.getNameTransaction(name, height, identity);
|
||||
|
||||
}
|
||||
|
||||
// TODO: break out the getHeight into its own class + interface
|
||||
// TODO: add identity isolation
|
||||
// TODO: use an older height if the newest height has insufficient confirmations, instead of throwing an Exception
|
||||
// NOTE: this might fail if special characters are in the name, since it's not URL-escaping them.
|
||||
public int getHeight(String name) throws Exception {
|
||||
ArrayList<NameData> untrustedNameHistory = getUntrustedNameHistory(name);
|
||||
|
||||
int height;
|
||||
|
||||
int index;
|
||||
|
||||
for (index = untrustedNameHistory.size() - 1; index >= 0; index--) {
|
||||
|
||||
height = untrustedNameHistory.get(index).height;
|
||||
try {
|
||||
verifyHeightTrustworthy(height);
|
||||
return height;
|
||||
}
|
||||
catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Height not trustworthy or name does not exist.");
|
||||
}
|
||||
|
||||
// TODO: add identity isolation
|
||||
protected ArrayList<NameData> getUntrustedNameHistory(String name) throws Exception {
|
||||
URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
ArrayList<NameData> untrustedNameHistory = new ArrayList<NameData>(Arrays.asList(mapper.readValue(nameUrl, NameData[].class)));
|
||||
|
||||
return untrustedNameHistory;
|
||||
}
|
||||
|
||||
protected void verifyHeightTrustworthy(int height) throws Exception {
|
||||
if (height < 1) {
|
||||
throw new Exception("Nonpositive block height; not trustworthy!");
|
||||
}
|
||||
|
||||
int headHeight = chain.getChainHead().getHeight();
|
||||
|
||||
int confirmations = headHeight - height + 1;
|
||||
|
||||
// TODO: optionally use transaction chains (with signature checks) to verify transactions without 12 confirmations
|
||||
// TODO: the above needs to be optional, because some applications (e.g. cert transparency) require confirmations
|
||||
if (confirmations < 12) {
|
||||
throw new Exception("Block does not yet have 12 confirmations; not trustworthy!");
|
||||
}
|
||||
|
||||
// TODO: check for off-by-one errors on this line
|
||||
if (confirmations >= 36000) {
|
||||
throw new Exception("Block has expired; not trustworthy!");
|
||||
}
|
||||
}
|
||||
|
||||
static protected class NameData {
|
||||
|
||||
public String name;
|
||||
public String value;
|
||||
public String txid;
|
||||
public String address;
|
||||
public int expires_in;
|
||||
public int height;
|
||||
|
||||
@JsonCreator
|
||||
public NameData(@JsonProperty("name") String name,
|
||||
@JsonProperty("value") String value,
|
||||
@JsonProperty("txid") String txid,
|
||||
@JsonProperty("address") String address,
|
||||
@JsonProperty("expires_in") int expires_in,
|
||||
@JsonProperty("height") int height) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.txid = txid;
|
||||
this.address = address;
|
||||
this.expires_in = expires_in;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2016 Jeremy Rand.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.libdohj.names;
|
||||
|
||||
import org.bitcoinj.core.Block;
|
||||
import org.bitcoinj.core.BlockChain;
|
||||
import org.bitcoinj.core.MerkleBranch;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.Utils;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO: document this
|
||||
|
||||
public class NameLookupLatestRestMerkleApi implements NameLookupLatest {
|
||||
|
||||
protected NetworkParameters params;
|
||||
protected BlockChain chain;
|
||||
protected BlockStore store;
|
||||
protected NameLookupByBlockHeightHashCache heightLookup; // only needed for the hash cache
|
||||
protected String restUrlPrefix;
|
||||
protected String restUrlSuffix;
|
||||
|
||||
// TODO: break out the hash cache into its own class so that we don't need the NameLookup features.
|
||||
public NameLookupLatestRestMerkleApi (NetworkParameters params, String restUrlPrefix, String restUrlSuffix, BlockChain chain, BlockStore store, NameLookupByBlockHeightHashCache heightLookup) {
|
||||
this.params = params;
|
||||
this.restUrlPrefix = restUrlPrefix;
|
||||
this.restUrlSuffix = restUrlSuffix;
|
||||
this.chain = chain;
|
||||
this.store = store;
|
||||
this.heightLookup = heightLookup;
|
||||
}
|
||||
|
||||
// TODO: make a new Exception class
|
||||
@Override
|
||||
public Transaction getNameTransaction(String name, String identity) throws Exception {
|
||||
|
||||
NameData data = getLatestUntrustedNameData(name);
|
||||
|
||||
Sha256Hash blockHash = heightLookup.getBlockHash(data.height);
|
||||
|
||||
Block blockHeader = store.get(blockHash).getHeader();
|
||||
|
||||
// Convert merkle hashes from String to Sha256Hash
|
||||
ArrayList<Sha256Hash> merkleHashes = new ArrayList<Sha256Hash>(data.mrkl_branch.size());
|
||||
for (String merkleHashString : data.mrkl_branch) {
|
||||
merkleHashes.add(Sha256Hash.wrap(merkleHashString));
|
||||
}
|
||||
|
||||
long merkleBranchSideMask = data.tx_idx;
|
||||
|
||||
MerkleBranch branch = new MerkleBranch(params, null, merkleHashes, merkleBranchSideMask);
|
||||
|
||||
Transaction tx = new Transaction(params, Utils.HEX.decode(data.rawtx));
|
||||
|
||||
Sha256Hash txId = tx.getHash();
|
||||
|
||||
if(! blockHeader.getMerkleRoot().equals(branch.calculateMerkleRoot(txId))) {
|
||||
throw new Exception("Merkle proof failed to verify!");
|
||||
}
|
||||
|
||||
tx.getConfidence().setAppearedAtChainHeight(data.height); // TODO: test this line
|
||||
tx.getConfidence().setDepthInBlocks(chain.getChainHead().getHeight() - data.height + 1);
|
||||
|
||||
if (NameTransactionUtils.getNameAnyUpdateOutput(tx, name) == null) {
|
||||
throw new Exception("Not a name_anyupdate transaction or wrong name!");
|
||||
}
|
||||
|
||||
return tx;
|
||||
|
||||
}
|
||||
|
||||
// TODO: break out the getHeight into its own class + interface
|
||||
// TODO: add identity isolation
|
||||
// TODO: use an older height if the newest height has insufficient confirmations, instead of throwing an Exception
|
||||
// NOTE: this might fail if special characters are in the name, since it's not URL-escaping them.
|
||||
public NameData getLatestUntrustedNameData(String name) throws Exception {
|
||||
ArrayList<NameData> untrustedNameHistory = getUntrustedNameHistory(name);
|
||||
|
||||
int height;
|
||||
|
||||
int index;
|
||||
|
||||
for (index = untrustedNameHistory.size() - 1; index >= 0; index--) {
|
||||
|
||||
NameData candidate = untrustedNameHistory.get(index);
|
||||
try {
|
||||
verifyHeightTrustworthy(candidate.height);
|
||||
return candidate;
|
||||
}
|
||||
catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Height not trustworthy or name does not exist.");
|
||||
}
|
||||
|
||||
// TODO: add identity isolation
|
||||
protected ArrayList<NameData> getUntrustedNameHistory(String name) throws Exception {
|
||||
URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
ArrayList<NameData> untrustedNameHistory = new ArrayList<NameData>(Arrays.asList(mapper.readValue(nameUrl, NameData[].class)));
|
||||
|
||||
return untrustedNameHistory;
|
||||
}
|
||||
|
||||
protected void verifyHeightTrustworthy(int height) throws Exception {
|
||||
if (height < 1) {
|
||||
throw new Exception("Nonpositive block height; not trustworthy!");
|
||||
}
|
||||
|
||||
int headHeight = chain.getChainHead().getHeight();
|
||||
|
||||
int confirmations = headHeight - height + 1;
|
||||
|
||||
// TODO: optionally use transaction chains (with signature checks) to verify transactions without 12 confirmations
|
||||
// TODO: the above needs to be optional, because some applications (e.g. cert transparency) require confirmations
|
||||
if (confirmations < 12) {
|
||||
throw new Exception("Block does not yet have 12 confirmations; not trustworthy!");
|
||||
}
|
||||
|
||||
// TODO: check for off-by-one errors on this line
|
||||
if (confirmations >= 36000) {
|
||||
throw new Exception("Block has expired; not trustworthy!");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: break this out into its own class; add the extra fields to bitcoinj-addons too
|
||||
static protected class NameData {
|
||||
|
||||
public String name;
|
||||
public String value;
|
||||
public String txid;
|
||||
public String address;
|
||||
public int expires_in;
|
||||
public int height;
|
||||
public long tx_idx;
|
||||
public ArrayList<String> mrkl_branch;
|
||||
public String rawtx;
|
||||
|
||||
@JsonCreator
|
||||
public NameData(@JsonProperty("name") String name,
|
||||
@JsonProperty("value") String value,
|
||||
@JsonProperty("txid") String txid,
|
||||
@JsonProperty("address") String address,
|
||||
@JsonProperty("expires_in") int expires_in,
|
||||
@JsonProperty("height") int height,
|
||||
@JsonProperty("tx_idx") long tx_idx,
|
||||
@JsonProperty("mrkl_branch") ArrayList<String> mrkl_branch,
|
||||
@JsonProperty("rawtx") String rawtx) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.txid = txid;
|
||||
this.address = address;
|
||||
this.expires_in = expires_in;
|
||||
this.height = height;
|
||||
this.tx_idx = tx_idx;
|
||||
this.mrkl_branch = mrkl_branch;
|
||||
this.rawtx = rawtx;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2016 Jeremy Rand.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.libdohj.names;
|
||||
|
||||
import org.bitcoinj.core.BlockChain;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
// This lookup client only downloads a single transaction from the API rather than a history.
|
||||
// This means that it's usually faster, but the API has to be careful to choose the correct transaction.
|
||||
// As of writing (2016 Jun 26), webbtc does *not* always make the correct choice.
|
||||
// That means that using this lookup client will result in an incorrect "nonexistent" result
|
||||
// if the latest name_update for the targeted name has a depth between 1 and 11 (inclusive).
|
||||
// I'm engaging with Marius from webbtc and hope to have a solution soon.
|
||||
// -- Jeremy
|
||||
|
||||
public class NameLookupLatestRestMerkleApiSingleTx extends NameLookupLatestRestMerkleApi {
|
||||
|
||||
public NameLookupLatestRestMerkleApiSingleTx (NetworkParameters params, String restUrlPrefix, String restUrlSuffix, BlockChain chain, BlockStore store, NameLookupByBlockHeightHashCache heightLookup) {
|
||||
super(params, restUrlPrefix, restUrlSuffix, chain, store, heightLookup);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ArrayList<NameData> getUntrustedNameHistory(String name) throws Exception {
|
||||
URL nameUrl = new URL(restUrlPrefix + name + restUrlSuffix);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
NameData[] untrustedNameSingleEntry = {mapper.readValue(nameUrl, NameData.class)};
|
||||
ArrayList<NameData> untrustedNameHistory = new ArrayList<NameData>(Arrays.asList(untrustedNameSingleEntry));
|
||||
|
||||
return untrustedNameHistory;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user