/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.net.eth.handler; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.netty.channel.ChannelHandlerContext; import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.SystemProperties; import org.ethereum.core.*; import org.ethereum.db.BlockStore; import org.ethereum.db.StateSource; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.net.eth.EthVersion; import org.ethereum.net.eth.message.EthMessage; import org.ethereum.net.eth.message.GetNodeDataMessage; import org.ethereum.net.eth.message.GetReceiptsMessage; import org.ethereum.net.eth.message.NodeDataMessage; import org.ethereum.net.eth.message.ReceiptsMessage; import org.ethereum.sync.PeerState; import org.ethereum.util.ByteArraySet; import org.ethereum.util.Value; import org.spongycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Set; import static org.ethereum.crypto.HashUtil.sha3; import static org.ethereum.net.eth.EthVersion.V63; /** * Fast synchronization (PV63) Handler */ @Component("Eth63") @Scope("prototype") public class Eth63 extends Eth62 { private static final EthVersion version = V63; @Autowired private StateSource stateSource; private List<byte[]> requestedReceipts; private SettableFuture<List<List<TransactionReceipt>>> requestReceiptsFuture; private Set<byte[]> requestedNodes; private SettableFuture<List<Pair<byte[], byte[]>>> requestNodesFuture; public Eth63() { super(version); } @Autowired public Eth63(final SystemProperties config, final Blockchain blockchain, BlockStore blockStore, final CompositeEthereumListener ethereumListener) { super(version, config, blockchain, blockStore, ethereumListener); } @Override public void channelRead0(final ChannelHandlerContext ctx, EthMessage msg) throws InterruptedException { super.channelRead0(ctx, msg); // Only commands that were added in V63, V62 are handled in child switch (msg.getCommand()) { case GET_NODE_DATA: processGetNodeData((GetNodeDataMessage) msg); break; case NODE_DATA: processNodeData((NodeDataMessage) msg); break; case GET_RECEIPTS: processGetReceipts((GetReceiptsMessage) msg); break; case RECEIPTS: processReceipts((ReceiptsMessage) msg); break; default: break; } } protected synchronized void processGetNodeData(GetNodeDataMessage msg) { if (logger.isTraceEnabled()) logger.trace( "Peer {}: processing GetNodeData, size [{}]", channel.getPeerIdShort(), msg.getNodeKeys().size() ); List<Value> nodeValues = new ArrayList<>(); for (byte[] nodeKey : msg.getNodeKeys()) { byte[] rawNode = stateSource.get(nodeKey); if (rawNode != null) { Value value = new Value(rawNode); nodeValues.add(value); logger.trace("Eth63: " + Hex.toHexString(nodeKey).substring(0, 8) + " -> " + value); } } sendMessage(new NodeDataMessage(nodeValues)); } protected synchronized void processGetReceipts(GetReceiptsMessage msg) { if (logger.isTraceEnabled()) logger.trace( "Peer {}: processing GetReceipts, size [{}]", channel.getPeerIdShort(), msg.getBlockHashes().size() ); List<List<TransactionReceipt>> receipts = new ArrayList<>(); for (byte[] blockHash : msg.getBlockHashes()) { Block block = blockchain.getBlockByHash(blockHash); if (block == null) continue; List<TransactionReceipt> blockReceipts = new ArrayList<>(); for (Transaction transaction : block.getTransactionsList()) { TransactionInfo transactionInfo = blockchain.getTransactionInfo(transaction.getHash()); if (transactionInfo == null) break; blockReceipts.add(transactionInfo.getReceipt()); } receipts.add(blockReceipts); } sendMessage(new ReceiptsMessage(receipts)); } public synchronized ListenableFuture<List<Pair<byte[], byte[]>>> requestTrieNodes(List<byte[]> hashes) { if (peerState != PeerState.IDLE) return null; GetNodeDataMessage msg = new GetNodeDataMessage(hashes); requestedNodes = new ByteArraySet(); requestedNodes.addAll(hashes); requestNodesFuture = SettableFuture.create(); sendMessage(msg); lastReqSentTime = System.currentTimeMillis(); peerState = PeerState.NODE_RETRIEVING; return requestNodesFuture; } public synchronized ListenableFuture<List<List<TransactionReceipt>>> requestReceipts(List<byte[]> hashes) { if (peerState != PeerState.IDLE) return null; GetReceiptsMessage msg = new GetReceiptsMessage(hashes); requestedReceipts = hashes; peerState = PeerState.RECEIPT_RETRIEVING; requestReceiptsFuture = SettableFuture.create(); sendMessage(msg); lastReqSentTime = System.currentTimeMillis(); return requestReceiptsFuture; } protected synchronized void processNodeData(NodeDataMessage msg) { if (requestedNodes == null) { logger.debug("Received NodeDataMessage when requestedNodes == null. Dropping peer " + channel); dropConnection(); } List<Pair<byte[], byte[]>> ret = new ArrayList<>(); if(msg.getDataList().isEmpty()) { String err = "Received NodeDataMessage contains empty node data. Dropping peer " + channel; dropUselessPeer(err); return; } for (Value nodeVal : msg.getDataList()) { byte[] hash = nodeVal.hash(); if (!requestedNodes.contains(hash)) { String err = "Received NodeDataMessage contains non-requested node with hash :" + Hex.toHexString(hash) + " . Dropping peer " + channel; dropUselessPeer(err); return; } ret.add(Pair.of(hash, nodeVal.encode())); } requestNodesFuture.set(ret); requestedNodes = null; requestNodesFuture = null; processingTime += (System.currentTimeMillis() - lastReqSentTime); lastReqSentTime = 0; peerState = PeerState.IDLE; } protected synchronized void processReceipts(ReceiptsMessage msg) { if (requestedReceipts == null) { logger.debug("Received ReceiptsMessage when requestedReceipts == null. Dropping peer " + channel); dropConnection(); } if (logger.isTraceEnabled()) logger.trace( "Peer {}: processing Receipts, size [{}]", channel.getPeerIdShort(), msg.getReceipts().size() ); List<List<TransactionReceipt>> receipts = msg.getReceipts(); requestReceiptsFuture.set(receipts); requestedReceipts = null; requestReceiptsFuture = null; processingTime += (System.currentTimeMillis() - lastReqSentTime); lastReqSentTime = 0; peerState = PeerState.IDLE; } private void dropUselessPeer(String err) { logger.debug(err); requestNodesFuture.setException(new RuntimeException(err)); dropConnection(); } @Override public String getSyncStats() { double nodesPerSec = 1000d * channel.getNodeStatistics().eth63NodesReceived.get() / channel.getNodeStatistics().eth63NodesRetrieveTime.get(); double missNodesRatio = 1 - (double) channel.getNodeStatistics().eth63NodesReceived.get() / channel.getNodeStatistics().eth63NodesRequested.get(); long lifeTime = System.currentTimeMillis() - connectedTime; return super.getSyncStats() + String.format("\tNodes/sec: %1$.2f, miss: %2$.2f", nodesPerSec, missNodesRatio); } }