diff --git a/core/src/main/java/org/bitcoinj/core/Block.java b/core/src/main/java/org/bitcoinj/core/Block.java index edde80e38c6..41d3f6fc443 100644 --- a/core/src/main/java/org/bitcoinj/core/Block.java +++ b/core/src/main/java/org/bitcoinj/core/Block.java @@ -66,6 +66,8 @@ public enum VerifyFlag { /** How many bytes are required to represent a block header WITHOUT the trailing 00 length byte. */ public static final int HEADER_SIZE = 80; + + public static final int HEADER_HYBRID_CONSENSUS_SIZE = 140; static final long ALLOWED_TIME_DRIFT = 2 * 60 * 60; // Same value as Bitcoin Core. @@ -105,6 +107,18 @@ public enum VerifyFlag { private long time; private long difficultyTarget; // "nBits" private long nonce; + + // Hybrid Consensus Block + private long stakeDifficulty; + private long voteBits; + private long ticketPoolSize; + private long ticketLotteryState; + private long voters; + private long freshStake; + private long revocations; + private Sha256Hash extraData; + private long stakeVersion; + // TODO: Get rid of all the direct accesses to this field. It's a long-since unnecessary holdover from the Dalvik days. /** If null, it means this object holds only the headers. */ @@ -125,12 +139,25 @@ public enum VerifyFlag { Block(NetworkParameters params, long setVersion) { super(params); // Set up a few basic things. We are not complete after this though. - version = setVersion; + version = setVersion; difficultyTarget = 0x1d07fff8L; time = System.currentTimeMillis() / 1000; prevBlockHash = Sha256Hash.ZERO_HASH; - length = HEADER_SIZE; + length = getBlockHeaderSize(); + + if(isHybridBlock()) { + // Hybrid consensus block + stakeDifficulty = 0; + voteBits = 0; + ticketPoolSize = 0; + ticketLotteryState = 0; + voters = 0; + freshStake = 0; + revocations = 0; + extraData = Sha256Hash.ZERO_HASH; + stakeVersion = 0; + } } /** @@ -202,16 +229,32 @@ public Block(NetworkParameters params, byte[] payloadBytes, int offset, @Nullabl * @param transactions List of transactions including the coinbase. */ public Block(NetworkParameters params, long version, Sha256Hash prevBlockHash, Sha256Hash merkleRoot, long time, - long difficultyTarget, long nonce, List transactions) { + long difficultyTarget, long nonce, List transactions, + long stakeDifficulty, long voteBits, long ticketPoolSize, + long ticketLotteryState, long freshStake, long stakeVersion, + long voters, long revocations,Sha256Hash extraData) { super(params); this.version = version; this.prevBlockHash = prevBlockHash; this.merkleRoot = merkleRoot; this.time = time; - this.difficultyTarget = difficultyTarget; + this.difficultyTarget = difficultyTarget; this.nonce = nonce; this.transactions = new LinkedList(); this.transactions.addAll(transactions); + + if(isHybridBlock()) { + // Hybrid consensus block + this.stakeDifficulty = stakeDifficulty; + this.voteBits = voteBits; + this.ticketPoolSize = ticketPoolSize; + this.ticketLotteryState = ticketLotteryState; + this.voters = voters; + this.freshStake = freshStake; + this.revocations = revocations; + this.extraData = extraData; + this.stakeVersion = stakeVersion; + } } @@ -237,7 +280,7 @@ public Coin getBlockInflation(int height) { */ protected void parseTransactions(final int transactionsOffset) throws ProtocolException { cursor = transactionsOffset; - optimalEncodingMessageSize = HEADER_SIZE; + optimalEncodingMessageSize = getBlockHeaderSize(); if (payload.length == cursor) { // This message is just a header, it has no transactions. transactionBytesValid = false; @@ -266,13 +309,28 @@ protected void parse() throws ProtocolException { prevBlockHash = readHash(); merkleRoot = readHash(); time = readUint32(); - difficultyTarget = readUint32(); + difficultyTarget = readUint32(); nonce = readUint32(); + if(isHybridBlock()) { + // Hybrid consensus block + stakeDifficulty = readInt64(); + voteBits = readUint16(); + ticketPoolSize = readUint32(); + ticketLotteryState = readUint32(); + voters = readUint16(); + freshStake = readUint16(); + revocations = readUint16(); + extraData = readHash(); + stakeVersion = readUint32(); + } + hash = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(payload, offset, cursor - offset)); + headerBytesValid = serializer.isParseRetainMode(); // transactions - parseTransactions(offset + HEADER_SIZE); + parseTransactions(offset + getBlockHeaderSize()); + length = cursor - offset; } @@ -286,8 +344,8 @@ public int getOptimalEncodingMessageSize() { // default for testing void writeHeader(OutputStream stream) throws IOException { // try for cached write first - if (headerBytesValid && payload != null && payload.length >= offset + HEADER_SIZE) { - stream.write(payload, offset, HEADER_SIZE); + if (headerBytesValid && payload != null && payload.length >= offset + getBlockHeaderSize()) { + stream.write(payload, offset, getBlockHeaderSize()); return; } // fall back to manual write @@ -295,8 +353,21 @@ void writeHeader(OutputStream stream) throws IOException { stream.write(prevBlockHash.getReversedBytes()); stream.write(getMerkleRoot().getReversedBytes()); Utils.uint32ToByteStreamLE(time, stream); - Utils.uint32ToByteStreamLE(difficultyTarget, stream); + Utils.uint32ToByteStreamLE(difficultyTarget, stream); //nBits Utils.uint32ToByteStreamLE(nonce, stream); + + if(isHybridBlock()) { + // Hybrid consensus block + Utils.uint32ToByteStreamLE(stakeDifficulty, stream); + Utils.uint32ToByteStreamLE(voteBits, stream); + Utils.uint32ToByteStreamLE(ticketPoolSize, stream); + Utils.uint32ToByteStreamLE(ticketLotteryState, stream); + Utils.uint32ToByteStreamLE(voters, stream); + Utils.uint32ToByteStreamLE(freshStake, stream); + Utils.uint32ToByteStreamLE(revocations, stream); + stream.write(extraData.getReversedBytes()); + Utils.uint32ToByteStreamLE(stakeVersion, stream); + } } private void writeTransactions(OutputStream stream) throws IOException { @@ -308,7 +379,7 @@ private void writeTransactions(OutputStream stream) throws IOException { // confirmed we must have transactions either cached or as objects. if (transactionBytesValid && payload != null && payload.length >= offset + length) { - stream.write(payload, offset + HEADER_SIZE, length - HEADER_SIZE); + stream.write(payload, offset + getBlockHeaderSize(), length - getBlockHeaderSize()); return; } @@ -343,7 +414,7 @@ public byte[] bitcoinSerialize() { // At least one of the two cacheable components is invalid // so fall back to stream write since we can't be sure of the length. - ByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(length == UNKNOWN_LENGTH ? HEADER_SIZE + guessTransactionsLength() : length); + ByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(length == UNKNOWN_LENGTH ? getBlockHeaderSize() + guessTransactionsLength() : length); try { writeHeader(stream); writeTransactions(stream); @@ -370,7 +441,7 @@ protected void bitcoinSerializeToStream(OutputStream stream) throws IOException */ private int guessTransactionsLength() { if (transactionBytesValid) - return payload.length - HEADER_SIZE; + return payload.length - getBlockHeaderSize(); if (transactions == null) return 0; int len = VarInt.sizeOf(transactions.size()); @@ -413,7 +484,8 @@ private void unCacheTransactions() { */ private Sha256Hash calculateHash() { try { - ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(HEADER_SIZE); + ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(getBlockHeaderSize()); + writeHeader(bos); return Sha256Hash.wrapReversed(Sha256Hash.hashTwice(bos.toByteArray())); } catch (IOException e) { @@ -468,7 +540,7 @@ public Block cloneAsHeader() { } /** Copy the block without transactions into the provided empty block. */ - protected final void copyBitcoinHeaderTo(final Block block) { + protected final void copyBitcoinHeaderTo(final Block block) { block.nonce = nonce; block.prevBlockHash = prevBlockHash; block.merkleRoot = getMerkleRoot(); @@ -476,6 +548,20 @@ protected final void copyBitcoinHeaderTo(final Block block) { block.time = time; block.difficultyTarget = difficultyTarget; block.transactions = null; + + if(isHybridBlock()) { + // Hybrid Block + block.stakeDifficulty = stakeDifficulty; + block.voteBits = voteBits; + block.ticketPoolSize = ticketPoolSize; + block.ticketLotteryState = ticketLotteryState; + block.voters = voters; + block.freshStake = freshStake; + block.revocations = revocations; + block.extraData = extraData; + block.stakeVersion = stakeVersion; + } + block.hash = getHash(); } @@ -488,7 +574,7 @@ public String toString() { StringBuilder s = new StringBuilder(); s.append(" block: \n"); s.append(" hash: ").append(getHashAsString()).append('\n'); - s.append(" version: ").append(version); + s.append(" version: ").append(version).append('\n'); String bips = Joiner.on(", ").skipNulls().join(isBIP34() ? "BIP34" : null, isBIP66() ? "BIP66" : null, isBIP65() ? "BIP65" : null); if (!bips.isEmpty()) @@ -554,6 +640,15 @@ protected boolean checkProofOfWork(boolean throwException) throws VerificationEx BigInteger target = getDifficultyTargetAsInteger(); BigInteger h = getHash().toBigInteger(); + + if(BigInteger.valueOf(getVersion()).compareTo(params.hybridConsensusVersion) > 0) { + // TODO: + // What validations should be here? + // Could validate TestHybridConsensusTestFramework from hybrid_consensus_test_framework.py + // For now we return true + return true; + } + if (h.compareTo(target) > 0) { // Proof of work check failed! if (throwException) @@ -1040,4 +1135,54 @@ public boolean isBIP66() { public boolean isBIP65() { return version >= BLOCK_VERSION_BIP65; } + + // Hybrid consensus block + public long getStakeDifficulty() { + return stakeDifficulty; + } + + public long getVoteBits() { + return voteBits; + } + + public long getTicketPoolSize() { + return ticketPoolSize; + } + + public long getTicketLotteryState() { + return ticketLotteryState; + } + + public long getFreshStake() { + return freshStake; + } + + public long getStakeVersion() { + return stakeVersion; + } + + public long getVoters() { + return voters; + } + + public long getRevocations() { + return revocations; + } + + public Sha256Hash getExtraData() { + return extraData; + } + + private boolean isHybridBlock() { + return (params.hybridConsensusVersion != null) && BigInteger.valueOf(getVersion()).compareTo(params.hybridConsensusVersion) > 0; + } + + private int getBlockHeaderSize(){ + int headerSize = HEADER_SIZE; + if(isHybridBlock()) { + headerSize = HEADER_HYBRID_CONSENSUS_SIZE; + } + + return headerSize; + } } diff --git a/core/src/main/java/org/bitcoinj/core/ListMessage.java b/core/src/main/java/org/bitcoinj/core/ListMessage.java index 0a7dafe8037..5cd487c23eb 100644 --- a/core/src/main/java/org/bitcoinj/core/ListMessage.java +++ b/core/src/main/java/org/bitcoinj/core/ListMessage.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -129,4 +130,12 @@ public boolean equals(Object o) { public int hashCode() { return items.hashCode(); } + + @Override + public String toString() { + return "ListMessage [arrayLen=" + arrayLen + ", items=" + items + ", offset=" + offset + ", cursor=" + cursor + + ", length=" + length + ", payload=" + Arrays.toString(payload) + ", recached=" + recached + + ", serializer=" + serializer + ", protocolVersion=" + protocolVersion + ", params=" + params + "]"; + } + } diff --git a/core/src/main/java/org/bitcoinj/core/Message.java b/core/src/main/java/org/bitcoinj/core/Message.java index e6f5cd2be3a..424b0f4a4c4 100644 --- a/core/src/main/java/org/bitcoinj/core/Message.java +++ b/core/src/main/java/org/bitcoinj/core/Message.java @@ -286,7 +286,17 @@ public final int getMessageSize() { checkState(false, "Length field has not been set in %s.", getClass().getSimpleName()); return length; } - + + protected long readUint16() throws ProtocolException { + try { + long u = Utils.readUint16BE(payload, cursor); + cursor += 2; + return u; + } catch (ArrayIndexOutOfBoundsException e) { + throw new ProtocolException(e); + } + } + protected long readUint32() throws ProtocolException { try { long u = Utils.readUint32(payload, cursor); @@ -331,7 +341,7 @@ protected byte[] readBytes(int length) throws ProtocolException { throw new ProtocolException("Claimed value length too large: " + length); } try { - byte[] b = new byte[length]; + byte[] b = new byte[length]; System.arraycopy(payload, cursor, b, 0, length); cursor += length; return b; diff --git a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java index e3e17abd44a..c85b00dedde 100644 --- a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java +++ b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java @@ -112,6 +112,8 @@ protected NetworkParameters() { alertSigningKey = SATOSHI_KEY; genesisBlock = createGenesis(this); } + + protected BigInteger hybridConsensusVersion; private static Block createGenesis(NetworkParameters n) { Block genesisBlock = new Block(n, Block.BLOCK_VERSION_GENESIS); @@ -127,6 +129,7 @@ private static Block createGenesis(NetworkParameters n) { if (n instanceof MainNetParams) { Script.writeBytes(scriptPubKeyBytes, Utils.HEX.decode("95ba0161eb524f97d3847653057baaef7d7ba0ff")); } else if (n instanceof TestNet3Params) { + // TESTNET_GENESIS_BLOCK_SIGNATURE - chainparams.cpp Script.writeBytes(scriptPubKeyBytes, Utils.HEX.decode("9a8abac6c3d97d37d627e6ebcaf68be72275168b")); } else { Script.writeBytes(scriptPubKeyBytes, Utils.HEX.decode("23103f0e2d2abbaad0d79b7a37759b1a382b7821")); diff --git a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java index 2c759b43c53..0290886285e 100644 --- a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java +++ b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java @@ -37,24 +37,32 @@ public class TestNet3Params extends AbstractBitcoinNetParams { public TestNet3Params() { super(); + + // TODO: to validate, from block.h HARDFORK_VERSION_BIT parameter + hybridConsensusVersion = BigInteger.valueOf(2147483648L); + id = ID_TESTNET; // Genesis hash is 0000000003976df1a1393912d10ea68fae1175ee2c7e6011a0dc4e05f18f8403 packetMagic = 0x0b091107; interval = INTERVAL; targetTimespan = TARGET_TIMESPAN; - maxTarget = Utils.decodeCompactBits(0x1c09fe61L); + + // TODO: to validate, obtained from chainparams.cpp, from TESTNET_GENESIS_BLOCK_NBITS field + maxTarget = Utils.decodeCompactBits(0x1e00ffff); + port = 18567; addressHeader = 51; p2shHeader = 180; acceptableAddressCodes = new int[] { addressHeader, p2shHeader }; dumpedPrivateKeyHeader = 226; genesisBlock.setTime(1504706516L); - genesisBlock.setDifficultyTarget(0x1c09fe61L); + genesisBlock.setDifficultyTarget(0x1c09fe61L); genesisBlock.setNonce(2253953817L); spendableCoinbaseDepth = 100; subsidyDecreaseBlockCount = 210000; String genesisHash = genesisBlock.getHashAsString(); checkState(genesisHash.equals("0000000003976df1a1393912d10ea68fae1175ee2c7e6011a0dc4e05f18f8403")); + // Paicoin no need for a different alert key for the moment //alertSigningKey = Utils.HEX.decode("04302390343f91cc401d56d68b123028bf52e5fca1939df127f63c6467cdf9c8e2c14b61104cf817d0b780da337893ecc4aaff1309e536162dabbdb45200ca2b0a"); @@ -69,7 +77,7 @@ public TestNet3Params() { majorityEnforceBlockUpgrade = 51; majorityRejectBlockOutdated = 75; - majorityWindow = 100; + majorityWindow = 100; } private static TestNet3Params instance;