text:
// Copyright (c) 2016-2020 The Gulden developers
// Authored by: Willem de Jonge (willem@isnapp.nl)
// Modifications by: Malcolm MacLeod (mmacleod@gmx.com)
// Distributed under the GULDEN software license, see the accompanying
// file COPYING

#include "witness_operations.h"

#include <stdexcept>
#include <numeric>
#include <algorithm>
#include <boost/uuid/uuid_io.hpp>
#include <cmath>
#include <inttypes.h>
#include "wallet.h"
#include "validation/witnessvalidation.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "Gulden/util.h"
#include "coincontrol.h"
#include "net.h"
#include "alert.h"
#include "utilmoneystr.h"
#include "wallet.h"

static witnessOutputsInfoVector getCurrentOutputsForWitnessAccount(CAccount* forAccount)
{
    std::map<COutPoint, Coin> allWitnessCoins;
    if (!getAllUnspentWitnessCoins(chainActive, Params(), chainActive.Tip(), allWitnessCoins))
        throw std::runtime_error("Failed to enumerate all witness coins.");

    witnessOutputsInfoVector matchedOutputs;
    for (const auto& [outpoint, coin] : allWitnessCoins)
    {
        if (IsMine(*forAccount, coin.out))
        {
            matchedOutputs.push_back(std::tuple(coin.out, coin.nHeight, coin.nTxIndex, outpoint));
        }
    }
    return matchedOutputs;
}

void extendwitnessaddresshelper(CAccount* fundingAccount, witnessOutputsInfoVector unspentWitnessOutputs, CWallet* pwallet, CAmount requestedAmount, uint64_t requestedLockPeriodInBlocks, std::string* pTxid, CAmount* pFee)
{
    AssertLockHeld(cs_main);
    AssertLockHeld(pwallet->cs_wallet);

    if (unspentWitnessOutputs.size() > 1)
        throw witness_error(witness::RPC_INVALID_ADDRESS_OR_KEY, strprintf("Address has too many witness outputs cannot extend "));

    if (requestedAmount < (gMinimumWitnessAmount*COIN))
        throw witness_error(witness::RPC_TYPE_ERROR, strprintf("Witness amount must be %d or larger", gMinimumWitnessAmount));

    if (requestedLockPeriodInBlocks > MaximumWitnessLockLength())
        throw witness_error(witness::RPC_INVALID_PARAMETER, "Maximum lock period of 3 years exceeded.");

    if (requestedLockPeriodInBlocks < MinimumWitnessLockLength())
        throw witness_error(witness::RPC_INVALID_PARAMETER, "Minimum lock period of 1 month exceeded.");

    // Add a small buffer to give us time to enter the blockchain
    if (requestedLockPeriodInBlocks == MinimumWitnessLockLength())
        requestedLockPeriodInBlocks += 50;

    // Check for immaturity
    const auto& [currentWitnessTxOut, currentWitnessHeight, currentWitnessTxIndex, currentWitnessOutpoint] = unspentWitnessOutputs[0];
    //fixme: (PHASE5) - Minor code cleanup.
    //This check should go through the actual chain maturity stuff (via wtx) and not calculate maturity directly.
    if (chainActive.Tip()->nHeight - currentWitnessHeight < (uint64_t)(COINBASE_MATURITY))
        throw witness_error(witness::RPC_MISC_ERROR, "Cannot perform operation on immature transaction, please wait for transaction to mature and try again");

    // Check type (can't extend script type, must witness once or renew after phase 4 activated)
    if (currentWitnessTxOut.GetType() != CTxOutType::PoW2WitnessOutput)
        throw witness_error(witness::RPC_TYPE_ERROR, "Witness has to be type POW2WITNESS");

    // Calculate existing lock period
    CTxOutPoW2Witness currentWitnessDetails;
    GetPow2WitnessOutput(currentWitnessTxOut, currentWitnessDetails);
    uint64_t remainingLockDurationInBlocks = GetPoW2RemainingLockLengthInBlocks(currentWitnessDetails.lockUntilBlock, chainActive.Tip()->nHeight);
    if (remainingLockDurationInBlocks == 0)
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, "PoW² witness has already unlocked so cannot be extended.");
    }

    if (requestedLockPeriodInBlocks < remainingLockDurationInBlocks)
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, strprintf("New lock period [%d] does not exceed remaining lock period [%d]", requestedLockPeriodInBlocks, remainingLockDurationInBlocks));
    }

    if (requestedAmount < currentWitnessTxOut.nValue)
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, strprintf("New amount [%d] is smaller than current amount [%d]", requestedAmount, currentWitnessTxOut.nValue));
    }

    // Enforce minimum weight
    int64_t newWeight = GetPoW2RawWeightForAmount(requestedAmount, requestedLockPeriodInBlocks);
    if (newWeight < gMinimumWitnessWeight)
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, "PoW² witness has insufficient weight.");
    }

    // Enforce new weight > old weight
    uint64_t notUsed1, notUsed2;
    int64_t oldWeight = GetPoW2RawWeightForAmount(currentWitnessTxOut.nValue, GetPoW2LockLengthInBlocksFromOutput(currentWitnessTxOut, currentWitnessHeight, notUsed1, notUsed2));
    if (newWeight <= oldWeight)
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, strprintf("New weight [%d] does not exceed old weight [%d].", newWeight, oldWeight));
    }

    // Find the account for this address
    CAccount* witnessAccount = pwallet->FindBestWitnessAccountForTransaction(currentWitnessTxOut);

    if (!witnessAccount)
        throw witness_error(witness::RPC_MISC_ERROR, "Could not locate account for address");
    if ((!witnessAccount->IsPoW2Witness()) || witnessAccount->IsFixedKeyPool())
    {
        throw witness_error(witness::RPC_MISC_ERROR, "Cannot extend a witness-only account as spend key is required to do this.");
    }

    // Finally attempt to create and send the witness transaction.
    CReserveKeyOrScript reservekey(pwallet, fundingAccount, KEYCHAIN_CHANGE);
    std::string reasonForFail;
    CAmount transactionFee;
    CMutableTransaction extendWitnessTransaction(CTransaction::SEGSIG_ACTIVATION_VERSION);
    {
        // Add the existing witness output as an input
        pwallet->AddTxInput(extendWitnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, currentWitnessHeight, currentWitnessTxIndex), false);

        // Add new witness output
        CTxOut extendedWitnessTxOutput;
        extendedWitnessTxOutput.SetType(CTxOutType::PoW2WitnessOutput);
        // As we are extending the address we reset the "lock from" and we set the "lock until" everything else except the value remains unchanged.
        extendedWitnessTxOutput.output.witnessDetails.lockFromBlock = 0;
        extendedWitnessTxOutput.output.witnessDetails.lockUntilBlock = chainActive.Tip()->nHeight + requestedLockPeriodInBlocks;
        extendedWitnessTxOutput.output.witnessDetails.spendingKeyID = currentWitnessDetails.spendingKeyID;
        extendedWitnessTxOutput.output.witnessDetails.witnessKeyID = currentWitnessDetails.witnessKeyID;
        extendedWitnessTxOutput.output.witnessDetails.failCount = currentWitnessDetails.failCount;
        extendedWitnessTxOutput.output.witnessDetails.actionNonce = currentWitnessDetails.actionNonce+1;
        extendedWitnessTxOutput.nValue = requestedAmount;
        extendWitnessTransaction.vout.push_back(extendedWitnessTxOutput);

        // Fund the additional amount in the transaction (including fees)
        int changeOutputPosition = 1;
        std::set<int> subtractFeeFromOutputs; // Empty - we don't subtract fee from outputs
        CCoinControl coincontrol;
        if (!pwallet->FundTransaction(fundingAccount, extendWitnessTransaction, transactionFee, changeOutputPosition, reasonForFail, false, subtractFeeFromOutputs, coincontrol, reservekey))
        {
            throw witness_error(witness::RPC_MISC_ERROR, strprintf("Failed to fund transaction [%s]", reasonForFail.c_str()));
        }
    }

    uint256 extendTransactionHash;
    {
        LOCK2(cs_main, pwallet->cs_wallet);
        if (!pwallet->SignAndSubmitTransaction(reservekey, extendWitnessTransaction, reasonForFail, &extendTransactionHash))
        {
            throw witness_error(witness::RPC_MISC_ERROR, strprintf("Failed to commit transaction [%s]", reasonForFail.c_str()));
        }
    }

    // Set result parameters
    if (pTxid != nullptr)
        *pTxid = extendTransactionHash.GetHex();
    if (pFee != nullptr)
        *pFee = transactionFee;
}

void fundwitnessaccount(CWallet* pwallet, CAccount* fundingAccount, CAccount* witnessAccount, CAmount amount, uint64_t requestedPeriodInBlocks, bool fAllowMultiple, std::string* pTxid, CAmount* pFee)
{
    if (!IsPow2Phase2Active(chainActive.Tip()))
    {
        throw witness_error(witness::RPC_MISC_ERROR, strprintf("Can't create witness accounts before phase 2 activates"));
    }

    std::vector<CAmount> amounts;
    LOCK(cs_main);

    // For the sake of testnet we turn off 'splitting' for very short lock periods as it was triggering some testnet specific 'short period' bugs that aren't worth fixing for mainnet (where they can't occur)
    if (IsSegSigEnabled(chainActive.TipPrev()) && requestedPeriodInBlocks > 1000)
    {
        CGetWitnessInfo witnessInfo = GetWitnessInfoWrapper();
        amounts = optimalWitnessDistribution(amount, requestedPeriodInBlocks, witnessInfo.nTotalWeightEligibleRaw);
    }
    else
        amounts = { amount };

    fundwitnessaccount(pwallet, fundingAccount, witnessAccount, amounts, requestedPeriodInBlocks, fAllowMultiple, pTxid, pFee);
}

void fundwitnessaccount(CWallet* pwallet, CAccount* fundingAccount, CAccount* witnessAccount, const std::vector<CAmount>& amounts, uint64_t requestedPeriodInBlocks, bool fAllowMultiple, std::string* pTxid, CAmount* pFee)
{
    if (!IsPow2Phase2Active(chainActive.Tip()))
    {
        throw witness_error(witness::RPC_MISC_ERROR, strprintf("Can't create witness accounts before phase 2 activates"));
    }

    if (pwallet == nullptr || witnessAccount == nullptr || fundingAccount == nullptr)
        throw witness_error(witness::RPC_INVALID_PARAMETER, "Require non-null pwallet, fundingAccount, witnessAccount");

    LOCK2(cs_main, pwallet->cs_wallet);

    if (!witnessAccount->IsPoW2Witness())
        throw witness_error(witness::RPC_MISC_ERROR, strprintf("Specified account is not a witness account [%s].", boost::uuids::to_string(witnessAccount->getUUID())));
    if (witnessAccount->m_Type == WitnessOnlyWitnessAccount || !witnessAccount->IsHD())
        throw witness_error(witness::RPC_MISC_ERROR, strprintf("Cannot fund a witness only witness account [%s].", boost::uuids::to_string(witnessAccount->getUUID())));

    const auto& unspentWitnessOutputs = getCurrentOutputsForWitnessAccount(witnessAccount);
    if (unspentWitnessOutputs.size() > 0)
    {
        if (!fAllowMultiple)
            throw std::runtime_error("Account already has an active funded witness address. Perhaps you intended to 'extend' it? See: 'help extendwitnessaccount'");
    }

    if (std::any_of(amounts.begin(), amounts.end(), [](const auto& amount){ return amount < (gMinimumWitnessAmount*COIN); }))
        throw witness_error(witness::RPC_TYPE_ERROR, strprintf("Witness amount must be %d or larger", gMinimumWitnessAmount));

    if (requestedPeriodInBlocks == 0)
        throw witness_error(witness::RPC_TYPE_ERROR, "Invalid number passed for lock period.");

    if (requestedPeriodInBlocks > MaximumWitnessLockLength())
        throw witness_error(witness::RPC_INVALID_PARAMETER, "Maximum lock period of 3 years exceeded.");

    if (requestedPeriodInBlocks < MinimumWitnessLockLength())
        throw witness_error(witness::RPC_INVALID_PARAMETER, "Minimum lock period of 1 month exceeded.");

    // Add a small buffer to give us time to enter the blockchain
    if (requestedPeriodInBlocks == MinimumWitnessLockLength())
        requestedPeriodInBlocks += 50;

    // Enforce minimum weight for each amount
    if (std::any_of(amounts.begin(), amounts.end(), [&](const auto& amount){ return GetPoW2RawWeightForAmount(amount, requestedPeriodInBlocks) < gMinimumWitnessWeight; }))
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, "PoW² witness has insufficient weight.");
    }

    // Finally attempt to create and send the witness transaction.
    CPoW2WitnessDestination destinationPoW2Witness;
    destinationPoW2Witness.lockFromBlock = 0;
    destinationPoW2Witness.lockUntilBlock = chainActive.Tip()->nHeight + requestedPeriodInBlocks;
    destinationPoW2Witness.failCount = 0;
    destinationPoW2Witness.actionNonce = 0;
    {
        CReserveKeyOrScript keyWitness(pactiveWallet, witnessAccount, KEYCHAIN_WITNESS);
        CPubKey pubWitnessKey;
        if (!keyWitness.GetReservedKey(pubWitnessKey))
            throw witness_error(witness::RPC_INVALID_ADDRESS_OR_KEY, "Error allocating witness key for witness account.");

        //We delibritely return the key here, so that if we fail we won't leak the key.
        //The key will be marked as used when the transaction is accepted anyway.
        keyWitness.ReturnKey();
        destinationPoW2Witness.witnessKey = pubWitnessKey.GetID();
    }
    {
        //Code should be refactored to only call 'KeepKey' -after- success, a bit tricky to get there though.
        CReserveKeyOrScript keySpending(pactiveWallet, witnessAccount, KEYCHAIN_SPENDING);
        CPubKey pubSpendingKey;
        if (!keySpending.GetReservedKey(pubSpendingKey))
            throw witness_error(witness::RPC_INVALID_ADDRESS_OR_KEY, "Error allocating spending key for witness account.");

        //We delibritely return the key here, so that if we fail we won't leak the key.
        //The key will be marked as used when the transaction is accepted anyway.
        keySpending.ReturnKey();
        destinationPoW2Witness.spendingKey = pubSpendingKey.GetID();
    }

    CAmount transactionFee;
    std::string strError;
    std::vector<CRecipient> vecSend;
    int nChangePosRet = -1;

    std::for_each(amounts.begin(), amounts.end(), [&](const CAmount& amount){
        CRecipient recipient = ( IsSegSigEnabled(chainActive.TipPrev()) ? ( CRecipient(GetPoW2WitnessOutputFromWitnessDestination(destinationPoW2Witness), amount, false) ) : ( CRecipient(GetScriptForDestination(destinationPoW2Witness), amount, false) ) ) ;
        if (!IsSegSigEnabled(chainActive.TipPrev()))
        {
            // We have to copy this anyway even though we are using a CSCript as later code depends on it to grab the witness key id.
            recipient.witnessDetails.witnessKeyID = destinationPoW2Witness.witnessKey;
        }

        //NB! Setting this is -super- important, if we don't then encrypted wallets may fail to witness.
        recipient.witnessForAccount = witnessAccount;
        vecSend.push_back(recipient);
    });

    CWalletTx wtx;
    CReserveKeyOrScript reservekey(pwallet, fundingAccount, KEYCHAIN_CHANGE);
    if (!pwallet->CreateTransaction(fundingAccount, vecSend, wtx, reservekey, transactionFee, nChangePosRet, strError))
    {
        throw witness_error(witness::RPC_WALLET_ERROR, strError);
    }

    CValidationState state;
    if (!pwallet->CommitTransaction(wtx, reservekey, g_connman.get(), state))
    {
        strError = strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason());
        throw witness_error(witness::RPC_WALLET_ERROR, strError);
    }

    // Set result parameters
    if (pTxid != nullptr)
        *pTxid = wtx.GetHash().GetHex();
    if (pFee != nullptr)
        *pFee = transactionFee;
}

void upgradewitnessaccount(CWallet* pwallet, CAccount* fundingAccount, CAccount* witnessAccount, std::string* pTxid, CAmount* pFee)
{
    if (pwallet == nullptr || witnessAccount == nullptr || fundingAccount == nullptr)
        throw witness_error(witness::RPC_INVALID_PARAMETER, "Require non-null pwallet, fundingAccount, witnessAccount");

    LOCK2(cs_main, pwallet->cs_wallet);

    if (!IsSegSigEnabled(chainActive.TipPrev()))
        throw std::runtime_error("Cannot use this command before segsig activates");

    if (pwallet->IsLocked()) {
        throw std::runtime_error("Wallet locked");
    }

    if ((!witnessAccount->IsPoW2Witness()) || witnessAccount->IsFixedKeyPool())
    {
        throw witness_error(witness::RPC_MISC_ERROR, "Cannot split a witness-only account as spend key is required to do this.");
    }

    std::string strError;
    CMutableTransaction tx(CURRENT_TX_VERSION_POW2);
    CReserveKeyOrScript changeReserveKey(pactiveWallet, fundingAccount, KEYCHAIN_EXTERNAL);
    CAmount transactionFee;
    pwallet->PrepareUpgradeWitnessAccountTransaction(fundingAccount, witnessAccount, changeReserveKey, tx, transactionFee);

    uint256 upgradeTransactionHash;
    if (!pwallet->SignAndSubmitTransaction(changeReserveKey, tx, strError, &upgradeTransactionHash, SignType::WitnessUpdate))
    {
        throw std::runtime_error(strprintf("Failed to sign transaction [%s]", strError.c_str()));
    }

    // Set result parameters
    if (pTxid != nullptr)
        *pTxid = upgradeTransactionHash.GetHex();
    if (pFee != nullptr)
        *pFee = transactionFee;
}

void rotatewitnessaddresshelper(CAccount* fundingAccount, witnessOutputsInfoVector unspentWitnessOutputs, CWallet* pwallet, std::string* pTxid, CAmount* pFee)
{
    if (unspentWitnessOutputs.size() == 0)
        throw witness_error(witness::RPC_INVALID_ADDRESS_OR_KEY, strprintf("Too few witness outputs cannot rotate."));

    // Check for immaturity
    for (const auto& [currentWitnessTxOut, currentWitnessHeight, currentWitnessTxIndex, currentWitnessOutpoint]: unspentWitnessOutputs)
    {
        (unused) currentWitnessTxOut;
        (unused) currentWitnessOutpoint;
        (unused) currentWitnessTxIndex;
        //fixme: (PHASE5) - Minor code cleanup.
        //This check should go through the actual chain maturity stuff (via wtx) and not calculate maturity directly.
        if (chainActive.Tip()->nHeight - currentWitnessHeight < (uint64_t)(COINBASE_MATURITY))
            throw witness_error(witness::RPC_MISC_ERROR, "Cannot perform operation on immature transaction, please wait for transaction to mature and try again");
    }

    // Find the account for this address
    CAccount* witnessAccount = nullptr;
    {
        CTxOutPoW2Witness currentWitnessDetails;
        const auto& currentWitnessTxOut = std::get<0>(unspentWitnessOutputs[0]);
        GetPow2WitnessOutput(currentWitnessTxOut, currentWitnessDetails);
        witnessAccount = pwallet->FindBestWitnessAccountForTransaction(currentWitnessTxOut);
    }
    if (!witnessAccount)
    {
        throw witness_error(witness::RPC_MISC_ERROR, "Could not locate account");
    }
    if ((!witnessAccount->IsPoW2Witness()) || witnessAccount->IsFixedKeyPool())
    {
        throw witness_error(witness::RPC_MISC_ERROR, "Cannot rotate a witness-only account as spend key is required to do this.");
    }

    // Finally attempt to create and send the witness transaction.
    CReserveKeyOrScript reservekey(pwallet, fundingAccount, KEYCHAIN_CHANGE);
    std::string reasonForFail;
    CAmount transactionFee;
    CMutableTransaction rotateWitnessTransaction(CTransaction::SEGSIG_ACTIVATION_VERSION);
    CPubKey pubWitnessKey;
    {
        // Get new witness key
        CReserveKeyOrScript keyWitness(pactiveWallet, witnessAccount, KEYCHAIN_WITNESS);
        if (!keyWitness.GetReservedKey(pubWitnessKey))
        {
            throw witness_error(witness::RPC_INVALID_ADDRESS_OR_KEY, "Error allocating witness key for witness account.");
        }

        //We delibritely return the key here, so that if we fail we won't leak the key.
        //The key will be marked as used when the transaction is accepted anyway.
        keyWitness.ReturnKey();
    }

    // Add all existing outputs as inputs and new outputs
    for (const auto& [currentWitnessTxOut, currentWitnessHeight, currentWitnessTxIndex, currentWitnessOutpoint]: unspentWitnessOutputs)
    {
        // Add input
        pwallet->AddTxInput(rotateWitnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, currentWitnessHeight, currentWitnessTxIndex), false);

        // Get witness details
        CTxOutPoW2Witness currentWitnessDetails;
        if (!GetPow2WitnessOutput(currentWitnessTxOut, currentWitnessDetails))
            throw witness_error(witness::RPC_MISC_ERROR, "Failure extracting witness details.");

        // Add rotated output
        CTxOut rotatedWitnessTxOutput;
        rotatedWitnessTxOutput.SetType(CTxOutType::PoW2WitnessOutput);
        rotatedWitnessTxOutput.output.witnessDetails.lockFromBlock = currentWitnessDetails.lockFromBlock;
        rotatedWitnessTxOutput.output.witnessDetails.lockUntilBlock = currentWitnessDetails.lockUntilBlock;
        rotatedWitnessTxOutput.output.witnessDetails.spendingKeyID = currentWitnessDetails.spendingKeyID;
        rotatedWitnessTxOutput.output.witnessDetails.witnessKeyID = pubWitnessKey.GetID();
        rotatedWitnessTxOutput.output.witnessDetails.failCount = currentWitnessDetails.failCount;
        rotatedWitnessTxOutput.output.witnessDetails.actionNonce = currentWitnessDetails.actionNonce+1;
        rotatedWitnessTxOutput.nValue = currentWitnessTxOut.nValue;
        rotateWitnessTransaction.vout.push_back(rotatedWitnessTxOutput);
    }

    // Fund the additional amount in the transaction (including fees)
    int changeOutputPosition = 1;
    std::set<int> subtractFeeFromOutputs; // Empty - we don't subtract fee from outputs
    CCoinControl coincontrol;
    if (!pwallet->FundTransaction(fundingAccount, rotateWitnessTransaction, transactionFee, changeOutputPosition, reasonForFail, false, subtractFeeFromOutputs, coincontrol, reservekey))
    {
        throw witness_error(witness::RPC_MISC_ERROR, strprintf("Failed to fund transaction [%s]", reasonForFail.c_str()));
    }

    //fixme: (PHASE5) - Minor code cleanup
    //Improve this a bit, we should make this 'atomic'
    //i.e. only perform the action that can't be rolled back (adding the key to the account) if the entire thing succeeds
    //So only after the signing of the transaction is a success
    //Also (low) this shares common code with CreateTransaction() - so it should be factored out into a common helper that both can use.
    CKey privWitnessKey;
    if (!witnessAccount->GetKey(pubWitnessKey.GetID(), privWitnessKey))
    {
        reasonForFail = strprintf("Wallet error, failed to retrieve private witness key.");
        throw witness_error(witness::RPC_MISC_ERROR, strprintf("Failed to fund transaction [%s]", reasonForFail.c_str()));
    }
    if (!pwallet->AddKeyPubKey(privWitnessKey, privWitnessKey.GetPubKey(), *witnessAccount, KEYCHAIN_WITNESS))
    {
        reasonForFail = strprintf("Wallet error, failed to store witness key.");
        throw witness_error(witness::RPC_MISC_ERROR, strprintf("Failed to fund transaction [%s]", reasonForFail.c_str()));
    }

    uint256 rotateTransactionHash;
    {
        LOCK2(cs_main, pactiveWallet->cs_wallet);
        if (!pwallet->SignAndSubmitTransaction(reservekey, rotateWitnessTransaction, reasonForFail, &rotateTransactionHash))
        {
            throw witness_error(witness::RPC_MISC_ERROR, strprintf("Failed to commit transaction [%s]", reasonForFail.c_str()));
        }
    }

    // Set result parameters
    if (pTxid != nullptr)
        *pTxid = rotateTransactionHash.GetHex();
    if (pFee != nullptr)
        *pFee = transactionFee;
}

void rotatewitnessaccount(CWallet* pwallet, CAccount* fundingAccount, CAccount* witnessAccount, std::string* pTxid, CAmount* pFee)
{
    if (pwallet == nullptr || witnessAccount == nullptr || fundingAccount == nullptr)
        throw witness_error(witness::RPC_INVALID_PARAMETER, "Require non-null pwallet, fundingAccount, witnessAccount");

    LOCK2(cs_main, pwallet->cs_wallet);

    if (!IsSegSigEnabled(chainActive.TipPrev()))
        throw std::runtime_error("Cannot use this command before segsig activates");

    if (pwallet->IsLocked()) {
        throw std::runtime_error("Wallet locked");
    }

    if ((!witnessAccount->IsPoW2Witness()) || witnessAccount->IsFixedKeyPool())
    {
        throw witness_error(witness::RPC_MISC_ERROR, "Cannot rotate a witness-only account as spend key is required to do this.");
    }

    const auto& unspentWitnessOutputs = getCurrentOutputsForWitnessAccount(witnessAccount);
    if (unspentWitnessOutputs.size() == 0)
        throw witness_error(witness::RPC_INVALID_ADDRESS_OR_KEY, strprintf("Account does not contain any witness outputs [%s].",
                                                                           boost::uuids::to_string(witnessAccount->getUUID())));

    rotatewitnessaddresshelper(fundingAccount, unspentWitnessOutputs, pwallet, pTxid, pFee);
}

CGetWitnessInfo GetWitnessInfoWrapper()
{
    CGetWitnessInfo witnessInfo;

    CBlock block;
    {
        LOCK(cs_main); // Required for ReadBlockFromDisk as well as GetWitnessInfo.
        if (!ReadBlockFromDisk(block, chainActive.Tip(), Params()))
        {
            std::string strErrorMessage = "Error in GetWitnessInfoWrapper, failed to read block from disk";
            CAlert::Notify(strErrorMessage, true, true);
            LogPrintf("%s", strErrorMessage.c_str());
            throw std::runtime_error(strErrorMessage);
        }
        if (!GetWitness(chainActive, Params(), nullptr, chainActive.Tip()->pprev, block, witnessInfo))
        {
            std::string strErrorMessage = "Error in GetWitnessInfoWrapper, failed to retrieve witness info";
            CAlert::Notify(strErrorMessage, true, true);
            LogPrintf("%s", strErrorMessage.c_str());
            throw std::runtime_error(strErrorMessage);
        }
    }

    return witnessInfo;
}

void EnsureMatchingWitnessCharacteristics(const witnessOutputsInfoVector& unspentWitnessOutputs)
{
    // Properties should beidentical, except lockFromBlock is special.
    // Extending (or initial funding) of a multiple-part witness can result in different lockFromBlock values, which are test as follows:
    // A) All outputs that have a non-zero lockFromBlock should share the same non-zero value.
    // B) All outputs with a zero lockFromBlock should share the same coin height.
    // C) And finally the non-zero value from A and B should be the same.
    // When a new witness is created it enters with a lockFromBlock == 0, which is replaced by the height the tx entered the chain
    // in a later operation (ie. a witness operation, rearrange, renew etc.)
    if (unspentWitnessOutputs.size() > 0)
    {
        CTxOutPoW2Witness witnessDetails0;
        if (!GetPow2WitnessOutput(std::get<0>(unspentWitnessOutputs[0]), witnessDetails0))
            throw std::runtime_error("Failure extracting witness details.");

        // collect values for A and B tests
        uint64_t nonZeroFrom = 0;
        uint64_t nonZeroCoin = 0;
        for (unsigned int i = 0; i < unspentWitnessOutputs.size(); i++) {
            CTxOutPoW2Witness witnessDetails;
            if (!GetPow2WitnessOutput(std::get<0>(unspentWitnessOutputs[i]), witnessDetails))
                throw std::runtime_error("Failure extracting witness details.");
            if (witnessDetails.lockFromBlock != 0)
                nonZeroFrom = witnessDetails.lockFromBlock;
            else
                nonZeroCoin = std::get<1>(unspentWitnessOutputs[i]);

            if (nonZeroFrom != 0 && nonZeroCoin != 0)
                break;
        }

        // test C
        if (nonZeroFrom != 0 && nonZeroCoin != 0 && nonZeroFrom != nonZeroCoin)
            throw std::runtime_error("Multiple addresses with different witness characteristics in account. Use RPC to furter handle this account.");

        for (unsigned int i = 0; i < unspentWitnessOutputs.size(); i++) {
            CTxOutPoW2Witness witnessCompare;
            if (!GetPow2WitnessOutput(std::get<0>(unspentWitnessOutputs[i]), witnessCompare))
                throw std::runtime_error("Failure extracting witness details.");
            if (witnessCompare.lockUntilBlock != witnessDetails0.lockUntilBlock ||
                witnessCompare.spendingKeyID != witnessDetails0.spendingKeyID ||
                witnessCompare.witnessKeyID != witnessDetails0.witnessKeyID ||
                (witnessCompare.lockFromBlock != 0 && witnessCompare.lockFromBlock != nonZeroFrom) || // test A
                (witnessCompare.lockFromBlock == 0 && std::get<1>(unspentWitnessOutputs[i]) != nonZeroCoin)) // test B
            {
                throw std::runtime_error("Multiple addresses with different witness characteristics in account. Use RPC to furter handle this account.");
            }
        }

    }
}

CWitnessAccountStatus GetWitnessAccountStatus(CWallet* pWallet, CAccount* account, CGetWitnessInfo* pWitnessInfo)
{
    WitnessStatus status;

    LOCK2(cs_main, pWallet->cs_wallet);

    CGetWitnessInfo witnessInfo;

    if (IsPow2Phase3Active(chainActive.Height())) {
        witnessInfo = GetWitnessInfoWrapper();
    }

    // Collect uspent witnesses coins for the account
    std::vector<RouletteItem> accountItems;
    for (const auto& item : witnessInfo.witnessSelectionPoolUnfiltered)
    {
        if (IsMine(*account, item.coin.out))
            accountItems.push_back(item);
    }

    bool haveUnspentWitnessUtxo = accountItems.size() > 0;

    CTxOutPoW2Witness witnessDetails0;
    if (haveUnspentWitnessUtxo && !GetPow2WitnessOutput(accountItems[0].coin.out, witnessDetails0))
        throw std::runtime_error("Failure extracting witness details.");

    uint64_t nLockFromBlock = 0;
    uint64_t nLockUntilBlock = 0;
    uint64_t nLockPeriodInBlocks = accountItems.size() > 0 ? GetPoW2LockLengthInBlocksFromOutput(accountItems[0].coin.out, accountItems[0].coin.nHeight, nLockFromBlock, nLockUntilBlock) : 0;

    const auto& unspentWitnessOutputs = getCurrentOutputsForWitnessAccount(account);
    EnsureMatchingWitnessCharacteristics(unspentWitnessOutputs);

    bool hasUnconfirmedBalance = pactiveWallet->GetUnconfirmedBalance(account, true, true) > 0;
    bool hasImmatureBalance = pactiveWallet->GetImmatureBalance(account, true, true) > 0;
    bool hasLockedBalance = pactiveWallet->GetLockedBalance(account, true) > 0;
    bool hasMatureBalance = pactiveWallet->GetBalance(account, true, false, true) > 0;

    bool hasBalance = hasUnconfirmedBalance||hasImmatureBalance||hasMatureBalance||hasLockedBalance;

    bool isLocked = haveUnspentWitnessUtxo && IsPoW2WitnessLocked(witnessDetails0, chainActive.Tip()->nHeight);

    uint64_t networkWeight = witnessInfo.nTotalWeightRaw;
    bool isExpired = haveUnspentWitnessUtxo && std::any_of(accountItems.begin(), accountItems.end(), [=](const RouletteItem& ri){
                         return witnessHasExpired(ri.nAge, ri.nWeight, networkWeight);
                     });

    if (!haveUnspentWitnessUtxo && (hasImmatureBalance||hasUnconfirmedBalance||hasLockedBalance))
    {
        status = WitnessStatus::Pending;
    }
    else if (!haveUnspentWitnessUtxo && !hasBalance)
    {
        status = WitnessStatus::Empty;
    }
    else if (!haveUnspentWitnessUtxo && hasMatureBalance && !(hasImmatureBalance || hasUnconfirmedBalance))
    {
        status = WitnessStatus::EmptyWithRemainder;
    }
    else if (haveUnspentWitnessUtxo && hasBalance && isLocked && isExpired)
    {
        status = WitnessStatus::Expired;
    }
    else if (haveUnspentWitnessUtxo && hasBalance && isLocked && !isExpired)
    {
        status = WitnessStatus::Witnessing;
    }
    else if (haveUnspentWitnessUtxo && hasBalance && isLocked && isExpired)
    {
        status = WitnessStatus::Expired;
    }
    else if (haveUnspentWitnessUtxo && hasBalance && !isLocked)
    {
        status = WitnessStatus::Ended;
    }
    else if (haveUnspentWitnessUtxo && !hasBalance && !isLocked)
    {
        status = WitnessStatus::Emptying;
    }
    else
    {
        throw std::runtime_error("Unable to determine witness state.");
    }

    // NOTE: assuming any unconfirmed tx here is a witness; avoid getting the witness bundles and testing those, this will almost always be
    // correct. Any edge cases where this fails will automatically resolve once the tx confirms.
    bool hasUnconfirmedWittnessTx = std::any_of(pWallet->mapWallet.begin(), pWallet->mapWallet.end(), [=](const auto& it){
        const auto& wtx = it.second;
        return account->HaveWalletTx(wtx) && wtx.InMempool();
    });

    // hasUnconfirmedWittnessTx -> renewed, extended ...
    if (status == WitnessStatus::Expired && hasUnconfirmedWittnessTx)
        status = WitnessStatus::Witnessing;

    bool hasScriptLegacyOutput = std::any_of(accountItems.begin(), accountItems.end(), [](const RouletteItem& ri){ return ri.coin.out.GetType() == CTxOutType::ScriptLegacyOutput; });

    std::vector<uint64_t> parts;
    std::transform(accountItems.begin(), accountItems.end(), std::back_inserter(parts), [](const auto& ri) { return ri.nWeight; });

    CWitnessAccountStatus result {
        account,
        status,
        networkWeight,
        //NB! We always want the account weight (even if expired) - otherwise how do we e.g. draw a historical graph of the expected earnings for the expired account?
        std::accumulate(accountItems.begin(), accountItems.end(), uint64_t(0), [](const uint64_t acc, const RouletteItem& ri){ return acc + ri.nWeight; }),
        hasScriptLegacyOutput,
        hasUnconfirmedWittnessTx,
        nLockFromBlock,
        nLockUntilBlock,
        nLockPeriodInBlocks,
        parts
    };

    if (pWitnessInfo && IsPow2Phase3Active(chainActive.Height()))
        *pWitnessInfo = std::move(witnessInfo);

    return result;
}

void redistributeandextendwitnessaccount(CWallet* pwallet, CAccount* fundingAccount, CAccount* witnessAccount, const std::vector<CAmount>& redistributionAmounts, uint64_t requestedLockPeriodInBlocks, std::string* pTxid, CAmount* pFee)
{
    if (pwallet == nullptr || witnessAccount == nullptr || fundingAccount == nullptr)
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, "Require non-null pwallet, fundingAccount, witnessAccount");
    }

    LOCK2(cs_main, pwallet->cs_wallet);

    if (!IsSegSigEnabled(chainActive.TipPrev()))
    {
        throw std::runtime_error("Segsig not activated");
    }

    if (pwallet->IsLocked())
    {
        throw std::runtime_error("Wallet locked");
    }

    if ((!witnessAccount->IsPoW2Witness()) || witnessAccount->IsFixedKeyPool())
    {
        throw witness_error(witness::RPC_MISC_ERROR, "Cannot operate on witness-only account as spend key is required to do this.");
    }

    const auto& unspentWitnessOutputs = getCurrentOutputsForWitnessAccount(witnessAccount);
    if (unspentWitnessOutputs.size() == 0)
    {
        throw witness_error(witness::RPC_INVALID_ADDRESS_OR_KEY, strprintf("Account does not contain any witness outputs [%s].", boost::uuids::to_string(witnessAccount->getUUID())));
    }

    EnsureMatchingWitnessCharacteristics(unspentWitnessOutputs);

    const auto& [currentWitnessTxOut, currentWitnessHeight, currentWitnessTxIndex, currentWitnessOutpoint] = unspentWitnessOutputs[0];
    (unused)currentWitnessHeight;
    (unused)currentWitnessTxIndex;
    (unused)currentWitnessOutpoint;

    // Get the current witness details
    CTxOutPoW2Witness currentWitnessDetails;
    GetPow2WitnessOutput(currentWitnessTxOut, currentWitnessDetails);

    if (std::any_of(redistributionAmounts.begin(), redistributionAmounts.end(), [](const auto& amount){ return amount < (gMinimumWitnessAmount*COIN); }))
        throw witness_error(witness::RPC_TYPE_ERROR, strprintf("Witness amount must be %d or larger", gMinimumWitnessAmount));

    uint64_t remainingLockDurationInBlocks = GetPoW2RemainingLockLengthInBlocks(currentWitnessDetails.lockUntilBlock, chainActive.Tip()->nHeight);
    if (remainingLockDurationInBlocks == 0)
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, "PoW² witness has already unlocked.");
    }

    // extend specifics
    if (requestedLockPeriodInBlocks != 0)
    {
        if (requestedLockPeriodInBlocks > MaximumWitnessLockLength())
        {
            throw witness_error(witness::RPC_INVALID_PARAMETER, "Maximum lock period of 3 years exceeded.");
        }

        if (requestedLockPeriodInBlocks < MinimumWitnessLockLength())
        {
            throw witness_error(witness::RPC_INVALID_PARAMETER, "Minimum lock period of 1 month exceeded.");
        }

        // Add a small buffer to give us time to enter the blockchain
        if (requestedLockPeriodInBlocks == MinimumWitnessLockLength())
        {
            requestedLockPeriodInBlocks += 50;
        }

        if (requestedLockPeriodInBlocks < remainingLockDurationInBlocks)
        {
            throw witness_error(witness::RPC_INVALID_PARAMETER, strprintf("New lock period [%d] does not exceed remaining lock period [%d]", requestedLockPeriodInBlocks, remainingLockDurationInBlocks));
        }

        // Enforce minimum weight for each amount
        auto tooSmallTest = [&] (const auto& amount){ return GetPoW2RawWeightForAmount(amount, requestedLockPeriodInBlocks) < gMinimumWitnessWeight; };
        bool witnessAmountTooSmall = std::any_of(redistributionAmounts.begin(), redistributionAmounts.end(), tooSmallTest);
        if (witnessAmountTooSmall)
        {
            throw witness_error(witness::RPC_TYPE_ERROR, strprintf("Witness amount must be %d or larger", gMinimumWitnessAmount));
        }

        // Enforce new combined weight > old combined weight
        const CGetWitnessInfo witnessInfo = GetWitnessInfoWrapper();
        uint64_t dummyLockFrom, dummyLockUntil;
        uint64_t oldLockPeriod = GetPoW2LockLengthInBlocksFromOutput(currentWitnessTxOut, currentWitnessHeight, dummyLockFrom, dummyLockUntil);
        std::vector<CAmount> oldAmounts;
        std::transform(unspentWitnessOutputs.begin(), unspentWitnessOutputs.end(), std::back_inserter(oldAmounts), [](const auto& it) { return std::get<0>(it).nValue; });
        uint64_t oldCombinedWeight = combinedWeight(oldAmounts, oldLockPeriod);
        uint64_t newCombinedWeight = combinedWeight(redistributionAmounts, requestedLockPeriodInBlocks);
        if (oldCombinedWeight >= newCombinedWeight)
        {
            throw witness_error(witness::RPC_TYPE_ERROR, strprintf("New combined witness weight (%" PRIu64 ") must exceed old (%" PRIu64 ")", newCombinedWeight, oldCombinedWeight));
        }
    }

    // Check for immaturity
    for ( const auto& [currentWitnessTxOut, currentWitnessHeight, currentWitnessTxIndex, currentWitnessOutpoint] : unspentWitnessOutputs )
    {
        (unused) currentWitnessTxOut;
        (unused) currentWitnessOutpoint;
        (unused) currentWitnessTxIndex;
        //fixme: (PHASE5) - Minor code cleanup.
        //This check should go through the actual chain maturity stuff (via wtx) and not calculate maturity directly.
        if (chainActive.Tip()->nHeight - currentWitnessHeight < (uint64_t)(COINBASE_MATURITY))
        {
            throw witness_error(witness::RPC_MISC_ERROR, "Cannot perform operation on immature transaction, please wait for transaction to mature and try again");
        }
    }

    // Check type (can't extend script type, must witness once or renew/upgrade after phase 4 activated)
    if (std::any_of(unspentWitnessOutputs.begin(), unspentWitnessOutputs.end(), [](const auto& it){ return std::get<0>(it).GetType() !=  CTxOutType::PoW2WitnessOutput; }))
    {
        throw witness_error(witness::RPC_TYPE_ERROR, "Witness has to be type POW2WITNESS, renew or upgrade account");
    }

    // Check that total of new value is at least old total
    CAmount redistributionTotal = std::accumulate(redistributionAmounts.begin(), redistributionAmounts.end(), CAmount(0), [](const CAmount acc, const CAmount amount){ return acc + amount; });
    CAmount oldTotal = std::accumulate(unspentWitnessOutputs.begin(), unspentWitnessOutputs.end(), CAmount(0), [](const CAmount acc, const auto& it){
        const CTxOut& txOut = std::get<0>(it);
        return acc + txOut.nValue;
    });
    if (redistributionTotal < oldTotal)
    {
        throw witness_error(witness::RPC_INVALID_PARAMETER, strprintf("New amount [%s] is smaller than current amount [%s]", FormatMoney(redistributionTotal), FormatMoney(oldTotal)));
    }

    // Create the redistribution transaction
    CReserveKeyOrScript reservekey(pwallet, fundingAccount, KEYCHAIN_CHANGE);
    std::string reasonForFail;
    CAmount transactionFee;
    CMutableTransaction witnessTransaction(CTransaction::SEGSIG_ACTIVATION_VERSION);
    {

        uint64_t highestActionNonce = 0;
        uint64_t highestFailCount = 0;

        // Add all original outputs as inputs
        for (const auto& [currentWitnessTxOut, currentWitnessHeight, currentWitnessTxIndex, currentWitnessOutpoint]: unspentWitnessOutputs)
        {
            pwallet->AddTxInput(witnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, currentWitnessHeight, currentWitnessTxIndex), false);

            CTxOutPoW2Witness details;
            if (!GetPow2WitnessOutput(currentWitnessTxOut, details))
            {
                throw witness_error(witness::RPC_MISC_ERROR, "Failure extracting witness details.");
            }
            if (details.actionNonce > highestActionNonce)
                highestActionNonce = details.actionNonce;
            if (details.failCount > highestFailCount)
                highestFailCount = details.failCount;
        }

        // Add new witness outputs
        for (const CAmount& distAmount : redistributionAmounts)
        {
            CTxOut distTxOutput;
            distTxOutput.SetType(CTxOutType::PoW2WitnessOutput);
             if (requestedLockPeriodInBlocks != 0)
             {
                 // extend
                 distTxOutput.output.witnessDetails.lockFromBlock = 0;
                 distTxOutput.output.witnessDetails.lockUntilBlock = chainActive.Tip()->nHeight + requestedLockPeriodInBlocks;
             }
             else
             {
                 // rearrange
                 distTxOutput.output.witnessDetails.lockFromBlock = currentWitnessDetails.lockFromBlock;
                 distTxOutput.output.witnessDetails.lockUntilBlock = currentWitnessDetails.lockUntilBlock;
             }
            distTxOutput.output.witnessDetails.spendingKeyID = currentWitnessDetails.spendingKeyID;
            distTxOutput.output.witnessDetails.witnessKeyID = currentWitnessDetails.witnessKeyID;
            distTxOutput.output.witnessDetails.failCount = highestFailCount;
            distTxOutput.output.witnessDetails.actionNonce = highestActionNonce + 1;
            distTxOutput.nValue = distAmount;
            witnessTransaction.vout.push_back(distTxOutput);
        }

        // Fund the additional amount in the transaction (including fees)
        int changeOutputPosition = 1;
        std::set<int> subtractFeeFromOutputs; // Empty - we don't subtract fee from outputs
        CCoinControl coincontrol;
        if (!pwallet->FundTransaction(fundingAccount, witnessTransaction, transactionFee, changeOutputPosition, reasonForFail, false, subtractFeeFromOutputs, coincontrol, reservekey))
        {
            throw witness_error(witness::RPC_MISC_ERROR, strprintf("Failed to fund transaction [%s]", reasonForFail.c_str()));
        }
    }

    uint256 finalTransactionHash;
    {
        LOCK2(cs_main, pwallet->cs_wallet);
        if (!pwallet->SignAndSubmitTransaction(reservekey, witnessTransaction, reasonForFail, &finalTransactionHash))
        {
            throw witness_error(witness::RPC_MISC_ERROR, strprintf("Failed to commit transaction [%s]", reasonForFail.c_str()));
        }
    }

    // Set result parameters
    if (pTxid != nullptr)
        *pTxid = finalTransactionHash.GetHex();
    if (pFee != nullptr)
        *pFee = transactionFee;
}

void redistributewitnessaccount(CWallet* pwallet, CAccount* fundingAccount, CAccount* witnessAccount, const std::vector<CAmount>& redistributionAmounts, std::string* pTxid, CAmount* pFee)
{
    redistributeandextendwitnessaccount(pwallet, fundingAccount, witnessAccount, redistributionAmounts, 0, pTxid, pFee);
}

void extendwitnessaccount(CWallet* pwallet, CAccount* fundingAccount, CAccount* witnessAccount, CAmount amount, uint64_t requestedLockPeriodInBlocks, std::string* pTxid, CAmount* pFee)
{
    CGetWitnessInfo witnessInfo = GetWitnessInfoWrapper();
    auto distribution = optimalWitnessDistribution(amount, requestedLockPeriodInBlocks, witnessInfo.nTotalWeightEligibleRaw);
    redistributeandextendwitnessaccount(pwallet, fundingAccount, witnessAccount, distribution, requestedLockPeriodInBlocks, pTxid, pFee);
}

uint64_t adjustedWeightForAmount(const CAmount amount, const uint64_t duration, uint64_t networkWeight)
{
    uint64_t maxWeight = networkWeight / 100;
    uint64_t rawWeight = GetPoW2RawWeightForAmount(amount, duration);
    uint64_t weight = std::min(rawWeight, maxWeight);
    return weight;
}

/** Estimated witnessing count expressed as a fraction per block for a single amount */
double witnessFraction(const CAmount amount, const uint64_t duration, const uint64_t totalWeight)
{
    uint64_t weight = adjustedWeightForAmount(amount, duration, totalWeight);

    // election probability
    const double p = double(weight) / totalWeight;

    // adjust for cooldown
    double fraction = 1.0 / (gMinimumParticipationAge + 1.0/p);

    return fraction;
}

/** Estimated witnessing count expressed as a fraction per block for an amount distribution */
double witnessFraction(const std::vector<CAmount>& amounts, const uint64_t duration, const uint64_t totalWeight)
{
    double fraction = std::accumulate(amounts.begin(), amounts.end(), 0.0, [=](double acc, CAmount amount) {
        return acc + witnessFraction(amount, duration, totalWeight);
    });
    return fraction;
}

std::tuple<std::vector<CAmount>, uint64_t, CAmount> witnessDistribution(CWallet* pWallet, CAccount* account)
{
    // assumes all account witness outputs have identical characteristics

    LOCK2(cs_main, pWallet->cs_wallet);

    const auto unspentWitnessOutputs = getCurrentOutputsForWitnessAccount(account);

    std::vector<CAmount> distribution;
    std::transform(unspentWitnessOutputs.begin(), unspentWitnessOutputs.end(), std::back_inserter(distribution), [](const auto& it) {
        const CTxOut& txOut = std::get<0>(it);
        return txOut.nValue;
    });

    CAmount total = std::accumulate(unspentWitnessOutputs.begin(), unspentWitnessOutputs.end(), CAmount(0), [](const CAmount acc, const auto& it){
        const CTxOut& txOut = std::get<0>(it);
        return acc + txOut.nValue;
    });

    int64_t duration = 0;
    if (unspentWitnessOutputs.size() > 0) {
        const CTxOut& txOut = std::get<0>(unspentWitnessOutputs[0]);
        uint64_t nUnused1, nUnused2;
        duration = GetPoW2LockLengthInBlocksFromOutput(txOut, std::get<1>(unspentWitnessOutputs[0]), nUnused1, nUnused2);
    }

    return std::tuple(distribution, duration, total);
}

CAmount maxAmountForDurationAndWeight(uint64_t lockDuration, uint64_t targetWeight)
{
    // note that yearly_blocks is only correct for mainnet and should be parametrized for testnet, however it is consistent with
    // with other parts of the code that also use mainnet characteristics for weight determination when on testnet
    const double yearly_blocks = 365 * gRefactorDailyBlocksUsage;
    const double K = 100000.0;
    const double T = (1.0 + lockDuration / (yearly_blocks));

    // solve quadratic to find amount that will match weight
    const double A = T / K;
    const double B = T;
    const double C = - (double)(targetWeight);

    double amount = (-B + sqrt(B*B - 4.0 * A * C)) / (2.0 * A);

    return CAmount (amount * COIN);
}

std::vector<CAmount> optimalWitnessDistribution(CAmount totalAmount, uint64_t duration, uint64_t totalWeight)
{
    std::vector<CAmount> distribution;

    // Amount where maximum weight is reached and so any extra on top of that will not produce any gains
    CAmount partMax = maxAmountForDurationAndWeight(duration, totalWeight/100);
    // Amount we have to be above to be a valid part of chain
    CAmount partMin = maxAmountForDurationAndWeight(duration, gMinimumWitnessWeight)+1;
    if (partMin < gMinimumWitnessAmount*COIN)
        partMin = gMinimumWitnessAmount*COIN+1;

    // Divide int parts into 95% of maximum workable amount.
    // Leaves some room for:
    // a) leaves some room for entwork weight fluctuatiopns
    // b) leaves some room when total network witness weight changes
    CAmount partTarget = (95 * partMax) / 100;

    // ensure minimum criterium is met (on mainnet this is not expected to happen)
    if (partTarget < partMin)
        partTarget = partMin;

    int wholeParts = totalAmount / partTarget;

    if (wholeParts > 0)
    {
        CAmount remainder = totalAmount - wholeParts * partTarget;
        CAmount partRemainder = remainder/wholeParts;

        for (int i=0; i< wholeParts; i++)
        {
            distribution.push_back(partTarget+partRemainder);
            remainder -= partRemainder;
        }

        // add any final remainder to first part
        distribution[0] += remainder;
    }
    else
    {
        distribution.push_back(totalAmount);
    }
    return distribution;
}

bool isWitnessDistributionNearOptimal(CWallet* pWallet, CAccount* account, const CGetWitnessInfo& witnessInfo)
{
    // NB! We prefer the raw weight to the eligible weight as the raw weight fluctuates less
    // And over time the two should become close anyway
    uint64_t totalWeight = witnessInfo.nTotalWeightEligibleRaw;

    auto [currentDistribution, duration, totalAmount] = witnessDistribution(pWallet, account);
    double currentFraction = witnessFraction(currentDistribution, duration, totalWeight);

    auto optimalDistribution = optimalWitnessDistribution(totalAmount, duration, totalWeight);
    double optimalFraction = witnessFraction(optimalDistribution, duration, totalWeight);

    const double OPTIMAL_DISTRIBUTION_THRESHOLD = 0.95;
    bool nearOptimal = currentFraction / optimalFraction >= OPTIMAL_DISTRIBUTION_THRESHOLD;
    return nearOptimal;
}

uint64_t combinedWeight(const std::vector<CAmount> amounts, uint64_t duration)
{
    return std::accumulate(amounts.begin(), amounts.end(), uint64_t(0), [=](uint64_t acc, CAmount amount)
    {
        return acc + GetPoW2RawWeightForAmount(amount, duration);
    });
}

std::string witnessKeysLinkUrlForAccount(CWallet* pWallet, CAccount* account)
{
    std::set<CKeyID> keys;

    LOCK2(cs_main, pWallet->cs_wallet);

    if (chainActive.Tip())
    {
        std::map<COutPoint, Coin> allWitnessCoins;
        if (getAllUnspentWitnessCoins(chainActive, Params(), chainActive.Tip(), allWitnessCoins))
        {
            for (const auto& [witnessOutPoint, witnessCoin] : allWitnessCoins)
            {
                (unused)witnessOutPoint;
                CTxOutPoW2Witness witnessDetails;
                GetPow2WitnessOutput(witnessCoin.out, witnessDetails);
                if (account->HaveKey(witnessDetails.witnessKeyID))
                {
                    keys.insert(witnessDetails.witnessKeyID);
                }
            }
        }
    }

    std::string witnessAccountKeys = "";
    for (const CKeyID& key: keys) {
        //fixme: (FUT) - to be 100% correct we should export the creation time of the actual key (where available) and not getEarliestPossibleCreationTime - however getEarliestPossibleCreationTime will do for now.
        CKey witnessPrivKey;
        if (account->GetKey(key, witnessPrivKey))
        witnessAccountKeys += CGuldenSecret(witnessPrivKey).ToString() + strprintf("#%s", account->getEarliestPossibleCreationTime());
        witnessAccountKeys += ":";
    }

    if (!witnessAccountKeys.empty())
    {
        witnessAccountKeys.pop_back();
        witnessAccountKeys = "gulden://witnesskeys?keys=" + witnessAccountKeys;
    }

    return witnessAccountKeys;
}

max_stars_repo_name: fakecoinbase/Guldenslashgulden-official

max_issues_repo_name: fakecoinbase/Guldenslashgulden-official

max_forks_repo_name: fakecoinbase/Guldenslashgulden-official


text:
// compile with -lboost_json

#include <iostream>
#include <boost/json.hpp>

void test1()
{
    boost::json::object obj;
    obj["a"] = true;
    obj["b"] = "hello";
    obj["c"] = { 1, 2, 3 }; // array
    obj["d"] = { { "d1", 0 }, { "d2", 1 } }; // object
    obj["e"] = nullptr;
    std::cout << boost::json::serialize(obj) << std::endl;
    //=>{"a":true,"b":"hello","c":[1,2,3],"d":{"d1":0,"d2":1},"e":null}
}

void test2()
{
    boost::json::value v =
    {
        { "pi", 3.141 },
        { "happy", true },
        { "name", "Boost" },
        { "nothing", nullptr },
        { "answer",
            {
                { "everything", 42 }
            }
        },
        { "list", {1, 0, 2} },
        { "object",
            {
                { "currency", "USD" },
                { "value", 42.99 }
            }
        }
    };
    std::cout << boost::json::serialize(v) << std::endl;
    //=>{"pi":3.141E0,"happy":true,"name":"Boost","nothing":null,"answer":{"everything":42},"list":[1,0,2],"object":{"currency":"USD","value":4.299E1}}
}

void test3()
{
    boost::json::value v = boost::json::parse("100");
    int num = boost::json::value_to<int>(v);

    try {
        boost::json::value v2 = boost::json::parse("Null");
    } catch (boost::wrapexcept<boost::system::system_error> e) {
        std::cout << e.what() << std::endl;
        //=>syntax error
    }

    boost::json::value v3 = boost::json::parse(R"( {"number": "100"} )");
    try {
        v3.at("Number");
    } catch (boost::wrapexcept<std::out_of_range> e) {
        std::cout << e.what() << std::endl;
        //=>out of range
    }

    try {
        num = boost::json::value_to<int>(v3.at("number"));
    } catch (boost::wrapexcept<boost::system::system_error> e) {
        std::cout << e.what() << std::endl;
        //=>not a number
    }
}

int main(int argc, char *argv[])
{
    test1();
    test2();
    test3();
}

ext: cpp

max_stars_repo_name: liphx/cplusplus

max_issues_repo_name: liphx/cplusplus

max_forks_repo_name: liphx/cplusplus


text:
//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_IF_ONE_ELSE_ZERO_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_IF_ONE_ELSE_ZERO_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-boolean
    This function object conditionally returns @c 1 (respectively @c 0)
    if @c c is @ref True (respectively  @ref False)

    @par Header <boost/simd/function/if_one_else_zero.hpp>

    @par Example:

      @snippet if_one_else_zero.cpp if_one_else_zero

    @par Possible output:

      @snippet if_one_else_zero.txt if_one_else_zero

  **/
  Value if_one_else_zero(Value const& c);
} }
#endif

#include <boost/simd/function/scalar/if_one_else_zero.hpp>
#include <boost/simd/function/simd/if_one_else_zero.hpp>

#endif

ext: hpp

max_stars_repo_name: SylvainCorlay/pythran

max_issues_repo_name: SylvainCorlay/pythran

max_forks_repo_name: SylvainCorlay/pythran


text:
/*
// Copyright (c) 2000-2009, Texas Engineering Experiment Station (TEES), a
// component of the Texas A&M University System.

// All rights reserved.

// The information and source code contained herein is the exclusive
// property of TEES and may not be disclosed, examined or reproduced
// in whole or in part without explicit written authorization from TEES.
*/

#ifndef STAPL_RUNTIME_NON_RMI_CONSTRUCT_HPP
#define STAPL_RUNTIME_NON_RMI_CONSTRUCT_HPP

#include "rpc.hpp"
#include "../context.hpp"
#include "../future.hpp"
#include "../gang.hpp"
#include "../location_range.hpp"
#include "../promise.hpp"
#include "../rmi_handle.hpp"
#include "../runqueue.hpp"
#include "../tags.hpp"
#include "../request/arg_storage.hpp"
#include "../request/location_rpc_request.hpp"
#include "../utility/ref_counted.hpp"
#include <algorithm>
#include <functional>
#include <iterator>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <boost/range/adaptor/transformed.hpp>

namespace stapl {

namespace runtime {

//////////////////////////////////////////////////////////////////////
/// @brief Container that associates parent gang to child gang location ids only
///        for locally managed locations.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
class managed_locations_info
: public ref_counted<managed_locations_info>
{
public:
  using id             = location_md::id;
  using size_type      = location_md::size_type;
private:
  using container_type = std::unordered_map<id, std::pair<id, size_type>>;
public:
  using value_type     = typename container_type::value_type;
  using const_iterator = typename container_type::const_iterator;
  using iterator       = const_iterator;
private:
  //////////////////////////////////////////////////////////////////////
  /// @brief Returns parent gang location ids.
  //////////////////////////////////////////////////////////////////////
  struct extract_location_id
  {
    id operator()(value_type const& v) const noexcept
    { return v.first; }
  };

  //////////////////////////////////////////////////////////////////////
  /// @brief Returns child gang location ids.
  //////////////////////////////////////////////////////////////////////
  struct extract_mapped_location_id
  {
    id operator()(value_type const& v) const noexcept
    { return v.second.first; }
  };

  container_type m_map;

public:
  template<typename Range>
  managed_locations_info(gang_description const& gd, Range const& r)
  {
    if (gd.is_on_shmem()) {
      id i = 0;
      for (auto it = std::begin(r); it != std::end(r); ++it, ++i) {
        const auto lid = *it;
        m_map.emplace(lid, std::make_pair(i, i));
      }
    }
    else if (gd.is_on_distmem()) {
      const auto my_pid = runqueue::get_process_id();
      id i = 0;
      for (auto it = std::begin(r); it != std::end(r); ++it, ++i) {
        const auto lid = *it;
        if (gd.get_process_id(lid)==my_pid) {
          m_map.emplace(lid, std::make_pair(i, 0));
          break;
        }
      }
    }
    else {
      const auto my_pid = runqueue::get_process_id();
      id i = 0;
      for (auto it = std::begin(r); it != std::end(r); ++it, ++i) {
        const auto lid = *it;
        if (gd.get_process_id(lid)==my_pid)
          m_map.emplace(lid, std::make_pair(i, m_map.size()));
      }
    }
  }

  size_type size(void) const noexcept
  { return m_map.size(); }

  const_iterator begin(void) const
  { return m_map.begin(); }

  const_iterator end(void) const
  { return m_map.end(); }

  std::pair<id, size_type> const& find(const id lid) const noexcept
  {
    auto it = m_map.find(lid);
    STAPL_RUNTIME_ASSERT(it!=m_map.end());
    return it->second;
  }

  auto get_location_ids(void)
    -> decltype(boost::adaptors::transform(
                  std::declval<ref_counted_range<managed_locations_info>>(),
                  extract_location_id{}))
  {
    return boost::adaptors::transform(
             ref_counted_range<managed_locations_info>{*this},
             extract_location_id{});
  }

  auto get_mapped_location_ids(void)
    -> decltype(boost::adaptors::transform(
                  std::declval<ref_counted_range<managed_locations_info>>(),
                  extract_mapped_location_id{}))
  {
    return boost::adaptors::transform(
             ref_counted_range<managed_locations_info>{*this},
             extract_mapped_location_id{});
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Request to construct an object in a new single location gang.
///
/// @tparam Callback Callback to pass a pointer to the constructed object.
/// @tparam Function Function that creates a new object.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename Callback, typename Function>
class single_location_construct_request final
: public location_rpc_request,
  private arg_storage_t<Callback, Callback const&>,
  private arg_storage_t<Function, Function const&>
{
private:
  using callback_storage_type = arg_storage_t<Callback, Callback const&>;
  using function_storage_type = arg_storage_t<Function, Function const&>;

public:
  template<typename C, typename F>
  static std::size_t expected_size(C&& c, F&& f) noexcept
  {
    return (sizeof(single_location_construct_request)              +
            callback_storage_type::packed_size(std::forward<C>(c)) +
            function_storage_type::packed_size(std::forward<F>(f)));
  }

  template<typename C, typename F>
  single_location_construct_request(C&& c, F&& f) noexcept
  : location_rpc_request(sizeof(*this)),
    callback_storage_type(std::forward<C>(c), this, this->size()),
    function_storage_type(std::forward<F>(f), this, this->size())
  { }

  bool operator()(location_md&, message_shared_ptr&) final
  {
    {
      gang g;
      auto* const t = function_storage_type::get(this)();
      callback_storage_type::get(this)(t);
    }

    this->~single_location_construct_request();
    return true;
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Request to construct an object on all locally managed locations.
///
/// @tparam Callback           Callback to pass a pointer to the constructed
///                            object.
/// @tparam Function           Function that creates a new object.
/// @tparam LocallyManagedOnly If @c true, then this request executes only on
///                            the locally managed locations.
///
/// This request always runs on the same process as the gang it references,
/// therefore it is correct to have a reference to the @ref gang_md object.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename Callback, typename Function, bool LocallyManagedOnly>
class all_locs_construct_request final
: public location_rpc_request,
  private arg_storage_t<Callback, Callback const&>,
  private arg_storage_t<Function, Function const&>
{
private:
  using callback_storage_type = arg_storage_t<Callback, Callback const&>;
  using function_storage_type = arg_storage_t<Function, Function const&>;

  gang_md& m_g;

public:
  template<typename C, typename F>
  static std::size_t expected_size(C&& c, F&& f) noexcept
  {
    return (sizeof(all_locs_construct_request)                     +
            callback_storage_type::packed_size(std::forward<C>(c)) +
            function_storage_type::packed_size(std::forward<F>(f)));
  }

  template<typename C, typename F>
  all_locs_construct_request(C&& c, gang_md& g, F&& f) noexcept
  : location_rpc_request(sizeof(*this)),
    callback_storage_type(std::forward<C>(c), this, this->size()),
    function_storage_type(std::forward<F>(f), this, this->size()),
    m_g(g)
  { }

  bool operator()(location_md& l, message_shared_ptr&) final
  {
    // create metadata
    auto* const nl = (LocallyManagedOnly
                       ? new location_md(l.local_index(), m_g)
                       : new location_md{l.get_id(), l.local_index(), m_g});

    // create object
    {
      context ctx{*nl};
      auto* const t = function_storage_type::get(this)();
      callback_storage_type::get(this)(t);
    }

    this->~all_locs_construct_request();
    return true;
  }
};

//////////////////////////////////////////////////////////////////////
/// @brief Request to construct an object in a new gang over specific locations
///        of an existing gang.
///
/// @tparam Callback        Callback to pass a pointer to the constructed
///                         object.
/// @tparam Function        Function that creates a new object.
/// @tparam LocationMapping Associative container with the mapping from current
///                         to new location id.
///
/// This request has a reference counted pointer to a shared data structure that
/// maps current gang location ids to new gang location ids.
///
/// The reference count is increased prior to assigning the pointer in the
/// request, as requests are memcpy'd instead of fully constructed. The
/// destructor of the request will assure that the last thread will delete the
/// shared data structure.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename Callback, typename Function, typename LocationMapping>
class construct_request final
: public location_rpc_request,
  private arg_storage_t<Callback, Callback const&>,
  private arg_storage_t<Function, Function const&>
{
private:
  using callback_storage_type = arg_storage_t<Callback, Callback const&>;
  using function_storage_type = arg_storage_t<Function, Function const&>;

  gang_md&                              m_g;
  boost::intrusive_ptr<LocationMapping> m_locs_info;

public:
  template<typename C, typename F>
  static std::size_t expected_size(C&& c, F&& f) noexcept
  {
    return (sizeof(construct_request)                              +
            callback_storage_type::packed_size(std::forward<C>(c)) +
            function_storage_type::packed_size(std::forward<F>(f)));
  }

  template<typename C, typename Map, typename F>
  construct_request(C&& c, gang_md& g, Map&& m, F&& f) noexcept
  : location_rpc_request(sizeof(*this)),
    callback_storage_type(std::forward<C>(c), this, this->size()),
    function_storage_type(std::forward<F>(f), this, this->size()),
    m_g(g),
    m_locs_info(std::forward<Map>(m))
  { }

  bool operator()(location_md& l, message_shared_ptr&) final
  {
    // create metadata by mapping current location id to new gang location id
    const auto lid     = l.get_id();
    const auto mapping = m_locs_info->find(lid);
    auto* const nl     = new location_md{mapping.first, mapping.second, m_g};

    // create object
    {
      context ctx{*nl};
      auto* const t = function_storage_type::get(this)();
      callback_storage_type::get(this)(t);
    }

    this->~construct_request();
    return true;
  }
};

template<typename ProcessRange, typename Callback, typename Function>
void call_construct_all_impl(ProcessRange&&,
                             Callback&&,
                             const gang_md::id,
                             const context::epoch_type,
                             const gang_md::id,
                             Function&&);

template<typename ProcessRange,
         typename Callback,
         typename T,
         typename Function>
void call_construct_range_impl(ProcessRange&&,
                               Callback&&,
                               const gang_md::id,
                               location_range_wrapper<T> const&,
                               const context::epoch_type,
                               const gang_md::id,
                               Function&&);

//////////////////////////////////////////////////////////////////////
/// @brief Creates a distributed object through @p f on all locations of gang
///        @p g.
///
/// @param c       Callback to pass a pointer to the constructed object.
/// @param g       Destination gang metadata.
/// @param e       Caller epoch.
/// @param new_gid New gang id; if @ref invalid_gang_id, then a new id will be
///                generated.
/// @param f       Object creation function.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename Callback, typename Function>
void construct_all_impl(Callback&& c,
                        gang_md& g,
                        const context::epoch_type e,
                        const gang_md::id new_gid,
                        Function&& f)
{
  const auto gid    = g.get_id();
  auto* const new_g = (new_gid==invalid_gang_id ? new gang_md{gid, g}
                                                : new gang_md{new_gid, gid, g});

  // forward information to children processes
  auto const& children = new_g->get_topology().children();
  if (!children.empty())
    call_construct_all_impl(children, c, gid, e, new_g->get_id(), f);

  // create object on all locally managed locations
  using request_type = all_locs_construct_request<
                         typename std::decay<Callback>::type,
                         typename std::decay<Function>::type,
                         false>;

  managed_locations_rpc_aggregator a{g, e};
  const auto size = request_type::expected_size(std::forward<Callback>(c),
                                                std::forward<Function>(f));
  new(a.allocate(size)) request_type{std::forward<Callback>(c),
                                     *new_g,
                                     std::forward<Function>(f)};
}

//////////////////////////////////////////////////////////////////////
/// @brief Calls @ref construct_all_impl() on processes @p pids.
///
/// @param pids    Destination processes.
/// @param c       Callback to pass a pointer to the constructed object.
/// @param gid     Destination gang id.
/// @param e       Caller epoch.
/// @param new_gid New gang id.
/// @param f       Object creation function.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename ProcessRange, typename Callback, typename Function>
void call_construct_all_impl(ProcessRange&& pids,
                             Callback&& c,
                             const gang_md::id gid,
                             const context::epoch_type e,
                             const gang_md::id new_gid,
                             Function&& f)
{
  rpc(&construct_all_impl<decltype(c), decltype(f)>,
      std::forward<ProcessRange>(pids),
      std::forward<Callback>(c),
      gang_retriever{gid}, e, new_gid, std::forward<Function>(f));
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates a distributed object through @p f on the subset @p locs of
///        the locations of the gang associated with @p g.
///
/// @param c       Callback to pass a pointer to the constructed object.
/// @param g       Destination gang metadata.
/// @param r       Destination locations of @p g.
/// @param e       Caller epoch.
/// @param new_gid New gang id; if @ref invalid_gang_id, then a new id will be
///                generated.
/// @param f       Object creation function.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename Callback, typename T, typename Function>
void construct_range_impl(Callback&& c,
                          gang_md& g,
                          location_range_wrapper<T> const& r,
                          const context::epoch_type e,
                          const gang_md::id new_gid,
                          Function&& f)
{
  // find participating locations on this process and create index in new gang
  auto managed_locs =
    make_ref_counted<managed_locations_info>(g.get_description(), r);
  const auto num_managed = managed_locs->size();
  managed_locs->add_ref(num_managed - 1);

  gang_md* new_g = nullptr;
  if (num_managed==r.size()) {
    // new gang only on shared memory
    STAPL_RUNTIME_ASSERT(new_gid==invalid_gang_id);
    new_g = new gang_md{g.get_id(), num_managed};
  }
  else {
    // create gang metadata
    const auto gid = g.get_id();
    gang_description new_gd{g.get_description(),
                            arbitrary<location_md::id, location_md::id>{r},
                            r.size()};
    auto new_lids = managed_locs->get_mapped_location_ids();
    new_g =
      ((new_gid==invalid_gang_id)
        ? new gang_md{gid, std::move(new_gd), std::move(new_lids)}
        : new gang_md{new_gid, gid, std::move(new_gd), std::move(new_lids)});

    // forward information to children processes
    auto const& children = new_g->get_topology().children();
    if (!children.empty())
      call_construct_range_impl(children, c, gid, r, e, new_g->get_id(), f);
  }

  // send to locally managed locations
  using request_type = construct_request<
                         typename std::decay<Callback>::type,
                         typename std::decay<Function>::type,
                         managed_locations_info>;
  managed_locations_rpc_aggregator a{g, managed_locs->get_location_ids(), e};
  const auto size = request_type::expected_size(std::forward<Callback>(c),
                                                std::forward<Function>(f));
  new(a.allocate(size)) request_type{std::forward<Callback>(c),
                                     *new_g,
                                     std::move(managed_locs),
                                     std::forward<Function>(f)};
}

//////////////////////////////////////////////////////////////////////
/// @brief Calls @ref construct_range_impl() on processes @p pids.
///
/// @param pids    Destination processes.
/// @param c       Callback to pass a pointer to the constructed object.
/// @param gid     Destination gang id.
/// @param r       Destination locations of @p g.
/// @param e       Caller epoch.
/// @param new_gid New gang id.
/// @param f       Object creation function.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename ProcessRange,
         typename Callback,
         typename T,
         typename Function>
void call_construct_range_impl(ProcessRange&& pids,
                               Callback&& c,
                               const gang_md::id gid,
                               location_range_wrapper<T> const& r,
                               const context::epoch_type e,
                               const gang_md::id new_gid,
                               Function&& f)
{
  rpc(&construct_range_impl<decltype(c), T, decltype(f)>,
      std::forward<ProcessRange>(pids),
      std::forward<Callback>(c),
      gang_retriever{gid}, r, e, new_gid, std::forward<Function>(f));
}

//////////////////////////////////////////////////////////////////////
/// @brief Target for forwarding the request to @ref construct_range_impl().
///
/// @param c Callback to pass a pointer to the constructed object.
/// @param g Destination gang metadata.
/// @param r Destination locations of @p g.
/// @param e Caller epoch.
/// @param f Object creation function.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename Callback, typename T, typename Function>
void fwd_construct_range_impl(Callback&& c,
                              gang_md& g,
                              location_range_wrapper<T> const& r,
                              const context::epoch_type e,
                              Function&& f)
{
  const auto pid = g.get_process_id(*std::begin(r));
  if (runqueue::get_process_id()==pid) {
    construct_range_impl(std::forward<Callback>(c),
                         g, r, e,
                         invalid_gang_id, std::forward<Function>(f));
  }
  else {
    call_construct_range_impl(pid,
                              std::forward<Callback>(c),
                              g.get_id(), r, e,
                              invalid_gang_id, std::forward<Function>(f));
  }
}

//////////////////////////////////////////////////////////////////////
/// @brief Calls @c promise<rmi_handle::reference>::set_value() from location 0.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
class call_promise_set_value
{
public:
  using promise_type = promise<rmi_handle::reference>;

private:
  promise_type m_promise;

public:
  explicit call_promise_set_value(promise_type p)
  : m_promise(std::move(p))
  { }

  template<typename T>
  void operator()(T* const t)
  {
    if (t->get_location_id()==0)
      m_promise.set_value(t->get_rmi_handle());
  }

  void define_type(typer& t)
  { t.member(m_promise); }
};

//////////////////////////////////////////////////////////////////////
/// @brief Calls the constructor of @p T with @p Args.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename T>
struct create_object
{
  template<typename... Args>
  T* operator()(Args&&... args) const
  { return new T(std::forward<Args>(args)...); }
};

//////////////////////////////////////////////////////////////////////
/// @brief Creates a function object used to construct an instance @p T with
///        @p args.
///
/// @ingroup requestBuildingBlock
//////////////////////////////////////////////////////////////////////
template<typename T, typename... Args>
auto call_constructor(Args&&... args)
  -> decltype(std::bind(create_object<T>{}, std::forward<Args>(args)...))
{
  return std::bind(create_object<T>{}, std::forward<Args>(args)...);
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates a distributed object through @p f in a new single location
///        gang over location @p lid of gang @p g.
///
/// @param c   Callback to pass a pointer to the constructed object.
/// @param gid Destination gang id.
/// @param g   Destination gang metadata.
/// @param lid Destination location id.
/// @param e   Caller epoch.
/// @param f   Object creation function.
///
/// @ingroup requestBuildingBlock
////////////////////////////////////////////////////////////////////
template<typename Callback, typename Function>
void construct_single(Callback&& c,
                      gang_md::id gid,
                      gang_md* g,
                      const location_md::id lid,
                      const context::epoch_type e,
                      Function&& f)
{
  using request_type = single_location_construct_request<
                         typename std::decay<Callback>::type,
                         typename std::decay<Function>::type>;
  const auto size = request_type::expected_size(std::forward<Callback>(c),
                                                std::forward<Function>(f));
  if (g) {
    location_rpc_aggregator a{*g, lid, e};
    new(a.allocate(size)) request_type{std::forward<Callback>(c),
                                       std::forward<Function>(f)};
  }
  else {
    location_rpc_aggregator a{gid, lid, e};
    new(a.allocate(size)) request_type{std::forward<Callback>(c),
                                       std::forward<Function>(f)};
  }
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates a distributed object through @p f in a new gang over all the
///        locations of the gang associated with @p ctx that are on shared
///        memory.
///
/// @param c Callback to pass a pointer to the constructed object.
/// @param g Destination gang metadata.
/// @param e Caller epoch.
/// @param f Object creation function.
///
/// @ingroup requestBuildingBlock
////////////////////////////////////////////////////////////////////
template<typename Callback, typename Function>
void construct_neighbors(Callback&& c,
                         gang_md& g,
                         const context::epoch_type e,
                         Function&& f)
{
  const auto nmanaged = g.local_size();
  managed_locations_rpc_aggregator a{g, e};
  if (nmanaged==1) {
    using request_type = single_location_construct_request<
                           typename std::decay<Callback>::type,
                           typename std::decay<Function>::type>;

    const std::size_t size =
      request_type::expected_size(std::forward<Callback>(c),
                                  std::forward<Function>(f));
    new(a.allocate(size)) request_type{std::forward<Callback>(c),
                                       std::forward<Function>(f)};
  }
  else {
    using request_type = all_locs_construct_request<
                           typename std::decay<Callback>::type,
                           typename std::decay<Function>::type,
                           true>;

    auto* const ng         = new gang_md{g.get_id(), nmanaged};
    const std::size_t size =
      request_type::expected_size(std::forward<Callback>(c),
                                  std::forward<Function>(f));
    new(a.allocate(size)) request_type{std::forward<Callback>(c),
                                       *ng,
                                       std::forward<Function>(f)};
  }
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates a distributed object through @p f in a new gang over all
///        locations of the gang of @p h.
///
/// @param c   Callback to pass a pointer to the constructed object.
/// @param gid Destination gang id.
/// @param g   Destination gang metadata.
/// @param e   Caller epoch.
/// @param f   Object creation function.
///
/// @ingroup requestBuildingBlock
///
/// @todo Everything is forwarded to the gang id owner process. Needs to be
///       avoided for scalability.
/// @todo Optimize for gangs of size 1.
////////////////////////////////////////////////////////////////////
template<typename Callback, typename Function>
void construct_all(Callback&& c,
                   const gang_md::id gid,
                   gang_md* g,
                   const context::epoch_type e,
                   Function&& f)
{
  const auto pid = gang_md_registry::id_owner(gid);
  if (runqueue::get_process_id()==pid) {
    // owner
    if (!g)
      STAPL_RUNTIME_ERROR("Gang metadata should be available at the owner.");
    construct_all_impl(std::forward<Callback>(c),
                       *g, e,
                       invalid_gang_id, std::forward<Function>(f));
  }
  else {
    // forward request to the owner process
    call_construct_all_impl(pid,
                            std::forward<Callback>(c),
                            gid, e,
                            invalid_gang_id, std::forward<Function>(f));
  }
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates a distributed object through @p f in a new gang over the
///        range @p r of the locations of the gang associated with @p g.
///
/// @param c   Callback to pass a pointer to the constructed object.
/// @param gid Destination gang id.
/// @param g   Destination gang metadata.
/// @param r   Destination locations of @p g.
/// @param e   Caller epoch.
/// @param f   Object creation function.
///
/// @ingroup requestBuildingBlock
////////////////////////////////////////////////////////////////////
template<typename Callback, typename T, typename Function>
void construct_range(Callback&& c,
                     gang_md::id gid,
                     gang_md* g,
                     location_range_wrapper<T> const& r,
                     const context::epoch_type e,
                     Function&& f)
{
  if (r.size()==1) {
    construct_single(std::forward<Callback>(c),
                     gid,
                     g,
                     *std::begin(r),
                     e,
                     std::forward<Function>(f));
  }
  else {
    if (!g) {
      // gang metadata unavailable; forward to owner
      const auto pid = gang_md_registry::id_owner(gid);
      if (pid==runqueue::get_process_id())
        STAPL_RUNTIME_ERROR("Gang metadata should be available at the owner.");
      rpc(&fwd_construct_range_impl<decltype(c), T, decltype(f)>,
          pid,
          std::forward<Callback>(c),
          gang_retriever{gid}, r, e, std::forward<Function>(f));
    }
    else {
      fwd_construct_range_impl(std::forward<Callback>(c),
                               *g, r, e, std::forward<Function>(f));
    }
  }
}

} // namespace runtime

//////////////////////////////////////////////////////////////////////
/// @brief Creates an object of type @p T in a new gang over all the locations
///        of the current gang that are on shared memory.
///
/// @param args Arguments to pass to the constructor of @p T.
///
/// @return A @ref future object with an @ref rmi_handle::reference to the
///         constructed object.
///
/// @ingroup distributedObjects
//////////////////////////////////////////////////////////////////////
template<typename T, typename... Args>
future<rmi_handle::reference> construct(neighbor_locations_t,
                                        Args&&... args)
{
  using namespace runtime;

  auto& ctx = this_context::get();

  promise<rmi_handle::reference> p;
  auto f = p.get_future();
  {
    STAPL_RUNTIME_PROFILE("construct(neighbor_locations)",
                          (primitive_traits::non_blocking |
                           primitive_traits::p2m          |
                           primitive_traits::comm));
    construct_neighbors(call_promise_set_value{std::move(p)},
                        ctx.get_gang_md(),
                        ctx.get_epoch(),
                        call_constructor<T>(std::forward<Args>(args)...));
  }

  scheduling_point(ctx);
  return f;
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates an object of type @p T in a new gang over the range @p r of
///        the locations of the current gang.
///
/// @warning @p r may only contain locations of the current gang.
///
/// @param r    Location id range of the current that the new gang will be
///             created in.
/// @param args Arguments to pass to the constructor of @p T.
///
/// @return A @ref future object with an @ref rmi_handle::reference to the
///         constructed object.
///
/// @ingroup distributedObjects
//////////////////////////////////////////////////////////////////////
template<typename T, typename U, typename... Args>
future<rmi_handle::reference> construct(location_range_wrapper<U> const& r,
                                        Args&&... args)
{
  using namespace runtime;

  auto& ctx = this_context::get();

  STAPL_RUNTIME_ASSERT_MSG(!r.has_invalid(ctx.get_gang_md().size()),
                           "Range contains invalid location id.");

  promise<rmi_handle::reference> p;
  auto f = p.get_future();
  {
    STAPL_RUNTIME_PROFILE("construct(range)", (primitive_traits::non_blocking |
                                               primitive_traits::p2m          |
                                               primitive_traits::comm));
    construct_range(call_promise_set_value{std::move(p)},
                    ctx.get_gang_id(),
                    &(ctx.get_gang_md()),
                    r,
                    ctx.get_epoch(),
                    call_constructor<T>(std::forward<Args>(args)...));
  }

  scheduling_point(ctx);
  return f;
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates an object of type @p T in a new gang over the range @p r of
///        the locations of the current gang and calls @p f with the object
///        pointer.
///
/// @warning @p r may only contain locations of the current gang.
///
/// @param f    Function to pass the pointer of the constructed object.
/// @param r    Location id range of the current that the new gang will be
///             created in.
/// @param args Arguments to pass to the constructor of @p T.
///
/// @ingroup distributedObjects
//////////////////////////////////////////////////////////////////////
template<typename T, typename Function, typename U, typename... Args>
void async_construct(Function&& f,
                     location_range_wrapper<U> const& r,
                     Args&&... args)
{
  using namespace runtime;

  auto& ctx = this_context::get();

  STAPL_RUNTIME_ASSERT_MSG(!r.has_invalid(ctx.get_gang_md().size()),
                           "Range contains invalid location id.");

  {
    STAPL_RUNTIME_PROFILE("async_construct(range)",
                          (primitive_traits::non_blocking |
                           primitive_traits::p2m          |
                           primitive_traits::comm));
    construct_range(std::forward<Function>(f),
                    ctx.get_gang_id(),
                    &(ctx.get_gang_md()),
                    r,
                    ctx.get_epoch(),
                    call_constructor<T>(std::forward<Args>(args)...));
  }

  scheduling_point(ctx);
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates an object of type @p T in a new gang over all the locations
///        of the gang of @p h.
///
/// @param h    Target distributed object handle.
/// @param args Arguments to pass to the constructor of @p T.
///
/// @return A @ref future object with an @ref rmi_handle::reference to the
///         constructed object.
///
/// @ingroup distributedObjects
//////////////////////////////////////////////////////////////////////
template<typename T, typename... Args>
future<rmi_handle::reference> construct(rmi_handle::const_reference const& h,
                                        all_locations_t,
                                        Args&&... args)
{
  using namespace runtime;

  auto& ctx = this_context::get();

  promise<rmi_handle::reference> p;
  auto f = p.get_future();
  {
    STAPL_RUNTIME_PROFILE("construct(handle,all_locations)",
                          (primitive_traits::non_blocking |
                           primitive_traits::p2m          |
                           primitive_traits::comm));
    construct_all(call_promise_set_value{std::move(p)},
                  h.get_gang_id(),
                  find_target_gang_md(ctx, h),
                  h.get_epoch(),
                  call_constructor<T>(std::forward<Args>(args)...));
  }

  scheduling_point(ctx);
  return f;
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates an object of type @p T in a new gang over all the locations
///        of the gang of @p h and calls @p f with the object pointer.
///
/// @param f    Function to pass the pointer of the constructed object.
/// @param h    Target distributed object handle.
/// @param args Arguments to pass to the constructor of @p T.
///
/// @ingroup distributedObjects
//////////////////////////////////////////////////////////////////////
template<typename T, typename Function, typename... Args>
void async_construct(Function&& f,
                     rmi_handle::const_reference const& h,
                     all_locations_t,
                     Args&&... args)
{
  using namespace runtime;

  auto& ctx = this_context::get();

  {
    STAPL_RUNTIME_PROFILE("async_construct(handle,all_locations)",
                          (primitive_traits::non_blocking |
                           primitive_traits::p2m          |
                           primitive_traits::comm));
    construct_all(std::forward<Function>(f),
                  h.get_gang_id(),
                  find_target_gang_md(ctx, h),
                  h.get_epoch(),
                  call_constructor<T>(std::forward<Args>(args)...));
  }

  scheduling_point(ctx);
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates an object of type @p T in a new gang over the range @p r of
///        the locations of the gang of @p h.
///
/// @warning @p r may only contain locations of the gang of the object
///          associated with the @ref rmi_handle::const_reference.
///
/// @param h    Target distributed object handle.
/// @param r    Location id range of the gang of @p h that the new gang will be
///             created in.
/// @param args Arguments to pass to the constructor of @p T.
///
/// @return A @ref future object with an @ref rmi_handle::reference to the
///         constructed object.
///
/// @ingroup distributedObjects
//////////////////////////////////////////////////////////////////////
template<typename T, typename U, typename... Args>
future<rmi_handle::reference> construct(rmi_handle::const_reference const& h,
                                        location_range_wrapper<U> const& r,
                                        Args&&... args)
{
  using namespace runtime;

  auto& ctx = this_context::get();

  STAPL_RUNTIME_ASSERT_MSG(!r.has_invalid(h.get_num_locations()),
                           "Range contains invalid location id.");

  promise<rmi_handle::reference> p;
  auto f = p.get_future();
  {
    STAPL_RUNTIME_PROFILE("construct(handle,range)",
                          (primitive_traits::non_blocking |
                           primitive_traits::p2m          |
                           primitive_traits::comm));
    construct_range(call_promise_set_value{std::move(p)},
                    h.get_gang_id(),
                    find_target_gang_md(ctx, h),
                    r,
                    h.get_epoch(),
                    call_constructor<T>(std::forward<Args>(args)...));
  }

  scheduling_point(ctx);
  return f;
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates an object of type @p T in a new gang over the range @p r of
///        the locations of the gang of @p h and calls @p f with the object
///        pointer.
///
/// @warning @p r may only contain locations of the gang of the object
///          associated with the @ref rmi_handle::const_reference.
///
/// @param f    Function to pass the pointer of the constructed object.
/// @param h    Target distributed object handle.
/// @param r    Location id range of the gang of @p h that the new gang will be
///             created in.
/// @param args Arguments to pass to the constructor of @p T.
///
/// @ingroup distributedObjects
//////////////////////////////////////////////////////////////////////
template<typename T, typename Function, typename U, typename... Args>
void async_construct(Function&& f,
                     rmi_handle::const_reference const& h,
                     location_range_wrapper<U> const& r,
                     Args&&... args)
{
  using namespace runtime;

  auto& ctx = this_context::get();

  STAPL_RUNTIME_ASSERT_MSG(!r.has_invalid(h.get_num_locations()),
                           "Range contains invalid location id.");

  {
    STAPL_RUNTIME_PROFILE("async_construct(handle,range)",
                          (primitive_traits::non_blocking |
                           primitive_traits::p2m          |
                           primitive_traits::comm));
    construct_range(std::forward<Function>(f),
                    h.get_gang_id(),
                    find_target_gang_md(ctx, h),
                    r,
                    h.get_epoch(),
                    call_constructor<T>(std::forward<Args>(args)...));
  }

  scheduling_point(ctx);
}

//////////////////////////////////////////////////////////////////////
/// @brief Creates an object of type @p T in a new gang over the level @p l of
///        the hierarchy.
///
/// @param l    Target hierarchy level.
/// @param args Arguments to pass to the constructor of @p T.
///
/// @return A @ref future object with an @ref rmi_handle::reference to the
///         constructed object.
///
/// @ingroup distributedObjects
//////////////////////////////////////////////////////////////////////
template<typename T, typename... Args>
future<rmi_handle::reference> construct(level const& l,
                                        Args&&... args)
{
  using namespace runtime;

  auto& ctx = this_context::get();

  if (l.get_level()==level::invalid)
    STAPL_RUNTIME_ERROR("construct(level): Invalid level requested.");

  promise<rmi_handle::reference> p;
  auto f = p.get_future();
  {
    STAPL_RUNTIME_PROFILE("construct(level)", (primitive_traits::non_blocking |
                                               primitive_traits::p2m          |
                                               primitive_traits::comm));

    call_promise_set_value callback{std::move(p)};
    auto cons_f = call_constructor<T>(std::forward<Args>(args)...);

    if (l.get_level()==level::current) {
      if (l.is_restricted()) {
        // all locations of the current level that are on shared memory
        construct_neighbors(std::move(callback),
                            ctx.get_gang_md(),
                            ctx.get_epoch(),
                            std::move(cons_f));
      }
      else {
        // all locations of the current level
        STAPL_RUNTIME_ERROR("Unrestricted current_level not supported yet.");
      }
    }
    else if (l.get_level()==level::max) {
      if (l.is_restricted()) {
        // maximum level, 1 location
        construct_single(std::move(callback),
                         ctx.get_gang_id(),
                         &(ctx.get_gang_md()),
                         ctx.get_location_id(),
                         ctx.get_epoch(),
                         std::move(cons_f));
      }
      else {
        // maximum level, all locations
        STAPL_RUNTIME_ERROR("Unrestricted lowest level not supported yet.");
      }
    }
    else {
      // lower level
      if (l.is_restricted()) {
        construct_neighbors(std::move(callback),
                            ctx.get_gang_md(),
                            ctx.get_epoch(),
                            std::move(cons_f));
      }
      else {
        STAPL_RUNTIME_ERROR("Unrestricted lower level not supported yet.");
      }
    }
  }

  scheduling_point(ctx);
  return f;
}

} // namespace stapl

#endif