com.bitsofproof.supernode.core.ImplementBCSAPI.java Source code

Java tutorial

Introduction

Here is the source code for com.bitsofproof.supernode.core.ImplementBCSAPI.java

Source

/*
 * Copyright 2013 bits of proof zrt.
 *
 * 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.bitsofproof.supernode.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import com.bitsofproof.supernode.api.BCSAPIMessage;
import com.bitsofproof.supernode.api.Block;
import com.bitsofproof.supernode.api.BloomFilter;
import com.bitsofproof.supernode.api.BloomFilter.UpdateMode;
import com.bitsofproof.supernode.api.Color;
import com.bitsofproof.supernode.api.Hash;
import com.bitsofproof.supernode.api.Transaction;
import com.bitsofproof.supernode.api.TrunkUpdateMessage;
import com.bitsofproof.supernode.api.ValidationException;
import com.bitsofproof.supernode.api.WireFormat;
import com.bitsofproof.supernode.core.BlockStore.TransactionProcessor;
import com.bitsofproof.supernode.messages.BlockMessage;
import com.bitsofproof.supernode.model.Blk;
import com.bitsofproof.supernode.model.StoredColor;
import com.bitsofproof.supernode.model.Tx;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

public class ImplementBCSAPI implements TrunkListener, TxListener {
    private static final Logger log = LoggerFactory.getLogger(ImplementBCSAPI.class);

    private final BitcoinNetwork network;
    private final BlockStore store;
    private final TxHandler txhandler;

    private PlatformTransactionManager transactionManager;

    private ConnectionFactory connectionFactory;

    private Connection connection;
    private Session session;

    private MessageProducer transactionProducer;
    private MessageProducer trunkProducer;
    private final Map<String, MessageProducer> correlationProducer = new HashMap<String, MessageProducer>();
    private final Map<String, BloomFilter> correlationBloomFilter = Collections
            .synchronizedMap(new HashMap<String, BloomFilter>());

    private final ExecutorService requestProcessor = Executors
            .newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public ImplementBCSAPI(BitcoinNetwork network, TxHandler txHandler) {
        this.network = network;
        this.txhandler = txHandler;
        this.store = network.getStore();

        store.addTrunkListener(this);
        txHandler.addTransactionListener(this);
    }

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setConnectionFactory(ConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    private void addMessageListener(String topic, MessageListener listener) throws JMSException {
        Destination destination = session.createTopic(topic);
        MessageConsumer consumer = session.createConsumer(destination);
        consumer.setMessageListener(listener);
    }

    public void init() {
        try {
            connection = connectionFactory.createConnection();
            connection.setClientID("bitsofproof supernode");
            connection.start();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            transactionProducer = session.createProducer(session.createTopic("transaction"));
            trunkProducer = session.createProducer(session.createTopic("trunk"));
            addNewTransactionListener();
            addNewBlockListener();
            addBlockrequestListener();
            addTransactionRequestListener();
            addColorRequestListener();
            addNewColorListener();
            addBloomFilterListener();
            addBloomScanListener();
            addMatchScanListener();
        } catch (JMSException e) {
            log.error("Error creating JMS producer", e);
        }
    }

    private void addBloomFilterListener() throws JMSException {
        addMessageListener("filterRequest", new MessageListener() {
            @Override
            public void onMessage(Message msg) {
                BytesMessage o = (BytesMessage) msg;
                byte[] body;
                try {
                    body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    BCSAPIMessage.FilterRequest request = BCSAPIMessage.FilterRequest.parseFrom(body);
                    byte[] data = request.getFilter().toByteArray();
                    long hashFunctions = request.getHashFunctions();
                    long tweak = request.getTweak();
                    UpdateMode updateMode = UpdateMode.values()[request.getMode()];
                    BloomFilter filter = new BloomFilter(data, hashFunctions, tweak, updateMode);
                    synchronized (correlationBloomFilter) {
                        if (!correlationProducer.containsKey(o.getJMSCorrelationID())) {
                            MessageProducer producer = session.createProducer(msg.getJMSReplyTo());
                            correlationProducer.put(o.getJMSCorrelationID(), producer);
                        }
                        correlationBloomFilter.put(o.getJMSCorrelationID(), filter);
                    }
                } catch (JMSException e) {
                    log.error("invalid filter request", e);
                } catch (InvalidProtocolBufferException e) {
                    log.error("invalid filter request", e);
                }
            }
        });
    }

    private void addBloomScanListener() throws JMSException {
        addMessageListener("scanRequest", new MessageListener() {
            @Override
            public void onMessage(Message msg) {
                BytesMessage o = (BytesMessage) msg;
                byte[] body;
                try {
                    body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    BCSAPIMessage.FilterRequest request = BCSAPIMessage.FilterRequest.parseFrom(body);
                    byte[] data = request.getFilter().toByteArray();
                    long hashFunctions = request.getHashFunctions();
                    long tweak = request.getTweak();
                    UpdateMode updateMode = UpdateMode.values()[request.getMode()];
                    final BloomFilter filter = new BloomFilter(data, hashFunctions, tweak, updateMode);
                    final MessageProducer producer = session.createProducer(msg.getJMSReplyTo());
                    requestProcessor.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                store.scan(filter, new TransactionProcessor() {
                                    @Override
                                    public void process(Tx tx) {
                                        if (tx != null) {
                                            Transaction transaction = toBCSAPITransaction(tx);
                                            BytesMessage m;
                                            try {
                                                m = session.createBytesMessage();
                                                m.writeBytes(transaction.toProtobuf().toByteArray());
                                                producer.send(m);
                                            } catch (JMSException e) {
                                            }
                                        } else {
                                            try {
                                                BytesMessage m = session.createBytesMessage();
                                                producer.send(m); // indicate EOF
                                                producer.close();
                                            } catch (JMSException e) {
                                            }
                                        }
                                    }
                                });
                            } catch (ValidationException e) {
                                log.error("Error while scanning", e);
                            }
                        }
                    });
                } catch (JMSException e) {
                    log.error("invalid filter request", e);
                } catch (InvalidProtocolBufferException e) {
                    log.error("invalid filter request", e);
                }
            }
        });
    }

    private void addMatchScanListener() throws JMSException {
        addMessageListener("matchRequest", new MessageListener() {
            @Override
            public void onMessage(Message msg) {
                BytesMessage o = (BytesMessage) msg;
                byte[] body;
                try {
                    body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    BCSAPIMessage.ExactMatchRequest request = BCSAPIMessage.ExactMatchRequest.parseFrom(body);
                    final List<byte[]> match = new ArrayList<byte[]>();
                    for (ByteString bs : request.getMatchList()) {
                        match.add(bs.toByteArray());
                    }
                    final UpdateMode mode = UpdateMode.values()[request.getMode()];
                    final MessageProducer producer = session.createProducer(msg.getJMSReplyTo());
                    final long after = request.hasAfter() ? request.getAfter() : 0;
                    requestProcessor.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                store.filterTransactions(match, mode, after, new TransactionProcessor() {
                                    @Override
                                    public void process(Tx tx) {
                                        if (tx != null) {
                                            Transaction transaction = toBCSAPITransaction(tx);
                                            BytesMessage m;
                                            try {
                                                m = session.createBytesMessage();
                                                m.writeBytes(transaction.toProtobuf().toByteArray());
                                                producer.send(m);
                                            } catch (JMSException e) {
                                            }
                                        } else {
                                            try {
                                                BytesMessage m = session.createBytesMessage();
                                                producer.send(m); // indicate EOF
                                                producer.close();
                                            } catch (JMSException e) {
                                            }
                                        }
                                    }
                                });
                            } catch (ValidationException e) {
                                log.error("Error while scanning", e);
                            }
                        }
                    });
                } catch (JMSException e) {
                    log.error("invalid filter request", e);
                } catch (InvalidProtocolBufferException e) {
                    log.error("invalid filter request", e);
                }
            }
        });
    }

    private void addNewColorListener() throws JMSException {
        addMessageListener("newColor", new MessageListener() {
            @Override
            public void onMessage(Message arg0) {
                BytesMessage o = (BytesMessage) arg0;
                try {
                    byte[] body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    Color color = Color.fromProtobuf(BCSAPIMessage.Color.parseFrom(body));
                    StoredColor sc = new StoredColor();
                    sc.setFungibleName(color.getFungibleName());
                    sc.setExpiryHeight(color.getExpiryHeight());
                    sc.setPubkey(color.getPubkey());
                    sc.setSignature(color.getSignature());
                    sc.setTerms(color.getTerms());
                    sc.setUnit(color.getUnit());
                    sc.setTxHash(color.getTransaction());
                    store.issueColor(sc);
                    reply(o.getJMSReplyTo(), null);
                } catch (Exception e) {
                    BCSAPIMessage.ExceptionMessage.Builder builder = BCSAPIMessage.ExceptionMessage.newBuilder();
                    builder.setBcsapiversion(1);
                    builder.addMessage(e.getMessage());
                    try {
                        reply(o.getJMSReplyTo(), builder.build().toByteArray());
                    } catch (JMSException e1) {
                        log.error("Can not send reply ", e1);
                    }
                }
            }
        });
    }

    private void addColorRequestListener() throws JMSException {
        addMessageListener("colorRequest", new MessageListener() {
            @Override
            public void onMessage(Message message) {
                BytesMessage o = (BytesMessage) message;
                try {
                    byte[] body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    String hash = new Hash(BCSAPIMessage.Hash.parseFrom(body).getHash(0).toByteArray()).toString();
                    Color c = getColor(hash);
                    if (c != null) {
                        reply(o.getJMSReplyTo(), c.toProtobuf().toByteArray());
                    } else {
                        reply(o.getJMSReplyTo(), null);
                    }
                } catch (Exception e) {
                    log.trace("Rejected invalid color request ", e);
                }
            }
        });
    }

    private void addTransactionRequestListener() throws JMSException {
        addMessageListener("transactionRequest", new MessageListener() {
            @Override
            public void onMessage(Message arg0) {
                BytesMessage o = (BytesMessage) arg0;
                try {
                    byte[] body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    String hash = new Hash(BCSAPIMessage.Hash.parseFrom(body).getHash(0).toByteArray()).toString();
                    Transaction t = getTransaction(hash);
                    if (t != null) {
                        reply(o.getJMSReplyTo(), t.toProtobuf().toByteArray());
                    } else {
                        reply(o.getJMSReplyTo(), null);
                    }
                } catch (Exception e) {
                    log.trace("Rejected invalid transaction request ", e);
                }
            }
        });
    }

    private void addBlockrequestListener() throws JMSException {
        addMessageListener("blockRequest", new MessageListener() {
            @Override
            public void onMessage(Message arg0) {
                BytesMessage o = (BytesMessage) arg0;
                try {
                    byte[] body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    String hash = new Hash(BCSAPIMessage.Hash.parseFrom(body).getHash(0).toByteArray()).toString();
                    Block b = getBlock(hash);
                    if (b != null) {
                        reply(o.getJMSReplyTo(), b.toProtobuf().toByteArray());
                    } else {
                        reply(o.getJMSReplyTo(), null);
                    }
                } catch (Exception e) {
                    log.trace("Rejected invalid block request ", e);
                }
            }
        });
    }

    private void addNewBlockListener() throws JMSException {
        addMessageListener("newBlock", new MessageListener() {
            @Override
            public void onMessage(Message arg0) {
                BytesMessage o = (BytesMessage) arg0;
                try {
                    byte[] body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    Block block = Block.fromProtobuf(BCSAPIMessage.Block.parseFrom(body));
                    block.computeHash();
                    sendBlock(block);
                    reply(o.getJMSReplyTo(), null);
                } catch (Exception e) {
                    BCSAPIMessage.ExceptionMessage.Builder builder = BCSAPIMessage.ExceptionMessage.newBuilder();
                    builder.setBcsapiversion(1);
                    builder.addMessage(e.getMessage());
                    try {
                        reply(o.getJMSReplyTo(), builder.build().toByteArray());
                    } catch (JMSException e1) {
                        log.error("Can not send reply ", e1);
                    }
                }
            }
        });
    }

    private void addNewTransactionListener() throws JMSException {
        addMessageListener("newTransaction", new MessageListener() {
            @Override
            public void onMessage(Message arg0) {
                BytesMessage o = (BytesMessage) arg0;
                try {
                    byte[] body = new byte[(int) o.getBodyLength()];
                    o.readBytes(body);
                    Transaction transaction = Transaction.fromProtobuf(BCSAPIMessage.Transaction.parseFrom(body));
                    transaction.computeHash();
                    sendTransaction(transaction);
                    reply(o.getJMSReplyTo(), null);
                } catch (Exception e) {
                    BCSAPIMessage.ExceptionMessage.Builder builder = BCSAPIMessage.ExceptionMessage.newBuilder();
                    builder.setBcsapiversion(1);
                    builder.addMessage(e.getMessage());
                    try {
                        reply(o.getJMSReplyTo(), builder.build().toByteArray());
                    } catch (JMSException e1) {
                        log.error("Can not send reply ", e1);
                    }
                }
            }
        });
    }

    private void reply(Destination destination, byte[] msg) {
        MessageProducer replier = null;
        try {
            replier = session.createProducer(destination);
            BytesMessage m = session.createBytesMessage();
            if (msg != null) {
                m.writeBytes(msg);
            }
            replier.send(m);
        } catch (JMSException e) {
            log.trace("can not reply", e);
        } finally {
            try {
                replier.close();
            } catch (JMSException e) {
            }
        }
    }

    public void destroy() {
        try {
            session.close();
            connection.close();
        } catch (JMSException e) {
        }
    }

    public Transaction getTransaction(final String hash) {
        try {
            log.trace("get transaction " + hash);
            Tx tx = txhandler.getTransaction(hash);
            if (tx == null) {
                return new TransactionTemplate(transactionManager).execute(new TransactionCallback<Transaction>() {
                    @Override
                    public Transaction doInTransaction(TransactionStatus status) {
                        status.setRollbackOnly();
                        Tx t;
                        try {
                            t = store.getTransaction(hash);
                            if (t != null) {
                                return toBCSAPITransaction(t);
                            }
                        } catch (ValidationException e) {
                        }
                        return null;
                    }
                });
            } else {
                return toBCSAPITransaction(tx);
            }
        } finally {
            log.trace("get transaction returned " + hash);
        }
    }

    public Block getBlock(final String hash) {
        log.trace("get block " + hash);
        final WireFormat.Writer writer = new WireFormat.Writer();
        new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                status.setRollbackOnly();
                String h = hash;
                if (h.equals(Hash.ZERO_HASH_STRING)) {
                    h = store.getHeadHash();
                }
                Blk b;
                try {
                    b = store.getBlock(h);
                    if (b != null) {
                        b.toWire(writer);
                    }
                } catch (ValidationException e) {
                }
            }
        });
        byte[] blockdump = writer.toByteArray();
        if (blockdump != null && blockdump.length > 0) {
            log.trace("get block returned " + hash);
            return Block.fromWire(new WireFormat.Reader(writer.toByteArray()));
        }
        log.trace("get block failed ");
        return null;
    }

    private Color getColor(final String hash) {
        log.trace("get color " + hash);
        final Color color = new Color();
        new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                status.setRollbackOnly();
                StoredColor c;
                try {
                    c = ((ColorStore) store).findColor(hash);
                    if (c != null) {
                        color.setExpiryHeight(c.getExpiryHeight());
                        color.setSignature(c.getSignature());
                        color.setTerms(c.getTerms());
                        color.setUnit(c.getUnit());
                        color.setPubkey(c.getPubkey());
                        color.setTransaction(c.getTxHash());
                    }
                } catch (ValidationException e) {
                    log.error("can not get color " + hash, e);
                }

            }
        });
        if (color.getTerms() != null) {
            log.trace("get color returned " + hash);
            return color;
        }
        log.trace("get color failed ");
        return null;
    }

    private void sendTransaction(Transaction transaction) throws ValidationException {
        log.trace("send transaction " + transaction.getHash());
        WireFormat.Writer writer = new WireFormat.Writer();
        transaction.toWire(writer);
        Tx t = new Tx();
        t.fromWire(new WireFormat.Reader(writer.toByteArray()));
        txhandler.validateCacheAndSend(t, null);
    }

    private void sendBlock(Block block) throws ValidationException {
        log.trace("send block " + block.getHash());
        WireFormat.Writer writer = new WireFormat.Writer();
        block.toWire(writer);
        Blk b = new Blk();
        b.fromWire(new WireFormat.Reader(writer.toByteArray()));
        store.storeBlock(b);
        for (BitcoinPeer p : network.getConnectPeers()) {
            BlockMessage bm = (BlockMessage) p.createMessage("block");
            bm.setBlock(b);
            p.send(bm);
        }
    }

    @Override
    public void process(Tx tx) {
        try {
            Transaction transaction = toBCSAPITransaction(tx);
            BytesMessage m = session.createBytesMessage();
            m.writeBytes(transaction.toProtobuf().toByteArray());
            transactionProducer.send(m);

            synchronized (correlationBloomFilter) {
                for (Map.Entry<String, BloomFilter> e : correlationBloomFilter.entrySet()) {
                    if (tx.passesFilter(e.getValue())) {
                        correlationProducer.get(e.getKey()).send(m);
                    }
                }
            }
        } catch (Exception e) {
            log.error("Can not send JMS message ", e);
        }
    }

    private Transaction toBCSAPITransaction(Tx tx) {
        WireFormat.Writer writer = new WireFormat.Writer();
        tx.toWire(writer);
        Transaction transaction = Transaction.fromWire(new WireFormat.Reader(writer.toByteArray()));
        return transaction;
    }

    @Override
    public void trunkUpdate(final List<Blk> removed, final List<Blk> extended) {
        try {
            List<Block> r = new ArrayList<Block>();
            List<Block> a = new ArrayList<Block>();
            for (Blk blk : removed) {
                WireFormat.Writer writer = new WireFormat.Writer();
                blk.toWire(writer);
                r.add(Block.fromWire(new WireFormat.Reader(writer.toByteArray())));
            }
            for (Blk blk : extended) {
                WireFormat.Writer writer = new WireFormat.Writer();
                blk.toWire(writer);
                a.add(Block.fromWire(new WireFormat.Reader(writer.toByteArray())));
            }
            TrunkUpdateMessage tu = new TrunkUpdateMessage(a, r);
            BytesMessage m = session.createBytesMessage();
            m.writeBytes(tu.toProtobuf().toByteArray());
            trunkProducer.send(m);
        } catch (Exception e) {
            log.error("Can not send JMS message ", e);
        }
    }

}