mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-30 23:02:15 +00:00
Add PartialMerkleTree and FilteredBlock classes
This commit is contained in:
parent
a5f9c3381b
commit
661ad3cb04
112
core/src/main/java/com/google/bitcoin/core/FilteredBlock.java
Normal file
112
core/src/main/java/com/google/bitcoin/core/FilteredBlock.java
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 Matt Corallo
|
||||||
|
*
|
||||||
|
* 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 com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A FilteredBlock is used to relay a block with its transactions filtered using a bloom filter.</p>
|
||||||
|
*
|
||||||
|
* <p>It consists of the block header and a {@link PartialMerkleTree} which contains the transactions
|
||||||
|
* which matched the filter.</p>
|
||||||
|
*/
|
||||||
|
public class FilteredBlock extends Message {
|
||||||
|
private Block header;
|
||||||
|
|
||||||
|
// The PartialMerkleTree of transactions
|
||||||
|
private PartialMerkleTree merkleTree;
|
||||||
|
private Set<Sha256Hash> cachedTransactionHashes = null;
|
||||||
|
|
||||||
|
// A set of transactions who's hashes are a subset of getTransactionHashes()
|
||||||
|
// These were relayed as a part of the filteredblock getdata, ie likely weren't previously received as loose transactions
|
||||||
|
private List<Transaction> associatedTransactions = new LinkedList<Transaction>();
|
||||||
|
|
||||||
|
public FilteredBlock(NetworkParameters params, byte[] payloadBytes) throws ProtocolException {
|
||||||
|
super(params, payloadBytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||||
|
throw new RuntimeException("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void parse() throws ProtocolException {
|
||||||
|
byte[] headerBytes = new byte[Block.HEADER_SIZE];
|
||||||
|
System.arraycopy(bytes, 0, headerBytes, 0, Block.HEADER_SIZE);
|
||||||
|
header = new Block(params, headerBytes);
|
||||||
|
|
||||||
|
merkleTree = new PartialMerkleTree(params, bytes, Block.HEADER_SIZE);
|
||||||
|
|
||||||
|
length = Block.HEADER_SIZE + merkleTree.getMessageSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void parseLite() throws ProtocolException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of leaf hashes which are contained in the partial merkle tree in this filtered block
|
||||||
|
*
|
||||||
|
* @throws ProtocolException If the partial merkle block is invalid or the merkle root of the partial merkle block doesnt match the block header
|
||||||
|
*/
|
||||||
|
public Set<Sha256Hash> getTransactionHashes() throws VerificationException {
|
||||||
|
if (cachedTransactionHashes != null)
|
||||||
|
return Collections.unmodifiableSet(cachedTransactionHashes);
|
||||||
|
Set<Sha256Hash> hashesMatched = new HashSet<Sha256Hash>();
|
||||||
|
if (header.getMerkleRoot().equals(merkleTree.getTxnHashAndMerkleRoot(hashesMatched))) {
|
||||||
|
cachedTransactionHashes = hashesMatched;
|
||||||
|
return Collections.unmodifiableSet(cachedTransactionHashes);
|
||||||
|
} else
|
||||||
|
throw new VerificationException("Merkle root of block header does not match merkle root of partial merkle tree.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a copy of the block header
|
||||||
|
*/
|
||||||
|
public Block getBlockHeader() {
|
||||||
|
return header.cloneAsHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the hash of the block represented in this Filtered Block */
|
||||||
|
public Sha256Hash getHash() {
|
||||||
|
return header.getHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide this FilteredBlock with a transaction which is in its merkle tree
|
||||||
|
* @returns false if the tx is not relevant to this FilteredBlock
|
||||||
|
*/
|
||||||
|
public boolean provideTransaction(Transaction tx) throws VerificationException {
|
||||||
|
if (getTransactionHashes().contains(tx.getHash())) {
|
||||||
|
associatedTransactions.add(tx);
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the set of transactions which were provided using provideTransaction() which match in getTransactionHashes() */
|
||||||
|
public List<Transaction> getAssociatedTransactions() {
|
||||||
|
return Collections.unmodifiableList(associatedTransactions);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,183 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012 The Bitcoin Developers
|
||||||
|
* Copyright 2012 Matt Corallo
|
||||||
|
*
|
||||||
|
* 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 com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following comment (and much of the code in this file)
|
||||||
|
* is copied from the reference client.
|
||||||
|
*
|
||||||
|
* Data structure that represents a partial merkle tree.
|
||||||
|
*
|
||||||
|
* It respresents a subset of the txid's of a known block, in a way that
|
||||||
|
* allows recovery of the list of txid's and the merkle root, in an
|
||||||
|
* authenticated way.
|
||||||
|
*
|
||||||
|
* The encoding works as follows: we traverse the tree in depth-first order,
|
||||||
|
* storing a bit for each traversed node, signifying whether the node is the
|
||||||
|
* parent of at least one matched leaf txid (or a matched txid itself). In
|
||||||
|
* case we are at the leaf level, or this bit is 0, its merkle node hash is
|
||||||
|
* stored, and its children are not explorer further. Otherwise, no hash is
|
||||||
|
* stored, but we recurse into both (or the only) child branch. During
|
||||||
|
* decoding, the same depth-first traversal is performed, consuming bits and
|
||||||
|
* hashes as they were written during encoding.
|
||||||
|
*
|
||||||
|
* The serialization is fixed and provides a hard guarantee about the
|
||||||
|
* encoded size:
|
||||||
|
*
|
||||||
|
* SIZE <= 10 + ceil(32.25*N)
|
||||||
|
*
|
||||||
|
* Where N represents the number of leaf nodes of the partial tree. N itself
|
||||||
|
* is bounded by:
|
||||||
|
*
|
||||||
|
* N <= total_transactions
|
||||||
|
* N <= 1 + matched_transactions*tree_height
|
||||||
|
*
|
||||||
|
* The serialization format:
|
||||||
|
* - uint32 total_transactions (4 bytes)
|
||||||
|
* - varint number of hashes (1-3 bytes)
|
||||||
|
* - uint256[] hashes in depth-first order (<= 32*N bytes)
|
||||||
|
* - varint number of bytes of flag bits (1-3 bytes)
|
||||||
|
* - byte[] flag bits, packed per 8 in a byte, least significant bit first (<= 2*N-1 bits)
|
||||||
|
* The size constraints follow from this.
|
||||||
|
*/
|
||||||
|
public class PartialMerkleTree extends Message {
|
||||||
|
// the total number of transactions in the block
|
||||||
|
int transactionCount;
|
||||||
|
|
||||||
|
// node-is-parent-of-matched-txid bits
|
||||||
|
byte[] matchedChildBits;
|
||||||
|
|
||||||
|
// txids and internal hashes
|
||||||
|
List<Sha256Hash> hashes;
|
||||||
|
|
||||||
|
public PartialMerkleTree(NetworkParameters params, byte[] payloadBytes, int offset) throws ProtocolException {
|
||||||
|
super(params, payloadBytes, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||||
|
throw new RuntimeException("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void parse() throws ProtocolException {
|
||||||
|
transactionCount = (int)readUint32();
|
||||||
|
|
||||||
|
int nHashes = (int) readVarInt();
|
||||||
|
hashes = new ArrayList<Sha256Hash>(nHashes);
|
||||||
|
for (int i = 0; i < nHashes; i++)
|
||||||
|
hashes.add(readHash());
|
||||||
|
|
||||||
|
int nFlagBytes = (int) readVarInt();
|
||||||
|
matchedChildBits = readBytes(nFlagBytes);
|
||||||
|
|
||||||
|
length = cursor - offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void parseLite() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to efficiently calculate the number of nodes at given height in the merkle tree
|
||||||
|
private int getTreeWidth(int height) {
|
||||||
|
return (transactionCount+(1 << height)-1) >> height;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValuesUsed {
|
||||||
|
public int bitsUsed = 0, hashesUsed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursive function that traverses tree nodes, consuming the bits and hashes produced by TraverseAndBuild.
|
||||||
|
// it returns the hash of the respective node.
|
||||||
|
private Sha256Hash recursiveExtractHashes(int height, int pos, ValuesUsed used, Set<Sha256Hash> matchedHashes) throws VerificationException {
|
||||||
|
if (used.bitsUsed >= matchedChildBits.length*8) {
|
||||||
|
// overflowed the bits array - failure
|
||||||
|
throw new VerificationException("CPartialMerkleTree overflowed its bits array");
|
||||||
|
}
|
||||||
|
boolean parentOfMatch = Utils.checkBitLE(matchedChildBits, used.bitsUsed++);
|
||||||
|
if (height == 0 || !parentOfMatch) {
|
||||||
|
// if at height 0, or nothing interesting below, use stored hash and do not descend
|
||||||
|
if (used.hashesUsed >= hashes.size()) {
|
||||||
|
// overflowed the hash array - failure
|
||||||
|
throw new VerificationException("CPartialMerkleTree overflowed its hash array");
|
||||||
|
}
|
||||||
|
if (height == 0 && parentOfMatch) // in case of height 0, we have a matched txid
|
||||||
|
matchedHashes.add(hashes.get(used.hashesUsed));
|
||||||
|
return hashes.get(used.hashesUsed++);
|
||||||
|
} else {
|
||||||
|
// otherwise, descend into the subtrees to extract matched txids and hashes
|
||||||
|
byte[] left = recursiveExtractHashes(height-1, pos*2, used, matchedHashes).getBytes(), right;
|
||||||
|
if (pos*2+1 < getTreeWidth(height-1))
|
||||||
|
right = recursiveExtractHashes(height-1, pos*2+1, used, matchedHashes).getBytes();
|
||||||
|
else
|
||||||
|
right = left;
|
||||||
|
// and combine them before returning
|
||||||
|
return new Sha256Hash(Utils.reverseBytes(Utils.doubleDigestTwoBuffers(
|
||||||
|
Utils.reverseBytes(left), 0, 32,
|
||||||
|
Utils.reverseBytes(right), 0, 32)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts tx hashes that are in this merkle tree
|
||||||
|
* and returns the merkle root of this tree.
|
||||||
|
*
|
||||||
|
* The returned root should be checked against the
|
||||||
|
* merkle root contained in the block header for security.
|
||||||
|
*
|
||||||
|
* @param matchedHashes A list which will contain the matched txn (will be cleared)
|
||||||
|
* @return the merkle root of this merkle tree
|
||||||
|
* @throws ProtocolException if this partial merkle tree is invalid
|
||||||
|
*/
|
||||||
|
public Sha256Hash getTxnHashAndMerkleRoot(Set<Sha256Hash> matchedHashes) throws VerificationException {
|
||||||
|
matchedHashes.clear();
|
||||||
|
|
||||||
|
// An empty set will not work
|
||||||
|
if (transactionCount == 0)
|
||||||
|
throw new VerificationException("Got a CPartialMerkleTree with 0 transactions");
|
||||||
|
// check for excessively high numbers of transactions
|
||||||
|
if (transactionCount > Block.MAX_BLOCK_SIZE / 60) // 60 is the lower bound for the size of a serialized CTransaction
|
||||||
|
throw new VerificationException("Got a CPartialMerkleTree with more transactions than is possible");
|
||||||
|
// there can never be more hashes provided than one for every txid
|
||||||
|
if (hashes.size() > transactionCount)
|
||||||
|
throw new VerificationException("Got a CPartialMerkleTree with more hashes than transactions");
|
||||||
|
// there must be at least one bit per node in the partial tree, and at least one node per hash
|
||||||
|
if (matchedChildBits.length*8 < hashes.size())
|
||||||
|
throw new VerificationException("Got a CPartialMerkleTree with fewer matched bits than hashes");
|
||||||
|
// calculate height of tree
|
||||||
|
int height = 0;
|
||||||
|
while (getTreeWidth(height) > 1)
|
||||||
|
height++;
|
||||||
|
// traverse the partial tree
|
||||||
|
ValuesUsed used = new ValuesUsed();
|
||||||
|
Sha256Hash merkleRoot = recursiveExtractHashes(height, 0, used, matchedHashes);
|
||||||
|
// verify that all bits were consumed (except for the padding caused by serializing it as a byte sequence)
|
||||||
|
if ((used.bitsUsed+7)/8 != matchedChildBits.length ||
|
||||||
|
// verify that all hashes were consumed
|
||||||
|
used.hashesUsed != hashes.size())
|
||||||
|
throw new VerificationException("Got a CPartialMerkleTree that didn't need all the data it provided");
|
||||||
|
|
||||||
|
return merkleRoot;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user