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

org.bitcoinj.governance.GovernanceManager Maven / Gradle / Ivy

There is a newer version: 21.0.0
Show newest version
package org.bitcoinj.governance;

import org.bitcoinj.core.*;
import org.bitcoinj.governance.listeners.GovernanceObjectAddedEventListener;
import org.bitcoinj.governance.listeners.GovernanceVoteConfidenceEventListener;
import org.bitcoinj.utils.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReentrantLock;

import static com.google.common.base.Preconditions.checkState;
import static org.bitcoinj.governance.GovernanceException.Type.GOVERNANCE_EXCEPTION_PERMANENT_ERROR;
import static org.bitcoinj.governance.GovernanceException.Type.GOVERNANCE_EXCEPTION_WARNING;
import static org.bitcoinj.governance.GovernanceObject.*;

/**
 * Created by HashEngineering on 5/11/2018.
 */
public class GovernanceManager extends AbstractManager {
    private static final Logger log = LoggerFactory.getLogger(GovernanceManager.class);
    // critical section to protect the inner data structures
    ReentrantLock lock = Threading.lock("GovernanceManager");

    public static final int MAX_GOVERNANCE_OBJECT_DATA_SIZE = 16 * 1024;
    public static final int MIN_GOVERNANCE_PEER_PROTO_VERSION = 70208;
    public static final int GOVERNANCE_FILTER_PROTO_VERSION = 70208;

    public static int nSubmittedFinalBudget;
    public static final int MAX_TIME_FUTURE_DEVIATION = 60*60;
    public static final int RELIABLE_PROPAGATION_TIME = 60;

    public static final String SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-12";


    private static final int MAX_CACHE_SIZE = 1000000;

    private long nTimeLastDiff;

    // keep track of current block height
    private int nCachedBlockHeight;

    // keep track of the scanning errors
    private HashMap mapObjects;

    // mapErasedGovernanceObjects contains key-value pairs, where
    //   key   - governance object's hash
    //   value - expiration time for deleted objects
    private HashMap mapErasedGovernanceObjects;

    private HashMap> mapMasternodeOrphanObjects;
    private HashMap mapMasternodeOrphanCounter;

    private HashMap mapPostponedObjects;
    private HashSet setAdditionalRelayObjects;

    private HashMap mapWatchdogObjects;

    private Sha256Hash nHashWatchdogCurrent;

    private long nTimeWatchdogCurrent;

    private CacheMap mapVoteToObject;

    private CacheMap mapInvalidVotes;

    private CacheMultiMap> mapOrphanVotes;

    private HashMap mapLastMasternodeObject;

    private HashSet setRequestedObjects;

    private HashSet setRequestedVotes;

    private boolean fRateChecksEnabled;

    public GovernanceManager(Context context) {
        super(context);
        this.nTimeLastDiff = 0;
        this.nCachedBlockHeight = 0;
        this.mapObjects = new HashMap();
        this.mapErasedGovernanceObjects = new HashMap();
        this.mapMasternodeOrphanObjects = new HashMap>();
        this.mapWatchdogObjects = new HashMap();
        this.nHashWatchdogCurrent = Sha256Hash.ZERO_HASH;
        this.nTimeWatchdogCurrent = 0;
        this.mapVoteToObject = new CacheMap(MAX_CACHE_SIZE);
        this.mapInvalidVotes = new CacheMap(MAX_CACHE_SIZE);
        this.mapOrphanVotes = new CacheMultiMap>(MAX_CACHE_SIZE);
        this.mapLastMasternodeObject = new HashMap();
        this.setRequestedObjects = new HashSet();
        this.setRequestedVotes = new HashSet();
        this.fRateChecksEnabled = true;

        this.mapPostponedObjects = new HashMap();
        this.mapMasternodeOrphanCounter = new HashMap();

        this.governanceObjectAddedListeners = new CopyOnWriteArrayList>();
    }

    public GovernanceManager(NetworkParameters params, byte [] payload, int cursor) {
        super(params, payload, cursor);
    }

    public int calculateMessageSizeInBytes() {
        int size = 0;
        lock.lock();
        try {
            size += 1;
            size *= 1000;


            return size;
        }
        finally {
            lock.unlock();
        }
    }
    @Override
    protected void parse() throws ProtocolException {

        String version = readStr();

        //READWRITE(mapErasedGovernanceObjects);
        int size = (int)readVarInt();
        mapErasedGovernanceObjects = new HashMap(size);
        for(int i = 0; i < size; ++i) {
            Sha256Hash hash = readHash();
            long time = readInt64();
            mapErasedGovernanceObjects.put(hash, time);
        }

        //READWRITE(mapInvalidVotes);
        mapInvalidVotes = new CacheMap(params, payload, cursor);
        cursor += mapInvalidVotes.getMessageSize();
        //READWRITE(mapOrphanVotes);
        mapOrphanVotes = new CacheMultiMap>(params, payload, cursor);
        cursor += mapOrphanVotes.getMessageSize();
        //READWRITE(mapObjects);
        size = (int)readVarInt();
        mapObjects = new HashMap(size);
        for(int i = 0; i < size; ++i) {
            Sha256Hash hash = readHash();
            GovernanceObjectFromFile govobj = new GovernanceObjectFromFile(params, payload, cursor);
            cursor += govobj.getMessageSize();
            mapObjects.put(hash, govobj);
        }
        //READWRITE(mapWatchdogObjects);
        size = (int)readVarInt();
        mapWatchdogObjects = new HashMap(size);
        for(int i = 0; i < size; ++i) {
            Sha256Hash hash = readHash();
            long time = readInt64();
            mapWatchdogObjects.put(hash, time);
        }
        //READWRITE(nHashWatchdogCurrent);
        nHashWatchdogCurrent = readHash();
        //READWRITE(nTimeWatchdogCurrent);
        nTimeWatchdogCurrent = readInt64();
        //READWRITE(mapLastMasternodeObject);
        size = (int)readVarInt();
        mapLastMasternodeObject = new HashMap(size);
        for(int i = 0; i < size; ++i) {
            TransactionOutPoint outPoint = new TransactionOutPoint(params, payload, cursor);
            cursor += outPoint.getMessageSize();
            LastObjectRecord record = new LastObjectRecord(params, payload, cursor);
            cursor += record.getMessageSize();
            mapLastMasternodeObject.put(outPoint, record);
        }

        if(!version.equals(SERIALIZATION_VERSION_STRING))
            clear();

        length = cursor - offset;
    }
    @Override
    protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {

        lock.lock();
        try {
            stream.write(new VarInt(SERIALIZATION_VERSION_STRING.length()).encode());
            stream.write(SERIALIZATION_VERSION_STRING.getBytes());

            //READWRITE(mapErasedGovernanceObjects);
            stream.write(new VarInt(mapErasedGovernanceObjects.size()).encode());
            for(Map.Entry entry : mapErasedGovernanceObjects.entrySet()) {
                stream.write(entry.getKey().getReversedBytes());
                Utils.int64ToByteStreamLE(entry.getValue(), stream);
            }
            //READWRITE(mapInvalidVotes);
            mapInvalidVotes.bitcoinSerialize(stream);
            //READWRITE(mapOrphanVotes);
            mapOrphanVotes.bitcoinSerialize(stream);
            //READWRITE(mapObjects);
            stream.write(new VarInt(mapObjects.size()).encode());
            for(Map.Entry entry : mapObjects.entrySet()) {
                stream.write(entry.getKey().getReversedBytes());
                entry.getValue().bitcoinSerialize(stream);
                entry.getValue().serializeToDisk(stream);
            }
            //READWRITE(mapWatchdogObjects);
            stream.write(new VarInt(mapWatchdogObjects.size()).encode());
            for(Map.Entry entry : mapWatchdogObjects.entrySet()) {
                stream.write(entry.getKey().getReversedBytes());
                Utils.int64ToByteStreamLE(entry.getValue(), stream);
            }
            //READWRITE(nHashWatchdogCurrent);
            stream.write(nHashWatchdogCurrent.getReversedBytes());
            //READWRITE(nTimeWatchdogCurrent);
            Utils.int64ToByteStreamLE(nTimeWatchdogCurrent, stream);
            //READWRITE(mapLastMasternodeObject);
            stream.write(new VarInt(mapLastMasternodeObject.size()).encode());
            for(Map.Entry entry : mapLastMasternodeObject.entrySet()) {
                entry.getKey().bitcoinSerialize(stream);
                entry.getValue().bitcoinSerialize(stream);
            }
        } finally {
            lock.unlock();
        }
    }

    public void clear() {
        lock.lock();
        try {
            unCache();
            log.info("gobject--Governance object manager was cleared");
            mapObjects.clear();
            mapErasedGovernanceObjects.clear();
            mapWatchdogObjects.clear();
            nHashWatchdogCurrent = Sha256Hash.ZERO_HASH;
            nTimeWatchdogCurrent = 0;
            mapVoteToObject.clear();
            mapInvalidVotes.clear();
            mapOrphanVotes.clear();
            mapLastMasternodeObject.clear();

        } finally {
            lock.unlock();
        }
    }

    @Override
    public AbstractManager createEmpty() {
        return new GovernanceManager(Context.get());
    }

    public void processGovernanceObject(Peer peer, GovernanceObject govobj) {
        Sha256Hash nHash = govobj.getHash();

        peer.setAskFor.remove(nHash);

        if(!context.masternodeSync.isBlockchainSynced()) {
            log.info("gobject--MNGOVERNANCEOBJECT -- masternode list not synced");
            return;
        }

        String strHash = nHash.toString();

        log.info("gobject--MNGOVERNANCEOBJECT -- Received object: {}", strHash);

        if(!acceptObjectMessage(nHash)) {
            log.info("MNGOVERNANCEOBJECT -- Received unrequested object: {}", strHash);
            return;
        }

        lock.lock();
        try {

            if (mapObjects.containsKey(nHash) || mapPostponedObjects.containsKey(nHash) ||
                    mapErasedGovernanceObjects.containsKey(nHash) || mapMasternodeOrphanObjects.containsKey(nHash)) {
                // TODO - print error code? what if it's GOVOBJ_ERROR_IMMATURE?
                log.info("gobject--MNGOVERNANCEOBJECT -- Received already seen object: {}", strHash);
                return;
            }

            boolean fRateCheckBypassed = false;
            if (!masternodeRateCheck(govobj, true, false).getFirst()) {
                log.info("MNGOVERNANCEOBJECT -- masternode rate check failed - {} - (current block height {})", strHash, nCachedBlockHeight);
                return;
            }

            StringBuilder strError = new StringBuilder();
            // CHECK OBJECT AGAINST LOCAL BLOCKCHAIN

            GovernanceObject.Validity validity = new GovernanceObject.Validity();
            boolean fIsValid = govobj.isValidLocally(validity, true);

            if (fRateCheckBypassed && (fIsValid || validity.fMissingMasternode)) {
                if (!masternodeRateCheck(govobj, true)) {
                    log.info("MNGOVERNANCEOBJECT -- masternode rate check failed (after signature verification) - {} - (current block height {})", strHash, nCachedBlockHeight);
                    return;
                }
            }

            if (!fIsValid) {
                if (validity.fMissingMasternode) {

                int count = mapMasternodeOrphanCounter.get(govobj.getMasternodeOutpoint());
                    if (count >= 10) {
                        log.info("gobject--MNGOVERNANCEOBJECT -- Too many orphan objects, missing masternode={}", govobj.getMasternodeOutpoint().toStringShort());
                        // ask for this object again in 2 minutes
                        InventoryItem inv = new InventoryItem(InventoryItem.Type.GovernanceObject, govobj.getHash());
                        peer.askFor(inv);
                        return;
                    }

                    count++;
                    mapMasternodeOrphanCounter.put(govobj.getMasternodeOutpoint(), count);
                    ExpirationInfo info = new ExpirationInfo(peer.hashCode(), Utils.currentTimeSeconds() + GOVERNANCE_ORPHAN_EXPIRATION_TIME);
                    mapMasternodeOrphanObjects.put(nHash, new Pair(govobj, info));
                    log.info("MNGOVERNANCEOBJECT -- Missing masternode for: {}, strError = {}", strHash, strError);
                } else if (validity.fMissingConfirmations) {
                    addPostponedObject(govobj);
                    log.info("MNGOVERNANCEOBJECT -- Not enough fee confirmations for: {}, strError = {}", strHash, strError);
                } else {
                    log.info("MNGOVERNANCEOBJECT -- Governance object is invalid - {}", strError);
                    // apply node's ban score
                    //Misbehaving(pfrom -> GetId(), 20);
                }

                return;
            }

            addGovernanceObject(govobj, peer);
        } finally {
            lock.unlock();
        }
    }

    public void processGovernanceObjectVote(Peer peer, GovernanceVote vote) {
        Sha256Hash nHash = vote.getHash();

        peer.setAskFor.remove(nHash);

        // Ignore such messages until masternode list is synced
        if (!context.masternodeSync.isBlockchainSynced()) {
            log.info("gobject--MNGOVERNANCEOBJECTVOTE -- masternode list not synced");
            return;
        }

        log.info("gobject--MNGOVERNANCEOBJECTVOTE -- Received vote: {}", vote.toString());

        String strHash = nHash.toString();

        if (!acceptVoteMessage(nHash)) {
            log.info("gobject--MNGOVERNANCEOBJECTVOTE -- Received unrequested vote object: {}, hash: {}, peer = {}", vote.toString(), strHash, peer.hashCode());
            return;
        }

        GovernanceException exception = new GovernanceException();
        if (processVote(peer, vote, exception)) {
            log.info("gobject--MNGOVERNANCEOBJECTVOTE -- {} new", strHash);
            context.masternodeSync.bumpAssetLastTime("processGovernanceObjectVote");
            vote.relay();
        } else {
            log.info("gobject--MNGOVERNANCEOBJECTVOTE -- Rejected vote, error = {}", exception.getMessage());
            if ((exception.getNodePenalty() != 0) && context.masternodeSync.isSynced()) {
                //Misbehaving(pfrom.GetId(), exception.GetNodePenalty());
            }
            return;
        }

    }

    public boolean processVote(Peer pfrom, GovernanceVote vote, GovernanceException exception) {
        lock.lock();
        try {
            Sha256Hash nHashVote = vote.getHash();
            if (mapInvalidVotes.hasKey(nHashVote)) {
                String message = "CGovernanceManager::ProcessVote -- Old invalid vote, MN outpoint = " + vote.getMasternodeOutpoint().toStringShort() +
                        ", governance object hash = " + vote.getParentHash().toString();
                log.info(message);
                exception.setException(message, GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20);
                return false;
            }

            Sha256Hash nHashGovobj = vote.getParentHash();
            GovernanceObject govobj = mapObjects.get(nHashGovobj);
            if (govobj == null) {
                String message = "CGovernanceManager::ProcessVote -- Unknown parent object, MN outpoint = " + vote.getMasternodeOutpoint().toStringShort() +
                        ", governance object hash = " + vote.getParentHash().toString();
                exception.setException(message, GOVERNANCE_EXCEPTION_WARNING);
                if (mapOrphanVotes.insert(nHashGovobj, new Pair(vote, Utils.currentTimeSeconds() + GOVERNANCE_ORPHAN_EXPIRATION_TIME))) {
                    requestGovernanceObject(pfrom, nHashGovobj, false);
                    log.info(message);
                    return false;
                }

                log.info("gobject--{}", message);
                return false;
            }

            if (govobj.isSetCachedDelete() || govobj.isSetExpired()) {
                log.info("gobject--CGovernanceObject::ProcessVote -- ignoring vote for expired or deleted object, hash = {}", nHashGovobj.toString());
                return false;
            }

            boolean fOk = govobj.processVote(pfrom, vote, exception);
            if (fOk) {
                mapVoteToObject.insert(nHashVote, govobj);

                /* TODO:  Fix Governance Objects
                if (govobj.getObjectType() == GOVERNANCE_OBJECT_WATCHDOG) {
                    context.masternodeManager.updateLastSentinelPingTime(vote.getMasternodeOutpoint());
                    log.info("gobject--CGovernanceObject::ProcessVote -- GOVERNANCE_OBJECT_WATCHDOG vote for {}", vote.getParentHash());
                }*/
            }
            return fOk;
        } finally {
            lock.unlock();
        }
    }


    public boolean acceptObjectMessage(Sha256Hash nHash) {
        lock.lock();
        try {
            return acceptMessage(nHash, setRequestedObjects);
        } finally {
            lock.unlock();
        }
    }
    public boolean acceptVoteMessage(Sha256Hash nHash) {
        lock.lock();
        try {
            return acceptMessage(nHash, setRequestedVotes);
        } finally {
            lock.unlock();
        }
    }
    public boolean acceptMessage(Sha256Hash nHash, HashSet setHash) {
        if(!setHash.contains(nHash)) {
            // We never requested this
            return false;
        }
        // Only accept one response
        setHash.remove(nHash);
        return true;
    }

    public void masternodeRateUpdate(GovernanceObject govobj) {
        int nObjectType = govobj.getObjectType();
        if ((nObjectType != GOVERNANCE_OBJECT_TRIGGER) && (nObjectType != GOVERNANCE_OBJECT_WATCHDOG)) {
            return;
        }

        final TransactionOutPoint outpoint = govobj.getMasternodeOutpoint();
        LastObjectRecord it = mapLastMasternodeObject.get(outpoint);

        if (it == null) {
            it = new LastObjectRecord(params, true);
            mapLastMasternodeObject.put(outpoint, it);
        }

        long nTimestamp = govobj.getCreationTime();
        if (GOVERNANCE_OBJECT_TRIGGER == nObjectType) {
            it.triggerBuffer.addTimestamp(nTimestamp);
        } else if (GOVERNANCE_OBJECT_WATCHDOG == nObjectType) {
            it.watchdogBuffer.addTimestamp(nTimestamp);
        }

        if (nTimestamp > Utils.currentTimeSeconds() + MAX_TIME_FUTURE_DEVIATION - RELIABLE_PROPAGATION_TIME) {
            // schedule additional relay for the object
            setAdditionalRelayObjects.add(govobj.getHash());
        }

        it.fStatusOK = true;
    }


    public boolean masternodeRateCheck(GovernanceObject govobj, boolean fUpdateFailStatus) {
        Pair result =  masternodeRateCheck(govobj, fUpdateFailStatus, true);
        return result.getFirst();
    }


    /**
     * Masternode rate check.
     *
     * @param govobj            the govobj
     * @param fUpdateFailStatus the f update fail status
     * @param fForce            the f force ok
     * @return the pair success, fRateCheckBypassed
     */
    public Pair masternodeRateCheck(GovernanceObject govobj, boolean fUpdateFailStatus, boolean fForce) {
        lock.lock();
        Pair result = new Pair(false, false);
        try {

            result.setSecond(false);

            if (!context.masternodeSync.isSynced()) {
                result.setFirst(true);
                return result;
            }

            if (!fRateChecksEnabled) {
                result.setFirst(true);
                return result;
            }

            int nObjectType = govobj.getObjectType();
            if ((nObjectType != GOVERNANCE_OBJECT_TRIGGER) && (nObjectType != GOVERNANCE_OBJECT_WATCHDOG)) {
                result.setFirst(true);
                return result;
            }

            final TransactionOutPoint outpoint = govobj.getMasternodeOutpoint();
            long nTimestamp = govobj.getCreationTime();
            long nNow = Utils.currentTimeSeconds();
            long nSuperblockCycleSeconds = (long)params.getSuperblockCycle() * params.TARGET_SPACING;

            String strHash = govobj.getHash().toString();

            if (nTimestamp < nNow - 2 * nSuperblockCycleSeconds) {
                log.info("CGovernanceManager::MasternodeRateCheck -- object {} rejected due to too old timestamp, masternode vin = {}, timestamp = {}, current time = {}", strHash, outpoint.toStringShort(), nTimestamp, nNow);
                result.setFirst(false);
                return result;
            }

            if (nTimestamp > nNow + MAX_TIME_FUTURE_DEVIATION) {
                log.info("CGovernanceManager::MasternodeRateCheck -- object {} rejected due to too new (future) timestamp, masternode vin = {}, timestamp = {}d, current time = {}", strHash, outpoint.toStringShort(), nTimestamp, nNow);
                result.setFirst(false);
                return result;
            }

            LastObjectRecord it = mapLastMasternodeObject.get(outpoint);
            if (it ==null) {
                result.setFirst(true);
                return result;
            }

            if (it.fStatusOK && !fForce) {
                result.setSecond(true);
                result.setFirst(true);
                return result;
            }

            double dMaxRate = 1.1 / nSuperblockCycleSeconds;
            double dRate = 0.0;
            RateCheckBuffer buffer = new RateCheckBuffer(params);
            switch (nObjectType) {
                case GOVERNANCE_OBJECT_TRIGGER:
                    // Allow 1 trigger per mn per cycle, with a small fudge factor
                    buffer = it.triggerBuffer;
                    dMaxRate = 2 * 1.1 / (double) nSuperblockCycleSeconds;
                    break;
                case GOVERNANCE_OBJECT_WATCHDOG:
                    buffer = it.watchdogBuffer;
                    dMaxRate = 2 * 1.1 / 3600.0;
                    break;
                default:
                    break;
            }

            buffer.addTimestamp(nTimestamp);
            dRate = buffer.getRate();

            boolean fRateOK = (dRate < dMaxRate);

            if (!fRateOK) {
                log.info("CGovernanceManager::MasternodeRateCheck -- Rate too high: object hash = {}, " +
                         "masternode vin = {}, object timestamp = {}, rate = {}, max rate = {}",
                          strHash, outpoint.toStringShort(), nTimestamp, dRate, dMaxRate);

                if (fUpdateFailStatus) {
                    it.fStatusOK = false;
                }
            }

            result.setFirst(fRateOK);
            return result;
        } finally {
            lock.unlock();
        }
    }

    void addPostponedObject(final GovernanceObject govobj)
    {
        lock.lock();
        try {
            mapPostponedObjects.put(govobj.getHash(), govobj);
        } finally {
            lock.unlock();
        }
    }

    long getLastDiffTime() { return nTimeLastDiff; }
    void updateLastDiffTime(long nTimeIn) { nTimeLastDiff = nTimeIn; }

    int getCachedBlockHeight() { return nCachedBlockHeight; }

    public void addInvalidVote(final GovernanceVote vote)
    {
        mapInvalidVotes.insert(vote.getHash(), vote);
    }

    void addOrphanVote(final GovernanceVote vote)
    {
        mapOrphanVotes.insert(vote.getHash(), new Pair(vote, Utils.currentTimeSeconds() + GOVERNANCE_ORPHAN_EXPIRATION_TIME));
    }

    public boolean areRateChecksEnabled() {
        lock.lock();
        try {
            return fRateChecksEnabled;
        } finally {
            lock.unlock();
        }
    }

    public void checkOrphanVotes(GovernanceObject govobj, GovernanceException exception) {
        Sha256Hash nHash = govobj.getHash();
        ArrayList> vecVotePairs = new ArrayList>();
        mapOrphanVotes.getAll(nHash, vecVotePairs);

        lock.lock();
        boolean _fRateChecksEnabled = fRateChecksEnabled;
        fRateChecksEnabled = false;
        try {

            long nNow = Utils.currentTimeSeconds();
            for (int i = 0; i < vecVotePairs.size(); ++i) {
                boolean fRemove = false;
                Pair pairVote = vecVotePairs.get(i);
                GovernanceVote vote = pairVote.getFirst();

                if (pairVote.getSecond() < nNow) {
                    fRemove = true;
                } else if (govobj.processVote(null, vote, exception)) {
                    vote.relay();
                    fRemove = true;
                }
                if (fRemove) {
                    mapOrphanVotes.erase(nHash, pairVote);
                }
            }
        } finally {
            fRateChecksEnabled = _fRateChecksEnabled;
            lock.unlock();
        }
    }


    void addGovernanceObject(GovernanceObject govobj, Peer pfrom)
    {
        log.info("CGovernanceManager::AddGovernanceObject START");

        Sha256Hash nHash = govobj.getHash();
        String strHash = nHash.toString();

        // UPDATE CACHED VARIABLES FOR THIS OBJECT AND ADD IT TO OUR MANANGED DATA

        govobj.updateSentinelVariables(); //this sets local vars in object

        //LOCK2(cs_main, cs);
        lock.lock();
        try {
            GovernanceObject.Validity validity = new GovernanceObject.Validity();

            // MAKE SURE THIS OBJECT IS OK

            if(!govobj.isValidLocally(validity, true)) {
                log.info("CGovernanceManager::AddGovernanceObject -- invalid governance object - {} - (nCachedBlockHeight {})", validity.strError, nCachedBlockHeight);
                return;
            }

            // IF WE HAVE THIS OBJECT ALREADY, WE DON'T WANT ANOTHER COPY

            if(mapObjects.containsKey(nHash)) {
                log.info("CGovernanceManager::AddGovernanceObject -- already have governance object {}", nHash.toString());
                return;
            }

            log.info("gobject--CGovernanceManager::AddGovernanceObject -- Adding object: hash = {}, type = {}", nHash.toString(), govobj.getObjectType());

            if(govobj.getObjectType() == GOVERNANCE_OBJECT_WATCHDOG) {
                // If it's a watchdog, make sure it fits required time bounds
                if((govobj.getCreationTime() < Utils.currentTimeSeconds() - GOVERNANCE_WATCHDOG_EXPIRATION_TIME ||
                        govobj.getCreationTime() > Utils.currentTimeSeconds() + GOVERNANCE_WATCHDOG_EXPIRATION_TIME)
                        ) {
                    // drop it
                    log.info("gobject--CGovernanceManager::AddGovernanceObject -- CreationTime is out of bounds: hash = {}", nHash.toString());
                    return;
                }

                if(!updateCurrentWatchdog(govobj)) {
                    // Allow wd's which are not current to be reprocessed
                    if(pfrom != null && !nHashWatchdogCurrent.isZero()) {
                        pfrom.pushInventory(new InventoryItem(InventoryItem.Type.GovernanceObject, nHashWatchdogCurrent));
                    }
                    log.info("gobject--CGovernanceManager::AddGovernanceObject -- Watchdog not better than current: hash = {}", nHash.toString());
                    return;
                }
            }

            // INSERT INTO OUR GOVERNANCE OBJECT MEMORY
            mapObjects.put(nHash, govobj);
            queueOnGovernanceObjectAdded(nHash, govobj);
            unCache();

            // SHOULD WE ADD THIS OBJECT TO ANY OTHER MANANGERS?

                log.info( "CGovernanceManager::AddGovernanceObject Before trigger block, strData = "
                    + govobj.getDataAsPlainString() +
                    ", nObjectType = " + govobj.getObjectType());

            switch(govobj.getObjectType()) {
                case GOVERNANCE_OBJECT_TRIGGER:
                    log.info("CGovernanceManager::AddGovernanceObject Before AddNewTrigger");
                    context.triggerManager.addNewTrigger(nHash);
                    log.info("CGovernanceManager::AddGovernanceObject After AddNewTrigger");
                    break;
                case GOVERNANCE_OBJECT_WATCHDOG:
                    mapWatchdogObjects.put(nHash, govobj.getCreationTime() + GOVERNANCE_WATCHDOG_EXPIRATION_TIME);
                    log.info("gobject--CGovernanceManager::AddGovernanceObject -- Added watchdog to map: hash = {}", nHash.toString());
                    break;
                default:
                    break;
            }

            log.info("AddGovernanceObject -- {} new, received from {}", strHash, pfrom != null ? pfrom.getAddress().getHostname() : "NULL");
            govobj.relay();

            // Update the rate buffer
            masternodeRateUpdate(govobj);

            context.masternodeSync.bumpAssetLastTime("addGovernanceObject");

            // WE MIGHT HAVE PENDING/ORPHAN VOTES FOR THIS OBJECT

            GovernanceException exception = new GovernanceException();
            checkOrphanVotes(govobj, exception);

            log.info("CGovernanceManager::AddGovernanceObject END");
        } finally {
            lock.unlock();
        }
    }

    public boolean updateCurrentWatchdog(GovernanceObject watchdogNew) {
        boolean fAccept = false;

        BigInteger nHashNew = new BigInteger(watchdogNew.getHash().getBytes());
        BigInteger nHashCurrent = new BigInteger(nHashWatchdogCurrent.getBytes());

        long nExpirationDelay = GOVERNANCE_WATCHDOG_EXPIRATION_TIME / 2;
        long nNow = Utils.currentTimeSeconds();

        if (nHashWatchdogCurrent.isZero() || ((nNow - watchdogNew.getCreationTime() < nExpirationDelay) && ((nNow - nTimeWatchdogCurrent > nExpirationDelay) || (nHashNew.compareTo(nHashCurrent) > 0))))
        { //  (current is expired OR -  (new one is NOT expired AND -  no known current OR
            //   its hash is lower))
            lock.lock();
            try {
                GovernanceObject it = mapObjects.get(nHashWatchdogCurrent);
                if (it != null) {
                    log.info("gobject--CGovernanceManager::UpdateCurrentWatchdog -- Expiring previous current watchdog, hash = {}", nHashWatchdogCurrent.toString());
                    it.setExpired(true);
                    if (it.getDeletionTime() == 0) {
                        it.setDeletionTime(nNow);
                    }
                }
                nHashWatchdogCurrent = watchdogNew.getHash();
                nTimeWatchdogCurrent = watchdogNew.getCreationTime();
                fAccept = true;
                log.info("gobject--CGovernanceManager::UpdateCurrentWatchdog -- Current watchdog updated to: hash = {}", Sha256Hash.wrap(nHashNew.toByteArray()).toString());
            } finally {
                lock.unlock();
            }
        }

        return fAccept;
    }

    public GovernanceObject findGovernanceObject(Sha256Hash nHash)
    {
        lock.lock();
        try {
            return mapObjects.get(nHash);
        } finally {
            lock.unlock();
        }
    }

    public void doMaintenance()
    {
        if(context.isLiteMode() || !context.masternodeSync.isSynced()) return;

        // CHECK OBJECTS WE'VE ASKED FOR, REMOVE OLD ENTRIES

        cleanOrphanObjects();

        requestOrphanObjects();

        // CHECK AND REMOVE - REPROCESS GOVERNANCE OBJECTS

        updateCachesAndClean();
    }

    public boolean confirmInventoryRequest(InventoryItem inv) {

        lock.lock();
        try {

            //log.info("gobject--CGovernanceManager::ConfirmInventoryRequest inv = {}", inv);

            // First check if we've already recorded this object
            switch (inv.type) {
                case GovernanceObject:
                    if (mapObjects.containsKey(inv.hash) || mapPostponedObjects.containsKey(inv.hash)) {
                        log.info("gobject--CGovernanceManager::ConfirmInventoryRequest already have governance object, returning false, object = {}", inv);
                        return false;
                    }
                    break;
                case GovernanceObjectVote:
                    if (mapVoteToObject.hasKey(inv.hash)) {
                        log.info("gobject--CGovernanceManager::ConfirmInventoryRequest already have governance vote, returning false, vote = {}", inv);
                        return false;
                    }
                    break;
                default:
                    log.info("gobject--CGovernanceManager::ConfirmInventoryRequest unknown type, returning false");
                    return false;
            }


            HashSet setHash = null;
            switch (inv.type) {
                case GovernanceObject:
                    setHash = setRequestedObjects;
                    break;
                case GovernanceObjectVote:
                    setHash = setRequestedVotes;
                    break;
                default:
                    return false;
            }

            boolean hasHash = setHash.contains(inv.hash);
            if (!hasHash) {
                setHash.add(inv.hash);
                log.info("gobject--CGovernanceManager::ConfirmInventoryRequest added inv to requested set, {}, object = {}", inv.type, inv);
            }

            //log.info("gobject--CGovernanceManager::ConfirmInventoryRequest reached end, returning true");
            return true;
        } finally {
            lock.unlock();
        }
    }

    public void checkAndRemove() {
        updateCachesAndClean();
    }

    public void updateCachesAndClean() {
        log.info("gobject--CGovernanceManager::UpdateCachesAndClean");

        ArrayList vecDirtyHashes = context.masternodeMetaDataManager.getAndClearDirtyGovernanceObjectHashes();
        
        lock.lock();
        try {

            // Flag expired watchdogs for removal
            long nNow = Utils.currentTimeSeconds();
            log.info("gobject--CGovernanceManager::UpdateCachesAndClean -- Number watchdogs in map: {}, current time = {}", mapWatchdogObjects.size(), nNow);
            if (mapWatchdogObjects.size() > 1) {
                Iterator> it = mapWatchdogObjects.entrySet().iterator();
                while (it.hasNext()) {
                    Map.Entry entry = it.next();
                    log.info("gobject--CGovernanceManager::UpdateCachesAndClean -- Checking watchdog: {}, expiration time = {}", entry.getKey(), entry.getValue());
                    if (entry.getValue() < nNow) {
                        log.info("gobject--CGovernanceManager::UpdateCachesAndClean -- Attempting to expire watchdog: {}, expiration time = {}", entry.getKey(), entry.getValue());
                        GovernanceObject governanceObject = mapObjects.get(entry.getKey());
                        if (governanceObject != null) {
                            log.info("gobject--CGovernanceManager::UpdateCachesAndClean -- Expiring watchdog: {}, expiration time = {}", entry.getValue(), entry.getValue());
                            governanceObject.setExpired(true);
                            if (governanceObject.getDeletionTime() == 0) {
                                governanceObject.setDeletionTime(nNow);
                            }
                        }
                        if (entry.getKey().equals(nHashWatchdogCurrent)) {
                            nHashWatchdogCurrent = Sha256Hash.ZERO_HASH;
                        }
                        it.remove();
                    }
                }
            }

            for (int i = 0; i < vecDirtyHashes.size(); ++i) {
                GovernanceObject it = mapObjects.get(vecDirtyHashes.get(i));
                if (it == null) {
                    continue;
                }
                it.clearMasternodeVotes();
                it.setDirtyCache(true);
            }

            //ScopedLockBool guard = new ScopedLockBool(cs, fRateChecksEnabled, false);
            lock.lock();
            boolean _fRateChecksEnabled = fRateChecksEnabled;
            fRateChecksEnabled = false;
            try {

                // UPDATE CACHE FOR EACH OBJECT THAT IS FLAGGED DIRTYCACHE=TRUE

                Iterator> it = mapObjects.entrySet().iterator();

                // Clean up any expired or invalid triggers
                context.triggerManager.cleanAndRemove();

                while (it.hasNext()) {
                    Map.Entry entry = it.next();
                    GovernanceObject pObj = entry.getValue();

                    if (pObj == null) {
                        continue;
                    }

                    Sha256Hash nHash = entry.getKey();
                    String strHash = nHash.toString();

                    // IF CACHE IS NOT DIRTY, WHY DO THIS?
                    if (pObj.isSetDirtyCache()) {
                        // UPDATE LOCAL VALIDITY AGAINST CRYPTO DATA
                        pObj.updateLocalValidity();

                        // UPDATE SENTINEL SIGNALING VARIABLES
                        pObj.updateSentinelVariables();
                    }

                    if (pObj.isSetCachedDelete() && (nHash == nHashWatchdogCurrent)) {
                        nHashWatchdogCurrent = Sha256Hash.ZERO_HASH;
                    }

                    // IF DELETE=TRUE, THEN CLEAN THE MESS UP!

                    long nTimeSinceDeletion = Utils.currentTimeSeconds() - pObj.getDeletionTime();

                    log.info("gobject--CGovernanceManager::UpdateCachesAndClean -- Checking object for deletion: {}, deletion time = {}, time since deletion = {}, delete flag = {}, expired flag = {}",
                            strHash, pObj.getDeletionTime(), nTimeSinceDeletion, pObj.isSetCachedDelete(), pObj.isSetExpired());

                    if ((pObj.isSetCachedDelete() || pObj.isSetExpired()) && (nTimeSinceDeletion >= GOVERNANCE_DELETION_DELAY)) {
                        log.info("CGovernanceManager::UpdateCachesAndClean -- erase obj {}", entry.getValue());
                        context.masternodeMetaDataManager.removeGovernanceObject(pObj.getHash());

                        // Remove vote references
                        final LinkedList> listItems = mapVoteToObject.getItemList();
                        Iterator> lit = listItems.iterator();
                        while (lit.hasNext()) {
                            CacheItem item = lit.next();
                            if (item.value == pObj) {
                                Sha256Hash nKey = item.key;
                                //mapVoteToObject.erase(nKey);//TODO: crash here?
                                lit.remove();
                            }
                        }

                        long nSuperblockCycleSeconds = (long)params.getSuperblockCycle() * params.TARGET_SPACING;
                        long nTimeExpired = pObj.getCreationTime() + 2 * nSuperblockCycleSeconds + GOVERNANCE_DELETION_DELAY;

                        if (pObj.getObjectType() == GOVERNANCE_OBJECT_WATCHDOG) {
                            mapWatchdogObjects.remove(nHash);
                        } else if (pObj.getObjectType() != GOVERNANCE_OBJECT_TRIGGER) {
                            // keep hashes of deleted proposals forever
                            nTimeExpired = Long.MAX_VALUE;
                        }

                        mapErasedGovernanceObjects.put(nHash, nTimeExpired);
                        it.remove();
                    }
                }

                // forget about expired deleted objects
                Iterator> sIt = mapErasedGovernanceObjects.entrySet().iterator();
                while (sIt.hasNext()) {
                    if (sIt.next().getValue() < nNow) {
                        sIt.remove();
                    }
                }
            } finally {
                fRateChecksEnabled = _fRateChecksEnabled;
                lock.unlock();
            }

            log.info("CGovernanceManager::UpdateCachesAndClean -- {}", toString());
            unCache();
        } finally {
            lock.unlock();
        }
    }

    public String toString() {
        lock.lock();
        try {
            int nProposalCount = 0;
            int nTriggerCount = 0;
            int nWatchdogCount = 0;
            int nOtherCount = 0;

            Iterator> it = mapObjects.entrySet().iterator();

            while (it.hasNext()) {
                switch (it.next().getValue().getObjectType()) {
                    case GOVERNANCE_OBJECT_PROPOSAL:
                        nProposalCount++;
                        break;
                    case GOVERNANCE_OBJECT_TRIGGER:
                        nTriggerCount++;
                        break;
                    case GOVERNANCE_OBJECT_WATCHDOG:
                        nWatchdogCount++;
                        break;
                    default:
                        nOtherCount++;
                        break;
                }
            }

            return String.format("Governance Objects: %d (Proposals: %d, Triggers: %d, Watchdogs: %d/%d, Other: %d; Erased: %d), Votes: %d",
                    mapObjects.size(), nProposalCount, nTriggerCount, nWatchdogCount, mapWatchdogObjects.size(), nOtherCount,
                    mapErasedGovernanceObjects.size(), (int) mapVoteToObject.getSize());
        } finally {
            lock.unlock();
        }
    }

    public void updatedBlockTip(StoredBlock newBlock) {
        // Note this gets called from ActivateBestChain without cs_main being held
        // so it should be safe to lock our mutex here without risking a deadlock
        // On the other hand it should be safe for us to access pindex without holding a lock
        // on cs_main because the CBlockIndex objects are dynamically allocated and
        // presumably never deleted.
        if (newBlock == null) {
            return;
        }

        nCachedBlockHeight = newBlock.getHeight();
        log.info("gobject--CGovernanceManager::UpdatedBlockTip -- nCachedBlockHeight: {}\n", nCachedBlockHeight);

        checkPostponedObjects();
    }
    public void requestOrphanObjects() {


        ArrayList vecHashesFiltered = new ArrayList();
        ArrayList vecHashes = new ArrayList();
        lock.lock();
        try {
            mapOrphanVotes.getKeys(vecHashes);
            for (int i = 0; i < vecHashes.size(); ++i) {
                final Sha256Hash nHash = vecHashes.get(i);
                if (!mapObjects.containsKey(nHash)) {
                    vecHashesFiltered.add(nHash);
                }
            }
        } finally {
            lock.unlock();
        }

        log.info("gobject--CGovernanceObject::RequestOrphanObjects -- number objects = {}\n", vecHashesFiltered.size());

        PeerGroup peerGroup = context.peerGroup;
        peerGroup.getLock().lock();
        try {
            for (int i = 0; i < vecHashesFiltered.size(); ++i) {
                final Sha256Hash nHash = vecHashesFiltered.get(i);
                for (int j = 0; j < peerGroup.getConnectedPeers().size(); ++j) {
                    Peer node = peerGroup.getConnectedPeers().get(j);
                    if (node.isMasternode()) {
                        continue;
                    }
                    requestGovernanceObject(node, nHash, false);
                }
            }
        } finally {
            peerGroup.getLock().unlock();
        }
    }
    public void cleanOrphanObjects() {

        lock.lock();
        try {
            final LinkedList>> items = mapOrphanVotes.getItemList();

            long nNow = Utils.currentTimeSeconds();

            Iterator>> it = items.iterator();
            while (it.hasNext()) {
                CacheItem> item = it.next();

                final Pair pairVote = item.value;
                if (pairVote.getSecond() < nNow) {
                    mapOrphanVotes.erase(item.key, item.value);
                }
            }
        } finally {
            lock.unlock();
        }
    }

    public void requestGovernanceObject(Peer pfrom, Sha256Hash nHash, boolean fUseFilter) {
        if (pfrom == null) {
            return;
        }

        log.info("gobject--CGovernanceObject::RequestGovernanceObject -- hash = {} (peer={})", nHash.toString(), pfrom.hashCode());

        if (pfrom.getVersionMessage().clientVersion < GOVERNANCE_FILTER_PROTO_VERSION) {
            pfrom.sendMessage(new GovernanceSyncMessage(params, nHash));
            return;
        }
        BloomFilter filter = null;

        int nVoteCount = 0;
        if (fUseFilter) {
            lock.lock();
            try {
                GovernanceObject pObj = findGovernanceObject(nHash);

                if (pObj != null) {
                    filter = new BloomFilter(params.getGovernanceFilterElements(), GOVERNANCE_FILTER_FP_RATE, new Random().nextInt(999999), BloomFilter.BloomUpdate.UPDATE_ALL);
                    ArrayList vecVotes = pObj.getVoteFile().getVotes();
                    nVoteCount = vecVotes.size();
                    for (int i = 0; i < vecVotes.size(); ++i) {
                        filter.insert(vecVotes.get(i).getHash().getReversedBytes());
                    }
                }
            } finally {
                lock.unlock();
            }
        }

        log.info("gobject--CGovernanceManager::RequestGovernanceObject -- nHash {} nVoteCount {} peer={}", nHash.toString(), nVoteCount, pfrom.hashCode());
        pfrom.sendMessage(fUseFilter ? new GovernanceSyncMessage(params, nHash, filter) :
                new GovernanceSyncMessage(params, nHash));
    }

    public void checkPostponedObjects() {
        if (!context.masternodeSync.isSynced()) {
            return;
        }

        //LOCK2(cs_main, cs);
        lock.lock();
        try {

            // Check postponed proposals
            Iterator> it = mapPostponedObjects.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = it.next();
                final Sha256Hash nHash = entry.getKey();
                GovernanceObject govobj = entry.getValue();

                assert govobj.getObjectType() != GOVERNANCE_OBJECT_WATCHDOG && govobj.getObjectType() != GOVERNANCE_OBJECT_TRIGGER;

                Validity validity = new Validity();
                if (govobj.isCollateralValid(validity)) {
                    if (govobj.isValidLocally(validity, false)) {
                        addGovernanceObject(govobj, null);
                    } else {
                        log.info("CGovernanceManager::CheckPostponedObjects -- {} invalid", nHash.toString());
                    }

                } else if (validity.fMissingConfirmations) {
                    // wait for more confirmations
                    continue;
                }

                // remove processed or invalid object from the queue
                it.remove();
            }


            // Perform additional relays for triggers/watchdogs
            long nNow = Utils.currentTimeSeconds();
            long nSuperblockCycleSeconds = (long)params.getSuperblockCycle() * params.TARGET_SPACING;

            Iterator it2 = setAdditionalRelayObjects.iterator();
            while (it.hasNext()) {

                Sha256Hash hash = it2.next();
                GovernanceObject govobj = mapObjects.get(hash);
                if (govobj != null) {

                    long nTimestamp = govobj.getCreationTime();

                    boolean fValid = (nTimestamp <= nNow + MAX_TIME_FUTURE_DEVIATION) && (nTimestamp >= nNow - 2 * nSuperblockCycleSeconds);
                    boolean fReady = (nTimestamp <= nNow + MAX_TIME_FUTURE_DEVIATION - RELIABLE_PROPAGATION_TIME);

                    if (fValid) {
                        if (fReady) {
                            log.info("CGovernanceManager::CheckPostponedObjects -- additional relay: hash = {}", govobj.getHash().toString());
                            govobj.relay();
                        } else {
                            continue;
                        }
                    }

                } else {
                    log.info("CGovernanceManager::CheckPostponedObjects -- additional relay of unknown object: {}", hash);
                }

                it.remove();
            }
        } finally {
            lock.unlock();
        }
    }

    public int requestGovernanceObjectVotes(Peer pnode) {
        if (pnode.getVersionMessage().clientVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) {
            return -3;
        }
        return requestGovernanceObjectVotes();
    }
    public int requestGovernanceObjectVotes() {
        //C++ TO JAVA CONVERTER NOTE: This static local variable declaration (not allowed in Java) has been moved just prior to the method:
        //	static ClassicMap> mapAskedRecently;
        HashMap> mapAskedRecently = new HashMap>();

        long nNow = Utils.currentTimeSeconds();
        int nTimeout = 60 * 60;
        int nPeersPerHashMax = 3;

        ArrayList vpGovObjsTmp = new ArrayList();
        ArrayList vpGovObjsTriggersTmp = new ArrayList();

        // This should help us to get some idea about an impact this can bring once deployed on mainnet.
        // Testnet is ~40 times smaller in masternode count, but only ~1000 masternodes usually vote,
        // so 1 obj on mainnet == ~10 objs or ~1000 votes on testnet. However we want to test a higher
        // number of votes to make sure it's robust enough, so aim at 2000 votes per masternode per request.
        // On mainnet nMaxObjRequestsPerNode is always set to 1.
        int nMaxObjRequestsPerNode = 1;
        int nProjectedVotes = 2000;
        if (params.getId() != NetworkParameters.ID_MAINNET) {
            nMaxObjRequestsPerNode = Math.max(1, (int)(nProjectedVotes / Math.max(1, context.masternodeListManager.getListAtChainTip().size())));
        }


        //LOCK2(cs_main, cs);
        lock.lock();
        try {

            if (mapObjects.isEmpty()) {
                return -2;
            }

            for (Map.Entry it : mapObjects.entrySet()) {
                if (mapAskedRecently.containsKey(it.getKey())) {
                    Iterator> it1 = mapAskedRecently.get(it.getKey()).entrySet().iterator();
                    while (it1.hasNext()) {
                        if (it1.next().getValue() < nNow) {
                            it1.remove();
                            //mapAskedRecently.get(it.getKey()).remove(it1++);
                        } else {
                        }
                    }
                    if (mapAskedRecently.get(it.getKey()).size() >= nPeersPerHashMax) {
                        continue;
                    }
                }
                if (it.getValue().getObjectType() == GOVERNANCE_OBJECT_TRIGGER) {
                    vpGovObjsTriggersTmp.add((it.getValue()));
                } else {
                    vpGovObjsTmp.add((it.getValue()));
                }
            }
        } finally {
            lock.unlock();
        }


        log.info("gobject--CGovernanceManager::RequestGovernanceObjectVotes -- start: vpGovObjsTriggersTmp {} vpGovObjsTmp {} mapAskedRecently {}", vpGovObjsTriggersTmp.size(), vpGovObjsTmp.size(), mapAskedRecently.size());

        Random insecureRand = new Random();
        // shuffle pointers
        Collections.shuffle(vpGovObjsTriggersTmp, insecureRand);
        Collections.shuffle(vpGovObjsTmp, insecureRand);

        for (int i = 0; i < nMaxObjRequestsPerNode; ++i) {
            Sha256Hash nHashGovobj = Sha256Hash.ZERO_HASH;

            // ask for triggers first
            if (vpGovObjsTriggersTmp.size() > 0) {
                nHashGovobj = vpGovObjsTriggersTmp.get(vpGovObjsTriggersTmp.size() - 1).getHash();
            } else {
                if (vpGovObjsTmp.isEmpty()) {
                    break;
                }
                nHashGovobj = vpGovObjsTmp.get(vpGovObjsTmp.size() - 1).getHash();
            }
            boolean fAsked = false;
            for (Peer pnode : context.peerGroup.getConnectedPeers()) {
                // Only use regular peers, don't try to ask from outbound "masternode" connections -
                // they stay connected for a short period of time and it's possible that we won't get everything we should.
                // Only use outbound connections - inbound connection could be a "masternode" connection
                // initiated from another node, so skip it too.
                if (pnode.isMasternode()) {
                    continue;
                }
                // only use up to date peers
                if (pnode.getVersionMessage().clientVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) {
                    continue;
                }
                // stop early to prevent setAskFor overflow
                int nProjectedSize = pnode.setAskFor.size() + nProjectedVotes;
                if (nProjectedSize > Peer.SETASKFOR_MAX_SZ / 2) {
                    continue;
                }
                // to early to ask the same node
                HashMap map = mapAskedRecently.get(nHashGovobj);
                if (map != null && map.containsKey(pnode.getAddress().getAddr())) {
                    continue;
                }

                requestGovernanceObject(pnode, nHashGovobj, true);
                if(map == null) {
                    map = new HashMap();
                }
                map.put(pnode.getAddress().getAddr(), nNow + nTimeout);
                mapAskedRecently.put(nHashGovobj, map);

                fAsked = true;
                // stop loop if max number of peers per obj was asked
                if (mapAskedRecently.get(nHashGovobj).size() >= nPeersPerHashMax) {
                    break;
                }
            }
            // NOTE: this should match `if` above (the one before `while`)
            if (vpGovObjsTriggersTmp.size() > 0) {
                vpGovObjsTriggersTmp.remove(vpGovObjsTriggersTmp.size() - 1);
            } else {
                vpGovObjsTmp.remove(vpGovObjsTmp.size() - 1);
            }
            if (!fAsked) {
                i--;
            }
        }
        log.info("gobject--CGovernanceManager::RequestGovernanceObjectVotes -- end: vpGovObjsTriggersTmp {} vpGovObjsTmp {} mapAskedRecently {}", vpGovObjsTriggersTmp.size(), vpGovObjsTmp.size(), mapAskedRecently.size());

        return (int)(vpGovObjsTriggersTmp.size() + vpGovObjsTmp.size());
    }

    public boolean processVoteAndRelay(GovernanceVote vote, GovernanceException exception) {
        boolean fOK = processVote(null, vote, exception);
        if(fOK) {
            vote.relay();
        }
        return fOK;
    }

    public void processGovernanceSyncMessage(Peer peer, GovernanceSyncMessage message) {
        if(peer.getVersionMessage().clientVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) {
            log.warn("gobject--MNGOVERNANCESYNC -- peer={} using obsolete version {}", peer.hashCode(), peer.getVersionMessage().clientVersion);
            peer.sendMessage(new RejectMessage(params, RejectMessage.RejectCode.OBSOLETE, Sha256Hash.ZERO_HASH, "obsolete-peer",
                    String.format("Version must be %d or greater", MIN_GOVERNANCE_PEER_PROTO_VERSION)));
            return;
        }

        // Ignore such requests until we are fully synced.
        // We could start processing this after masternode list is synced
        // but this is a heavy one so it's better to finish sync first.
        if (!context.masternodeSync.isSynced()) return;

        if(message.prop.isZero()) {
            syncAll(peer);
        } else {
            syncSingleObjAndItsVotes(peer, message.prop, message.bloomFilter);
        }
        log.info("gobject--MNGOVERNANCESYNC -- syncing governance objects to our peer at {}", peer.getAddress());
    }

    public void syncSingleObjAndItsVotes(Peer pnode, Sha256Hash nProp, BloomFilter filter) {
        // do not provide any data until our node is synced
        if (!context.masternodeSync.isSynced()) {
            return;
        }

        int nVoteCount = 0;

        // SYNC GOVERNANCE OBJECTS WITH OTHER CLIENT

        log.info("gobject--CGovernanceManager::syncSingleObjAndItsVotes -- syncing single object to peer={}, nProp = {}", pnode.hashCode(), nProp.toString());

        lock.lock();
        try {

            // single valid object and its valid votes
            GovernanceObject govobj = mapObjects.get(nProp);
            if (govobj == null) {
                log.info("gobject--CGovernanceManager:: -- no matching object for hash {}, peer={}", nProp.toString(), pnode.hashCode());
                return;
            }
            String strHash = nProp.toString();

            log.info("gobject--CGovernanceManager:: -- attempting to sync govobj: {}, peer={}", strHash, pnode.hashCode());

            if (govobj.isSetCachedDelete() || govobj.isSetExpired()) {
                log.info("CGovernanceManager:: -- not syncing deleted/expired govobj: {}, peer={}", strHash, pnode.hashCode());
                return;
            }

            // Push the govobj inventory message over to the other client
            log.info("gobject--CGovernanceManager:: -- syncing govobj: {}, peer={}\n", strHash, pnode.hashCode());
            pnode.pushInventory(new InventoryItem(InventoryItem.Type.GovernanceObject, nProp));

            //C++ TO JAVA CONVERTER TODO TASK: There is no equivalent to implicit typing in Java unless the Java 10 inferred typing option is selected:
            GovernanceObjectVoteFile fileVotes = govobj.getVoteFile();

            //C++ TO JAVA CONVERTER TODO TASK: There is no equivalent to implicit typing in Java unless the Java 10 inferred typing option is selected:
            for (GovernanceVote vote : fileVotes.getVotes()) {
                Sha256Hash nVoteHash = vote.getHash();
                if (filter.contains(nVoteHash.getReversedBytes()) || !vote.isValid(true)) {
                    continue;
                }
                pnode.pushInventory(new InventoryItem(InventoryItem.Type.GovernanceObjectVote, nVoteHash));
                ++nVoteCount;
            }

            pnode.sendMessage(new SyncStatusCount(MasternodeSync.MASTERNODE_SYNC_GOVOBJ, 1));
            pnode.sendMessage(new SyncStatusCount(MasternodeSync.MASTERNODE_SYNC_GOVOBJ_VOTE, nVoteCount));
            log.info("CGovernanceManager:: -- sent 1 object and {} votes to peer={}", nVoteCount, pnode.hashCode());
        } finally {
            lock.unlock();
        }
    }

    public void syncAll(Peer pnode) {
        // do not provide any data until our node is synced
        if (!context.masternodeSync.isSynced()) {
            return;
        }

        if (context.netFullfilledRequestManager.hasFulfilledRequest(pnode.getAddress(), "govsync")) {
            //LOCK(cs_main);
            // Asking for the whole list multiple times in a short period of time is no good
            log.info("gobject--CGovernanceManager:: -- peer already asked me for the list");
            //Misbehaving(pnode.GetId(), 20);
            return;
        }
        context.netFullfilledRequestManager.addFulfilledRequest(pnode.getAddress(), "govsync");

        int nObjCount = 0;
        int nVoteCount = 0;

        // SYNC GOVERNANCE OBJECTS WITH OTHER CLIENT

        log.info("gobject--CGovernanceManager:: -- syncing all objects to peer={}", pnode.hashCode());

        lock.lock();
        try {

            // all valid objects, no votes
            for (Map.Entry it : mapObjects.entrySet()) {
                final GovernanceObject govobj = it.getValue();
                String strHash = it.getKey().toString();

                log.info("gobject--CGovernanceManager:: -- attempting to sync govobj: {}, peer={}\n", strHash, pnode.hashCode());

                if (govobj.isSetCachedDelete() || govobj.isSetExpired()) {
                    log.info("CGovernanceManager:: -- not syncing deleted/expired govobj: {}, peer={}\n", strHash, pnode.hashCode());
                    continue;
                }

                // Push the inventory budget proposal message over to the other client
                log.info("gobject--CGovernanceManager:: -- syncing govobj: {}, peer={}", strHash, pnode.hashCode());
                pnode.pushInventory(new InventoryItem(InventoryItem.Type.GovernanceObject, it.getKey()));
                ++nObjCount;
            }

            pnode.sendMessage(new SyncStatusCount(MasternodeSync.MASTERNODE_SYNC_GOVOBJ, nObjCount));
            pnode.sendMessage(new SyncStatusCount(MasternodeSync.MASTERNODE_SYNC_GOVOBJ_VOTE, nVoteCount));
            log.info("CGovernanceManager:: -- sent {} objects and {} votes to peer={}", nObjCount, nVoteCount, pnode.hashCode());
        } finally {
            lock.unlock();
        }
    }

    public boolean haveVoteForHash(Sha256Hash voteHash)
    {
        lock.lock();
        try {

            CacheItem item = mapVoteToObject.get(voteHash);
            if(item == null)
                return false;
            return item.value.getVoteFile().hasVote(voteHash);
        } finally {
            lock.unlock();
        }
    }

    public GovernanceVote getVoteForHash(Sha256Hash voteHash)
    {
        lock.lock();
        try {

            CacheItem item = mapVoteToObject.get(voteHash);
            if(item == null)
                return null;
            return item.value.getVoteFile().getVote(voteHash);
        } finally {
            lock.unlock();
        }
    }

    public ArrayList getAllNewerThan(long nMoreThanTime) {
        lock.lock();
        try {
            ArrayList vGovObjs = new ArrayList();

            Iterator> it = mapObjects.entrySet().iterator();
            while (it.hasNext()) {
                // IF THIS OBJECT IS OLDER THAN TIME, CONTINUE
                Map.Entry entry = it.next();

                if (entry.getValue().getCreationTime() < nMoreThanTime) {
                    continue;
                }

                // ADD GOVERNANCE OBJECT TO LIST

                GovernanceObject pGovObj = entry.getValue();
                vGovObjs.add(pGovObj);
            }

            return vGovObjs;
        } finally {
            lock.unlock();
        }
    }
    private final CopyOnWriteArrayList> voteConfidenceListeners
            = new CopyOnWriteArrayList>();

    /**
     * Adds an event listener object. Methods on this object are called when confidence
     * of a vote changes. Runs the listener methods in the user thread.
     */
    public void addVoteConfidenceEventListener(GovernanceVoteConfidenceEventListener listener) {
        addVoteConfidenceEventListener(Threading.USER_THREAD, listener);
    }

    /**
     * Adds an event listener object. Methods on this object are called when confidence
     * of a vote changes. The listener is executed by the given executor.
     */
    public void addVoteConfidenceEventListener(Executor executor, GovernanceVoteConfidenceEventListener listener) {
        // This is thread safe, so we don't need to take the lock.
        voteConfidenceListeners.add(new ListenerRegistration(listener, executor));
    }

    /**
     * Removes the given event listener object. Returns true if the listener was removed, false if that listener
     * was never added.
     */
    public boolean removeVoteConfidenceEventListener(GovernanceVoteConfidenceEventListener listener) {
        return ListenerRegistration.removeFromList(listener, voteConfidenceListeners);
    }

    private void queueOnTransactionConfidenceChanged(final GovernanceVote vote) {
        checkState(lock.isHeldByCurrentThread());
        for (final ListenerRegistration registration : voteConfidenceListeners) {
            if (registration.executor == Threading.SAME_THREAD) {
                registration.listener.onVoteConfidenceChanged(vote);
            } else {
                registration.executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        registration.listener.onVoteConfidenceChanged(vote);
                    }
                });
            }
        }
    }

    private transient CopyOnWriteArrayList> governanceObjectAddedListeners;

    /**
     * Adds an event listener object. Methods on this object are called when something interesting happens,
     * like receiving money. Runs the listener methods in the user thread.
     */
    public void addGovernanceObjectAddedListener(GovernanceObjectAddedEventListener listener) {
        addGovernanceObjectAddedListener(listener, Threading.USER_THREAD);
    }

    /**
     * Adds an event listener object. Methods on this object are called when something interesting happens,
     * like receiving money. The listener is executed by the given executor.
     */
    public void addGovernanceObjectAddedListener(GovernanceObjectAddedEventListener listener, Executor executor) {
        // This is thread safe, so we don't need to take the lock.
        governanceObjectAddedListeners.add(new ListenerRegistration(listener, executor));
        //keychain.addEventListener(listener, executor);
    }

    /**
     * Removes the given event listener object. Returns true if the listener was removed, false if that listener
     * was never added.
     */
    public boolean removeGovernanceObjectAddedListener(GovernanceObjectAddedEventListener listener) {
        return ListenerRegistration.removeFromList(listener, governanceObjectAddedListeners);
    }

    private void queueOnGovernanceObjectAdded(final Sha256Hash nHash, final GovernanceObject object) {
        checkState(lock.isHeldByCurrentThread());
        for (final ListenerRegistration registration : governanceObjectAddedListeners) {
            if (registration.executor == Threading.SAME_THREAD) {
                registration.listener.onGovernanceObjectAdded(nHash, object);
            } else {
                registration.executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        registration.listener.onGovernanceObjectAdded(nHash, object);
                    }
                });
            }
        }
    }

    @Override
    public void close() {

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy