co.rsk.net.eth.RskWireProtocol.java Source code

Java tutorial

Introduction

Here is the source code for co.rsk.net.eth.RskWireProtocol.java

Source

/*
 * This file is part of RskJ
 * Copyright (C) 2017 RSK Labs Ltd.
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 */

package co.rsk.net.eth;

import co.rsk.config.RskSystemProperties;
import co.rsk.core.Rsk;
import co.rsk.net.MessageHandler;
import co.rsk.net.MessageSender;
import co.rsk.net.Metrics;
import co.rsk.net.Status;
import co.rsk.net.messages.BlockMessage;
import co.rsk.net.messages.GetBlockMessage;
import co.rsk.net.messages.Message;
import co.rsk.net.messages.StatusMessage;
import io.netty.channel.ChannelHandlerContext;
import org.ethereum.core.*;
import org.ethereum.core.genesis.GenesisLoader;
import org.ethereum.db.BlockStore;
import org.ethereum.net.eth.EthVersion;
import org.ethereum.net.eth.handler.EthHandler;
import org.ethereum.net.eth.handler.GetBlockHeadersMessageWrapper;
import org.ethereum.net.eth.message.EthMessage;
import org.ethereum.net.eth.message.TransactionsMessage;
import org.ethereum.net.message.ReasonCode;
import org.ethereum.net.server.Channel;
import org.ethereum.sync.SyncState;
import org.ethereum.sync.SyncStatistics;
import org.ethereum.util.ByteUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

import static org.ethereum.net.eth.EthVersion.V62;
import static org.ethereum.net.message.ReasonCode.USELESS_PEER;

/**
 * Eth 62 New Protocol
 *
 * @author ajlopez
 * @since 05.16.2016
 */
@Component
@Scope("prototype")
public class RskWireProtocol extends EthHandler {

    protected static final int MAX_HASHES_TO_SEND = 65536;

    private final static Logger logger = LoggerFactory.getLogger("sync");
    private final static Logger loggerNet = LoggerFactory.getLogger("net");
    /**
     * Header list sent in GET_BLOCK_BODIES message,
     * used to create blocks from headers and bodies
     * also, is useful when returned BLOCK_BODIES msg doesn't cover all sent hashes
     * or in case when peer is disconnected
     */
    protected final List<BlockHeaderWrapper> sentHeaders = Collections
            .synchronizedList(new ArrayList<BlockHeaderWrapper>());
    protected final SyncStatistics syncStats = new SyncStatistics();
    @Autowired
    protected BlockStore blockstore;
    @Autowired
    protected PendingState pendingState;
    protected EthState ethState = EthState.INIT;
    protected SyncState syncState = SyncState.IDLE;
    protected boolean syncDone = false;
    /**
     * Last block hash to be asked from the peer,
     * is set on header retrieving start
     */
    protected byte[] lastHashToAsk;
    /**
     * Number and hash of best known remote block
     */
    protected Queue<GetBlockHeadersMessageWrapper> headerRequests = new LinkedBlockingQueue<>();

    @Autowired
    private Rsk rsk;

    private MessageSender messageSender;
    private MessageHandler messageHandler;

    private MessageRecorder messageRecorder;

    public RskWireProtocol() {
        super(V62);
    }

    @Override
    public void setChannel(Channel channel) {
        super.setChannel(channel);

        if (channel == null)
            return;

        this.messageSender.setNodeID(channel.getNodeId());
    }

    @PostConstruct
    private void init() {
        this.messageSender = new EthMessageSender(this);
        this.messageRecorder = RskSystemProperties.RSKCONFIG.getMessageRecorder();

        if (this.rsk != null)
            this.messageHandler = this.rsk.getMessageHandler();

        maxHashesAsk = config.maxHashesAsk();
    }

    @Override
    public void channelRead0(final ChannelHandlerContext ctx, EthMessage msg) throws InterruptedException {
        super.channelRead0(ctx, msg);

        if (this.messageRecorder != null)
            this.messageRecorder.recordMessage(messageSender.getNodeID(), msg);

        Metrics.messageBytes(messageSender.getNodeID(), msg.getEncoded().length);
        switch (msg.getCommand()) {
        case STATUS:
            processStatus((org.ethereum.net.eth.message.StatusMessage) msg, ctx);
            break;
        case RSK_MESSAGE:
            RskMessage rskmessage = (RskMessage) msg;
            Message message = rskmessage.getMessage();

            switch (message.getMessageType()) {
            case BLOCK_MESSAGE:
                loggerNet.trace("RSK Block Message: Block {} {} from {}",
                        ((BlockMessage) message).getBlock().getNumber(),
                        ((BlockMessage) message).getBlock().getShortHash(), this.messageSender.getNodeID());
                syncStats.addBlocks(1);
                break;
            case GET_BLOCK_MESSAGE:
                loggerNet.trace("RSK Get Block Message: Block {} from {}",
                        Hex.toHexString(((GetBlockMessage) message).getBlockHash()).substring(0, 10),
                        this.messageSender.getNodeID());
                syncStats.getBlock();
                break;
            case STATUS_MESSAGE:
                loggerNet.trace("RSK Status Message: Block {} {} from {}",
                        ((StatusMessage) message).getStatus().getBestBlockNumber(),
                        Hex.toHexString(((StatusMessage) message).getStatus().getBestBlockHash()).substring(0, 10),
                        this.messageSender.getNodeID());
                syncStats.addStatus();
                break;
            }

            if (this.messageHandler != null) {
                this.messageHandler.postMessage(this.messageSender, rskmessage.getMessage());
            }
            break;
        default:
            break;
        }
    }

    /*************************
     *  Message Processing   *
     *************************/

    protected void processStatus(org.ethereum.net.eth.message.StatusMessage msg, ChannelHandlerContext ctx)
            throws InterruptedException {

        try {
            Genesis genesis = GenesisLoader.loadGenesis(config.genesisInfo(),
                    config.getBlockchainConfig().getCommonConstants().getInitialNonce(), true);
            if (!Arrays.equals(msg.getGenesisHash(), genesis.getHash())
                    || msg.getProtocolVersion() != version.getCode()) {
                loggerNet.info("Removing EthHandler for {} due to protocol incompatibility",
                        ctx.channel().remoteAddress());
                ethState = EthState.STATUS_FAILED;
                disconnect(ReasonCode.INCOMPATIBLE_PROTOCOL);
                ctx.pipeline().remove(this); // Peer is not compatible for the 'eth' sub-protocol
                return;
            }

            if (msg.getNetworkId() != config.networkId()) {
                ethState = EthState.STATUS_FAILED;
                disconnect(ReasonCode.NULL_IDENTITY);
                return;
            }

            // basic checks passed, update statistics
            channel.getNodeStatistics().ethHandshake(msg);
            ethereumListener.onEthStatusUpdated(channel, msg);

            if (peerDiscoveryMode) {
                loggerNet.debug("Peer discovery mode: STATUS received, disconnecting...");
                disconnect(ReasonCode.REQUESTED);
                ctx.close().sync();
                ctx.disconnect().sync();
                return;
            }
        } catch (NoSuchElementException e) {
            loggerNet.debug("EthHandler already removed");
        }
    }

    /*************************
     *    Message Sending    *
     *************************/

    @Override
    public void sendStatus() {
        byte protocolVersion = version.getCode();
        int networkId = config.networkId();

        BigInteger totalDifficulty = blockchain.getTotalDifficulty();
        byte[] bestHash = blockchain.getBestBlockHash();
        Genesis genesis = GenesisLoader.loadGenesis(config.genesisInfo(),
                config.getBlockchainConfig().getCommonConstants().getInitialNonce(), true);
        org.ethereum.net.eth.message.StatusMessage msg = new org.ethereum.net.eth.message.StatusMessage(
                protocolVersion, networkId, ByteUtil.bigIntegerToBytes(totalDifficulty), bestHash,
                genesis.getHash());
        sendMessage(msg);

        // RSK new protocol send status
        Block bestBlock = blockchain.getBestBlock();
        Status status = new Status(bestBlock.getNumber(), bestBlock.getHash());
        RskMessage rskmessage = new RskMessage(new StatusMessage(status));
        loggerNet.trace("Sending status best block {} to {}", status.getBestBlockNumber(),
                this.messageSender.getNodeID().toString());
        sendMessage(rskmessage);

        ethState = EthState.STATUS_SENT;
    }

    @Override
    public void recoverGap(BlockWrapper block) {

    }

    @Override
    public void sendTransaction(List<Transaction> txs) {
        TransactionsMessage msg = new TransactionsMessage(txs);
        sendMessage(msg);
    }

    @Override
    public void sendNewBlock(Block newBlock) {

    }

    @Override
    public void sendNewBlockHashes(Block block) {

    }

    /*************************
     *    Sync Management    *
     *************************/

    @Override
    public void onShutdown() {

    }

    /*************************
     *   Getters, setters    *
     *************************/

    @Override
    public boolean hasStatusPassed() {
        return ethState.ordinal() > EthState.STATUS_SENT.ordinal();
    }

    @Override
    public boolean hasStatusSucceeded() {
        return ethState == EthState.STATUS_SUCCEEDED;
    }

    @Override
    public boolean isHashRetrievingDone() {
        return syncState == SyncState.DONE_HASH_RETRIEVING;
    }

    @Override
    public boolean isHashRetrieving() {
        return syncState == SyncState.HASH_RETRIEVING;
    }

    @Override
    public boolean isIdle() {
        return syncState == SyncState.IDLE;
    }

    @Override
    public void enableTransactions() {
        processTransactions = true;
    }

    @Override
    public void disableTransactions() {
        processTransactions = false;
    }

    @Override
    public SyncStatistics getStats() {
        return syncStats;
    }

    @Override
    public EthVersion getVersion() {
        return version;
    }

    @Override
    public void onSyncDone(boolean done) {
        syncDone = done;
    }

    @Override
    public void dropConnection() {

        // todo: reduce reputation

        logger.info("Peer {}: is a bad one, drop", channel.getPeerIdShort());
        disconnect(USELESS_PEER);
    }

    @Override
    public void fetchBodies(List<BlockHeaderWrapper> headers) {

    }

    /*************************
     *       Logging         *
     *************************/

    @Override
    public void logSyncStats() {
        if (!logger.isInfoEnabled()) {
            return;
        }
    }

    @Override
    public boolean isUsingNewProtocol() {
        return true;
    }

    protected enum EthState {
        INIT, STATUS_SENT, STATUS_SUCCEEDED, STATUS_FAILED
    }
}