org.hyperledger.core.bitcoin.BitcoinPersistentBlocks.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperledger.core.bitcoin.BitcoinPersistentBlocks.java

Source

/**
 * 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.hyperledger.core.bitcoin;

import com.google.common.base.Stopwatch;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.hyperledger.common.*;
import org.hyperledger.core.*;
import org.hyperledger.core.kvstore.OrderedMapStore;
import org.hyperledger.core.kvstore.OrderedMapStoreKey;
import org.hyperledger.model.LevelDBStore;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * blocks stored persistently.
 */
public class BitcoinPersistentBlocks implements PersistentBlocks {
    protected final OrderedMapStore store;

    @Override
    public void start() {
        store.open();
    }

    public BitcoinPersistentBlocks(OrderedMapStore store) {
        this.store = store;
    }

    private volatile int nRead = 0;
    private volatile long readTime = 0L;

    @Override
    public StoredTransaction readTransaction(TID hash) throws HyperLedgerException {
        nRead++;
        Stopwatch watch = Stopwatch.createStarted();
        try {
            byte[] data = store
                    .get(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.TX, hash.unsafeGetArray()));
            if (data != null) {
                StoredTransaction t = StoredTransaction.fromLevelDB(data);
                if (!t.getID().equals(hash)) {
                    throw new HyperLedgerException("Database inconsistency in TX " + hash);
                }
                return t;
            }
            return null;
        } finally {
            readTime += watch.stop().elapsed(TimeUnit.MILLISECONDS);
        }
    }

    @Override
    public StoredTransaction readTransaction(Outpoint outpoint) throws HyperLedgerException {
        StoredTransaction transaction = readTransaction(outpoint.getTransactionId());
        if (transaction != null && transaction.getOutputs().size() > outpoint.getOutputIndex())
            return transaction;
        return null;
    }

    @Override
    public StoredHeader readHeader(BID hash) throws HyperLedgerException {
        byte[] data = store
                .get(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.BLOCKHEADER, hash.unsafeGetArray()));
        if (data != null) {
            StoredHeader h = StoredHeader.fromLevelDB(data);
            if (!h.getID().equals(hash))
                throw new HyperLedgerException("Database hash inconsistency in block" + hash);
            return h;
        }
        return null;
    }

    @Override
    public StoredBlock readBlock(BID hash) throws HyperLedgerException {
        StoredHeader header = readHeader(hash);
        if (header != null) {
            byte[] content = store.get(
                    OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.BLOCKCONTENT, hash.unsafeGetArray()));
            LevelDBStore.BLOCKCONTENT c;
            try {
                c = LevelDBStore.BLOCKCONTENT.parseFrom(content);
                List<MerkleTreeNode> txs = new ArrayList<>(c.getTxHashesCount());
                for (ByteString txhash : c.getTxHashesList()) {
                    byte[] rtxh = txhash.toByteArray();
                    Hash txh = Hash.createFromSafeArray(Arrays.copyOf(rtxh, 32));
                    if (rtxh[32] == 0) {
                        StoredTransaction tx = readTransaction(new TID(txh));
                        if (tx == null)
                            txs.add(new PrunedNode(txh, 0));
                        else
                            txs.add(tx);
                    } else {
                        txs.add(new PrunedNode(txh, rtxh[32] & 0xFF));
                    }
                }
                return new StoredBlock(header, txs);
            } catch (InvalidProtocolBufferException e) {
                throw new HyperLedgerException(e);
            }
        }
        return null;
    }

    private void writeTx(StoredTransaction t, BID block) throws HyperLedgerException {
        byte[] id = t.getID().unsafeGetArray();
        byte[] shortId = Arrays.copyOf(id, 4);
        t.addBlock(block);
        store.put(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.TX, id), t.toLevelDB());
        for (TransactionOutput out : t.getOutputs()) {
            addDataIndex(OrderedMapStoreKey.KeyType.OUTSCRIPT, toBytesArray(out.getScript().hashCode()), shortId);
        }
        for (TransactionInput in : t.getInputs()) {
            if (!in.getSourceTransactionID().equals(TID.INVALID)) {
                addSpendIndex(in.getSource(), t.getID());
            }
        }
    }

    private void removeTransaction(TID tid) throws HyperLedgerException {
        store.remove(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.TX, tid.unsafeGetArray()));
        StoredTransaction transaction = readTransaction(tid);
        if (transaction != null) {
            for (Coin c : transaction.getCoins()) {
                store.remove(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.SPEND,
                        toBytesArray(c.getOutpoint().hashCode())));
            }
        }
    }

    @Override
    public void updateBlock(StoredBlock prunedBlock) throws HyperLedgerException {
        try {
            store.startBatch();

            for (TID tId : readBlockTIDList(prunedBlock.getID())) {
                if (!contained(prunedBlock.getTransactions(), tId)) {
                    removeTransaction(tId);
                }
            }
            store.put(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.BLOCKCONTENT,
                    prunedBlock.getID().unsafeGetArray()), prunedBlock.toLevelDBContent());
        } catch (Exception e) {
            store.cancelBatch();
            throw e;
        } finally {
            store.endBatch();
        }
    }

    private boolean contained(List<? extends StoredTransaction> transactions, TID transactionId) {
        for (Transaction prunedTransaction : transactions) {
            if (prunedTransaction.getID().equals(transactionId)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public PersistenceStatistics writeBlock(StoredBlock b) throws HyperLedgerException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        PersistenceStatistics stats;
        try {
            store.startBatch();

            store.put(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.BLOCKHEADER,
                    b.getID().unsafeGetArray()), b.toLevelDBHeader());
            store.put(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.BLOCKCONTENT,
                    b.getID().unsafeGetArray()), b.toLevelDBContent());
            for (StoredTransaction t : b.getTransactions()) {
                byte[] stored = store.get(
                        OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.TX, t.getID().unsafeGetArray()));
                if (stored != null) {
                    t.addBlock(b.getID());
                    store.put(
                            OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.TX, t.getID().unsafeGetArray()),
                            t.toLevelDB());
                } else
                    writeTx(t, b.getID());
            }
        } catch (Exception e) {
            store.cancelBatch();
            throw e;
        } finally {
            store.endBatch();
            stats = new PersistenceStatistics(nRead, readTime, stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
            nRead = 0;
            readTime = 0;
        }
        return stats;
    }

    public boolean writeMisc(BID id, byte[] data) {
        try {
            store.startBatch();
            store.put(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.MISC, id.unsafeGetArray()), data);
        } catch (Exception e) {
            store.cancelBatch();
            throw e;
        } finally {
            store.endBatch();
        }
        return true;
    }

    public byte[] readMisc(BID id) {
        return store.get(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.MISC, id.unsafeGetArray()));
    }

    @Override
    public void readHeaders(Map<BID, StoredHeader> headers) throws HyperLedgerException {
        store.forAll(OrderedMapStoreKey.KeyType.BLOCKHEADER, (key, data) -> {
            StoredHeader b = StoredHeader.fromLevelDB(data);
            headers.put(b.getID(), b);
            return true;
        });
    }

    @Override
    public boolean isEmpty() {
        return store.isEmpty();
    }

    @Override
    public boolean hasTransaction(TID hash) throws HyperLedgerException {
        return store
                .get(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.TX, hash.unsafeGetArray())) != null;
    }

    @Override
    public List<TID> readBlockTIDList(BID hash) throws HyperLedgerException {
        byte[] data = store
                .get(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.BLOCKCONTENT, hash.unsafeGetArray()));
        if (data != null)
            try {
                LevelDBStore.BLOCKCONTENT c = LevelDBStore.BLOCKCONTENT.parseFrom(data);
                List<TID> txs = new ArrayList<>(c.getTxHashesCount());
                for (ByteString txhash : c.getTxHashesList()) {
                    byte[] id = txhash.toByteArray();
                    if (id[32] == 0) {
                        txs.add(new TID(Arrays.copyOfRange(id, 0, 32)));
                    }
                }
                return txs;
            } catch (InvalidProtocolBufferException e) {
                throw new HyperLedgerException(e);
            }
        return new ArrayList<>();
    }

    @Override
    public boolean hasBlock(BID hash) throws HyperLedgerException {
        return readHeader(hash) != null;
    }

    protected void addSpendIndex(Outpoint outpoint, TID spendingTx) {
        byte[] k = OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.SPEND,
                toBytesArray(outpoint.hashCode()));
        byte[] data = store.get(k);
        if (data == null) {
            data = new byte[4];
        } else {
            data = Arrays.copyOf(data, data.length + 4);
        }

        byte[] id = Arrays.copyOf(spendingTx.unsafeGetArray(), 4);
        System.arraycopy(id, 0, data, data.length - id.length, id.length);
        store.put(k, data);
    }

    protected void addDataIndex(OrderedMapStoreKey.KeyType type, byte[] kid, final byte[] id)
            throws HyperLedgerException {
        byte[] data = new byte[0];

        byte[] k = OrderedMapStoreKey.createKey(type, kid);
        byte[] lastKey = Arrays.copyOf(k, k.length + 4);
        lastKey[lastKey.length - 1] = (byte) 0xff;
        lastKey[lastKey.length - 2] = (byte) 0xff;
        lastKey[lastKey.length - 3] = (byte) 0xff;
        lastKey[lastKey.length - 4] = (byte) 0xff;

        byte[] currentKey = store.getFloorKey(lastKey);
        if (currentKey != null) {
            boolean found = true;
            for (int i = 0; found && i < currentKey.length && i < k.length; ++i) {
                if (currentKey[i] != k[i]) {
                    found = false;
                }
            }
            if (found) {
                data = store.get(currentKey);
            } else {
                currentKey = Arrays.copyOf(k, k.length + 4);
            }
        } else {
            currentKey = Arrays.copyOf(k, k.length + 4);
        }

        if (data.length < 4000) {
            boolean different = false;
            if (data.length >= id.length)
                for (int i = 0; !different && i < id.length; ++i)
                    if (data[data.length - id.length + i] != id[i])
                        different = true;
            if (data.length == 0 || different) {
                byte[] nids = Arrays.copyOf(data, data.length + id.length);
                System.arraycopy(id, 0, nids, data.length, id.length);
                store.put(currentKey, nids);
            }
        } else {
            int n = 0;
            for (int i = currentKey.length - 4; i < currentKey.length; ++i) {
                n <<= 8;
                n += currentKey[i] & 0xff;
            }

            ++n;

            for (int i = currentKey.length - 1; i >= currentKey.length - 4; --i) {
                currentKey[i] = (byte) (n & 0xff);
                n >>>= 8;
            }
            store.put(currentKey, id);
        }
    }

    private byte[] toBytesArray(int hashcode) {
        WireFormat.ArrayWriter writer = new WireFormat.ArrayWriter();
        try {
            writer.writeUint32(hashcode);
        } catch (IOException e) {
        }
        return writer.toByteArray();
    }

    @Override
    public boolean probablyHadTransactionsWithOutput(Script script) throws HyperLedgerException {
        return store.get(OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.OUTSCRIPT,
                Arrays.copyOf(toBytesArray(script.hashCode()), 8))) != null;
    }

    @Override
    public Set<StoredTransaction> getTransactionsWithOutput(final Script script) throws HyperLedgerException {
        final Set<StoredTransaction> txs = new HashSet<>();
        store.forAll(OrderedMapStoreKey.KeyType.OUTSCRIPT, toBytesArray(script.hashCode()), (key, data) -> {
            for (int i = 0; i < data.length; i += 4) {
                byte id[] = Arrays.copyOfRange(data, i, i + 4);
                store.forAll(OrderedMapStoreKey.KeyType.TX, id, (k, d) -> {
                    StoredTransaction t = StoredTransaction.fromLevelDB(d);
                    for (TransactionOutput o : t.getOutputs()) {
                        if (o.getScript().equals(script)) {
                            txs.add(t);
                            break;
                        }
                    }
                    return true;
                });
            }
            return true;
        });
        return txs;
    }

    @Override
    public Set<StoredTransaction> getSpendingTransactions(Outpoint outpoint) throws HyperLedgerException {
        final Set<StoredTransaction> txs = new HashSet<>();
        byte[] spend = store.get(
                OrderedMapStoreKey.createKey(OrderedMapStoreKey.KeyType.SPEND, toBytesArray(outpoint.hashCode())));
        if (spend != null) {
            for (int i = 0; i < spend.length; i += 4) {
                byte[] tk = Arrays.copyOfRange(spend, i, i + 4);
                store.forAll(OrderedMapStoreKey.KeyType.TX, tk, (tx, txdata) -> {
                    boolean found = false;
                    StoredTransaction potentialSpender = StoredTransaction.fromLevelDB(txdata);
                    for (TransactionInput in : potentialSpender.getInputs()) {
                        if (in.getSource().equals(outpoint)) {
                            found = true;
                            txs.add(potentialSpender);
                            break;
                        }
                    }
                    return !found;
                });
            }
        }
        return txs;
    }
}