package snowblossom.node; import com.google.protobuf.ByteString; import io.grpc.stub.StreamObserver; import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; import snowblossom.lib.*; import snowblossom.proto.*; import snowblossom.trie.proto.TrieNode; public class SnowUserService extends UserServiceGrpc.UserServiceImplBase { private static final Logger logger = Logger.getLogger("snowblossom.userservice"); private LinkedList<BlockSubscriberInfo> block_subscribers = new LinkedList<>(); private SnowBlossomNode node; private Object tickle_trigger = new Object(); private FeeEstimator fee_est; public SnowUserService(SnowBlossomNode node) { super(); this.node = node; fee_est = new FeeEstimator(node); } public void start() { new Tickler().start(); } @Override public StreamObserver<SubscribeBlockTemplateRequest> subscribeBlockTemplateStream(StreamObserver<Block> responseObserver) { logger.log(Level.INFO, "Subscribe block template stream called"); BlockSubscriberInfo info = new BlockSubscriberInfo(null, responseObserver); synchronized(block_subscribers) { block_subscribers.add(info); } return new TemplateUpdateObserver(info); } @Override public void subscribeBlockTemplate(SubscribeBlockTemplateRequest req, StreamObserver<Block> responseObserver) { logger.log(Level.FINE, "Subscribe block template called"); BlockSubscriberInfo info = new BlockSubscriberInfo(req, responseObserver); synchronized(block_subscribers) { block_subscribers.add(info); } sendBlock(info); } protected void sendBlock(BlockSubscriberInfo info) { if (node.areWeSynced()) { Block block = node.getBlockForge().getBlockTemplate(info.req); info.sink.onNext(block); } else { logger.log(Level.WARNING, "We are not yet synced, refusing to send block template to miner"); } } /** * Trigger new blocks being send to block subscribers */ public void tickleBlocks() { synchronized(tickle_trigger) { tickle_trigger.notifyAll(); } } private void sendNewBlocks() { synchronized(block_subscribers) { LinkedList<BlockSubscriberInfo> continue_list = new LinkedList<>(); for(BlockSubscriberInfo info : block_subscribers) { try { // Could be null if stream hasn't started up yet if (info.req != null) { sendBlock(info); } continue_list.add(info); } catch(Throwable t) { logger.fine("Error: " + t); } } block_subscribers.clear(); block_subscribers.addAll(continue_list); } } @Override public void submitBlock(Block block, StreamObserver<SubmitReply> responseObserver) { try { node.getBlockIngestor().ingestBlock(block); } catch(ValidationException e) { logger.info("Rejecting block: " + e); responseObserver.onNext(SubmitReply.newBuilder() .setSuccess(false) .setErrorMessage(e.toString()) .build()); responseObserver.onCompleted(); return; } responseObserver.onNext(SubmitReply.newBuilder().setSuccess(true).build()); responseObserver.onCompleted(); } @Override public void submitTransaction(Transaction tx, StreamObserver<SubmitReply> responseObserver) { try { tx = Transaction.parseFrom(tx.toByteString()); if (node.getMemPool().addTransaction(tx)) { node.getPeerage().broadcastTransaction(tx); } } catch(ValidationException e) { logger.info("Rejecting transaction: " + e); responseObserver.onNext(SubmitReply.newBuilder() .setSuccess(false) .setErrorMessage(e.toString()) .build()); responseObserver.onCompleted(); return; } catch(com.google.protobuf.InvalidProtocolBufferException e) { logger.info("Rejecting transaction, strange error: " + e); responseObserver.onNext(SubmitReply.newBuilder() .setSuccess(false) .setErrorMessage(e.toString()) .build()); responseObserver.onCompleted(); return; } responseObserver.onNext(SubmitReply.newBuilder().setSuccess(true).build()); responseObserver.onCompleted(); } @Override public void getUTXONode(GetUTXONodeRequest request, StreamObserver<GetUTXONodeReply> responseObserver) { ChainHash utxo_root = null; if (request.getUtxoRootHash().size() > 0) { utxo_root = new ChainHash(request.getUtxoRootHash()); } else { utxo_root = UtxoUpdateBuffer.EMPTY; BlockSummary summary = node.getBlockIngestor().getHead(); if (summary != null) { utxo_root = new ChainHash(summary.getHeader().getUtxoRootHash()); } } ByteString target=request.getPrefix(); LinkedList<TrieNode> proof = new LinkedList<>(); LinkedList<TrieNode> results = new LinkedList<>(); int max_results = 10000; if (request.getMaxResults() > 0) max_results = Math.min(max_results, request.getMaxResults()); node.getUtxoHashedTrie().getNodeDetails(utxo_root.getBytes(), target, proof, results, max_results); GetUTXONodeReply.Builder reply = GetUTXONodeReply.newBuilder(); reply.setUtxoRootHash(utxo_root.getBytes()); reply.addAllAnswer(results); if (request.getIncludeProof()) { reply.addAllProof(proof); } responseObserver.onNext(reply.build()); responseObserver.onCompleted(); } @Override public void getNodeStatus(NullRequest null_request, StreamObserver<NodeStatus> responseObserver) { NodeStatus.Builder ns = NodeStatus.newBuilder(); ns .setMemPoolSize(node.getMemPool().getMemPoolSize()) .setConnectedPeers(node.getPeerage().getConnectedPeerCount()) .setEstimatedNodes(node.getPeerage().getEstimateUniqueNodes()) .setNodeVersion(Globals.VERSION) .putAllVersionMap(node.getPeerage().getVersionMap()); ns.setNetwork( node.getParams().getNetworkName() ); if (node.getBlockIngestor().getHead() != null) { ns.setHeadSummary(node.getBlockIngestor().getHead()); } responseObserver.onNext(ns.build()); responseObserver.onCompleted(); } @Override public void getBlock(RequestBlock req, StreamObserver<Block> responseObserver) { if (req.getBlockHash().size() > 0) { Block blk = node.getDB().getBlockMap().get(req.getBlockHash()); responseObserver.onNext(blk); responseObserver.onCompleted(); } else { ChainHash block_hash = node.getDB().getBlockHashAtHeight(req.getBlockHeight()); if (block_hash == null) { responseObserver.onNext(null); responseObserver.onCompleted(); return; } Block blk = node.getDB().getBlockMap().get(block_hash.getBytes()); responseObserver.onNext(blk); responseObserver.onCompleted(); } } @Override public void getBlockHeader(RequestBlockHeader req, StreamObserver<BlockHeader> responseObserver) { ChainHash block_hash = null; if (req.getBlockHash().size() > 0) { block_hash = new ChainHash(req.getBlockHash()); } else { block_hash = node.getDB().getBlockHashAtHeight(req.getBlockHeight()); } BlockHeader answer = null; if (block_hash != null) { BlockSummary sum = node.getDB().getBlockSummaryMap().get(block_hash.getBytes()); answer = sum.getHeader(); } responseObserver.onNext(answer); responseObserver.onCompleted(); } @Override public void getTransaction(RequestTransaction req, StreamObserver<Transaction> observer) { Transaction tx = node.getDB().getTransactionMap().get(req.getTxHash()); if (tx == null) { tx = node.getMemPool().getTransaction(new ChainHash(req.getTxHash())); } observer.onNext(tx); observer.onCompleted(); } @Override public void getTransactionStatus(RequestTransaction req, StreamObserver<TransactionStatus> observer) { ChainHash tx_id = new ChainHash(req.getTxHash()); TransactionStatus status = null; status = TransactionMapUtil.getTxStatus(tx_id, node.getDB(), node.getBlockIngestor().getHead()); if (status.getUnknown()) { if (node.getMemPool().getTransaction(tx_id) != null) { status = TransactionStatus.newBuilder().setMempool(true).build(); } } observer.onNext(status); observer.onCompleted(); } @Override public void getMempoolTransactionList(RequestAddress req, StreamObserver<TransactionHashList> observer) { AddressSpecHash spec_hash = new AddressSpecHash(req.getAddressSpecHash()); TransactionHashList.Builder list = TransactionHashList.newBuilder(); for(ChainHash h : node.getMemPool().getTransactionsForAddress(spec_hash)) { list.addTxHashes(h.getBytes()); } observer.onNext( list.build()); observer.onCompleted(); } @Override public void getFeeEstimate(NullRequest null_request, StreamObserver<FeeEstimate> observer) { //observer.onNext( FeeEstimate.newBuilder().setFeePerByte( Globals.BASIC_FEE ).build() ); observer.onNext( FeeEstimate.newBuilder().setFeePerByte( fee_est.getFeeEstimate()).build()); observer.onCompleted(); } @Override public void getAddressHistory(RequestAddress req, StreamObserver<HistoryList> observer) { AddressSpecHash spec_hash = new AddressSpecHash(req.getAddressSpecHash()); try { observer.onNext( AddressHistoryUtil.getHistory(spec_hash, node.getDB(), node.getBlockIngestor().getHead()) ); observer.onCompleted(); } catch(ValidationException e) { observer.onNext(HistoryList.newBuilder().setNotEnabled(true).build()); observer.onCompleted(); } catch(Throwable e) { String addr = AddressUtil.getAddressString(node.getParams().getAddressPrefix(), spec_hash); logger.info("Exception "+addr+" " + e.toString()); observer.onNext(HistoryList.newBuilder().build()); observer.onError(e); observer.onCompleted(); return; } } @Override public void getFBOList(RequestAddress req, StreamObserver<TxOutList> ob) { AddressSpecHash spec_hash = new AddressSpecHash(req.getAddressSpecHash()); try { ob.onNext(ForBenefitOfUtil.getFBOList(spec_hash, node.getDB(), node.getBlockIngestor().getHead())); ob.onCompleted(); } catch(Throwable e) { String addr = AddressUtil.getAddressString(node.getParams().getAddressPrefix(), spec_hash); logger.info("Exception "+addr+" " + e.toString()); ob.onError(e); ob.onCompleted(); return; } } @Override public void getIDList(RequestNameID req, StreamObserver<TxOutList> ob) { try { TxOutList lst = null; ByteString type = null; if (req.getNameType() == RequestNameID.IdType.USERNAME) { type = ForBenefitOfUtil.ID_MAP_USER; } if (req.getNameType() == RequestNameID.IdType.CHANNELNAME) { type = ForBenefitOfUtil.ID_MAP_CHAN; } lst = ForBenefitOfUtil.getIdList( type, req.getName(), node.getDB(), node.getBlockIngestor().getHead()); //lst = ForBenefitOfUtil.filterByCurrent(lst); ob.onNext(lst); ob.onCompleted(); } catch(Throwable e) { logger.info("Exception " + e.toString()); ob.onError(e); ob.onCompleted(); return; } } class BlockSubscriberInfo { volatile SubscribeBlockTemplateRequest req; final StreamObserver<Block> sink; public BlockSubscriberInfo(SubscribeBlockTemplateRequest req, StreamObserver<Block> sink) { this.req = req; this.sink = sink; } public void updateTemplate(SubscribeBlockTemplateRequest req) { this.req = req; } } public class Tickler extends Thread { public Tickler() { setName("SnowUserService/Tickler"); setDaemon(true); } public void run() { while(true) { try { synchronized(tickle_trigger) { tickle_trigger.wait(30000); } sendNewBlocks(); } catch(Throwable t) { logger.log(Level.INFO, "Tickle error: " + t); } } } } public class TemplateUpdateObserver implements StreamObserver<SubscribeBlockTemplateRequest> { public final BlockSubscriberInfo info; public TemplateUpdateObserver(BlockSubscriberInfo info) { this.info = info; } public void onNext(SubscribeBlockTemplateRequest req) { info.updateTemplate(req); // Send a new one immediately sendBlock(info); } public void onError(Throwable t) { logger.log(Level.INFO, "Error in TemplateUpdateObserver: " + t); } public void onCompleted() { } } }