All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cryptape.cita.protocol.rx.JsonRpc2_0Rx Maven / Gradle / Ivy

package com.cryptape.cita.protocol.rx;

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;

import com.cryptape.cita.protocol.CITAj;
import com.cryptape.cita.protocol.core.DefaultBlockParameter;
import com.cryptape.cita.protocol.core.DefaultBlockParameterName;
import com.cryptape.cita.protocol.core.DefaultBlockParameterNumber;
import com.cryptape.cita.protocol.core.methods.request.AppFilter;
import com.cryptape.cita.protocol.core.methods.response.AppBlock;
import com.cryptape.cita.protocol.core.methods.response.AppTransaction;
import com.cryptape.cita.protocol.core.methods.response.Log;
import com.cryptape.cita.utils.Flowables;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.Scheduler;

import io.reactivex.functions.Cancellable;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;
import io.reactivex.schedulers.Schedulers;
import com.cryptape.cita.protocol.core.filters.BlockFilter;
import com.cryptape.cita.protocol.core.filters.Callback;
import com.cryptape.cita.protocol.core.filters.Filter;
import com.cryptape.cita.protocol.core.filters.LogFilter;
import com.cryptape.cita.protocol.core.filters.PendingTransactionFilter;
import com.cryptape.cita.protocol.core.methods.response.Transaction;
import org.reactivestreams.Publisher;

/**
 * citaj reactive API implementation.
 */
public class JsonRpc2_0Rx {

    private final CITAj citaj;
    private final ScheduledExecutorService scheduledExecutorService;
    private final Scheduler scheduler;

    public JsonRpc2_0Rx(CITAj citaj, ScheduledExecutorService scheduledExecutorService) {
        this.citaj = citaj;
        this.scheduledExecutorService = scheduledExecutorService;
        this.scheduler = Schedulers.from(scheduledExecutorService);
    }

    public Flowable appBlockHashFlowable(long pollingInterval) {
        return Flowable.create(subscriber -> {
            BlockFilter blockFilter = new BlockFilter(citaj, new Callback() {
                @Override
                public void onEvent(final String value) {
                    subscriber.onNext(value);
                }
            });
            JsonRpc2_0Rx.this.run(blockFilter, subscriber, pollingInterval);
        }, BackpressureStrategy.BUFFER);
    }

    public Flowable appPendingTransactionHashFlowable(long pollingInterval) {
        return Flowable.create(subscriber -> {
            PendingTransactionFilter pendingTransactionFilter = new PendingTransactionFilter(
                    citaj, subscriber::onNext);

            run(pendingTransactionFilter, subscriber, pollingInterval);
        }, BackpressureStrategy.BUFFER);
    }

    public Flowable appLogFlowable(AppFilter appFilter, long pollingInterval) {
        return Flowable.create(new FlowableOnSubscribe() {
            @Override
            public void subscribe(FlowableEmitter emitter) throws Exception {
                final LogFilter logFilter = new LogFilter(citaj, new Callback() {
                    @Override
                    public void onEvent(Log value) {
                        emitter.onNext(value);
                    }
                }, appFilter);
                run(logFilter, emitter, pollingInterval);
            }
        }, BackpressureStrategy.BUFFER);
    }

    private  void run(
            Filter filter, FlowableEmitter emitter,
            long pollingInterval) {

        filter.run(scheduledExecutorService, pollingInterval);
        emitter.setCancellable(new Cancellable() {
            @Override
            public void cancel() throws Exception {
                filter.cancel();
            }
        });
    }

    public Flowable transactionFlowable(long pollingInterval) {
        return blockFlowable(true, pollingInterval)
                .flatMapIterable(JsonRpc2_0Rx::toTransactions);
    }

    public Flowable pendingTransactionFlowable(long pollingInterval) {
        return appPendingTransactionHashFlowable(pollingInterval)
                .flatMap(new Function>() {
                    @Override
                    public Publisher apply(String transactionHash) throws Exception {
                        return citaj.appGetTransactionByHash(transactionHash).flowable();
                    }
                })
                .filter(new Predicate() {
                    @Override
                    public boolean test(AppTransaction ethTransaction) throws Exception {
                        return ethTransaction.getTransaction() != null;
                    }
                })
                .map(new Function() {
                    @Override
                    public Transaction apply(AppTransaction ethTransaction) throws Exception {
                        return ethTransaction.getTransaction();
                    }
                });
    }

    public Flowable blockFlowable(
            boolean fullTransactionObjects, long pollingInterval) {
        return appBlockHashFlowable(pollingInterval)
                .flatMap(blockHash ->
                        citaj.appGetBlockByHash(
                                blockHash, fullTransactionObjects).flowable());
    }

    public Flowable replayBlocksFlowable(
            DefaultBlockParameter startBlock, DefaultBlockParameter endBlock,
            boolean fullTransactionObjects) {
        return replayBlocksFlowable(startBlock, endBlock, fullTransactionObjects, true);
    }

    public Flowable replayBlocksFlowable(
            DefaultBlockParameter startBlock, DefaultBlockParameter endBlock,
            boolean fullTransactionObjects, boolean ascending) {
        // We use a scheduler to ensure this Flowable runs asynchronously for users to be
        // consistent with the other Flowables
        return replayBlocksFlowableSync(startBlock, endBlock, fullTransactionObjects, ascending)
                .subscribeOn(scheduler);
    }

    private Flowable replayBlocksFlowableSync(
            DefaultBlockParameter startBlock, DefaultBlockParameter endBlock,
            boolean fullTransactionObjects) {
        return replayBlocksFlowableSync(startBlock, endBlock, fullTransactionObjects, true);
    }

    private Flowable replayBlocksFlowableSync(
            DefaultBlockParameter startBlock, DefaultBlockParameter endBlock,
            boolean fullTransactionObjects, boolean ascending) {

        BigInteger startBlockNumber = null;
        BigInteger endBlockNumber = null;
        try {
            startBlockNumber = getBlockNumber(startBlock);
            endBlockNumber = getBlockNumber(endBlock);
        } catch (IOException e) {
            Flowable.error(e);
        }

        if (ascending) {
            return Flowables.range(startBlockNumber, endBlockNumber)
                    .flatMap(i -> citaj.appGetBlockByNumber(
                            new DefaultBlockParameterNumber(i),
                            fullTransactionObjects).flowable());
        } else {
            return Flowables.range(startBlockNumber, endBlockNumber, false)
                    .flatMap(i -> citaj.appGetBlockByNumber(
                            new DefaultBlockParameterNumber(i),
                            fullTransactionObjects).flowable());
        }
    }

    public Flowable replayTransactionsFlowable(
            DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) {
        return replayBlocksFlowable(startBlock, endBlock, true)
                .flatMapIterable(JsonRpc2_0Rx::toTransactions);
    }

    public Flowable catchUpToLatestBlockFlowable(
            DefaultBlockParameter startBlock, boolean fullTransactionObjects,
            Flowable onCompleteFlowable) {
        // We use a scheduler to ensure this Flowable runs asynchronously for users to be
        // consistent with the other Flowables
        return catchUpToLatestBlockFlowableSync(
                startBlock, fullTransactionObjects, onCompleteFlowable)
                .subscribeOn(scheduler);
    }

    public Flowable catchUpToLatestBlockFlowable(
            DefaultBlockParameter startBlock, boolean fullTransactionObjects) {
        return catchUpToLatestBlockFlowable(
                startBlock, fullTransactionObjects, Flowable.empty());
    }

    private Flowable catchUpToLatestBlockFlowableSync(
            DefaultBlockParameter startBlock, boolean fullTransactionObjects,
            Flowable onCompleteFlowable) {

        BigInteger startBlockNumber;
        BigInteger latestBlockNumber;
        try {
            startBlockNumber = getBlockNumber(startBlock);
            latestBlockNumber = getLatestBlockNumber();
        } catch (IOException e) {
            return Flowable.error(e);
        }

        if (startBlockNumber.compareTo(latestBlockNumber) > -1) {
            return onCompleteFlowable;
        } else {
            return Flowable.concat(
                    replayBlocksFlowableSync(
                            new DefaultBlockParameterNumber(startBlockNumber),
                            new DefaultBlockParameterNumber(latestBlockNumber),
                            fullTransactionObjects),
                    Flowable.defer(() -> catchUpToLatestBlockFlowableSync(
                            new DefaultBlockParameterNumber(latestBlockNumber.add(BigInteger.ONE)),
                            fullTransactionObjects,
                            onCompleteFlowable)));
        }
    }

    public Flowable catchUpToLatestTransactionFlowable(
            DefaultBlockParameter startBlock) {
        return catchUpToLatestBlockFlowable(
                startBlock, true, Flowable.empty())
                .flatMapIterable(JsonRpc2_0Rx::toTransactions);
    }

    public Flowable catchUpToLatestAndSubscribeToNewBlocksFlowable(
            DefaultBlockParameter startBlock, boolean fullTransactionObjects,
            long pollingInterval) {

        return catchUpToLatestBlockFlowable(
                startBlock, fullTransactionObjects,
                blockFlowable(fullTransactionObjects, pollingInterval));
    }

    public Flowable catchUpToLatestAndSubscribeToNewTransactionsFlowable(
            DefaultBlockParameter startBlock, long pollingInterval) {
        return catchUpToLatestAndSubscribeToNewBlocksFlowable(
                startBlock, true, pollingInterval)
                .flatMapIterable(JsonRpc2_0Rx::toTransactions);
    }

    private BigInteger getLatestBlockNumber() throws IOException {
        return getBlockNumber(DefaultBlockParameterName.PENDING);
    }

    private BigInteger getBlockNumber(
            DefaultBlockParameter defaultBlockParameter) throws IOException {
        if (defaultBlockParameter instanceof DefaultBlockParameterNumber) {
            return ((DefaultBlockParameterNumber) defaultBlockParameter).getBlockNumber();
        } else {
            AppBlock latestEthBlock = citaj.appGetBlockByNumber(
                    defaultBlockParameter, false).send();
            return latestEthBlock.getBlock().getHeader().getNumberDec();
        }
    }

    private static List toTransactions(AppBlock appBlock) {
        // If you ever see an exception thrown here, it's probably due to an incomplete chain in
        // Geth/Parity. You should resync to solve.
        List transactionResults = appBlock.getBlock().getBody().getTransactions();
        List transactions = new ArrayList(transactionResults.size());

        for (AppBlock.TransactionResult transactionResult : transactionResults) {
            transactions.add((Transaction) transactionResult.get());
        }
        return transactions;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy