// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
/******************************************************************************
 * Copyright © 2014-2019 The SuperNET Developers.                             *
 *                                                                            *
 * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at                  *
 * the top-level directory of this distribution for the individual copyright  *
 * holder information and the developer policies on copyright and licensing.  *
 *                                                                            *
 * Unless otherwise agreed in a custom licensing agreement, no part of the    *
 * SuperNET software, including this file may be copied, modified, propagated *
 * or distributed except according to the terms contained in the LICENSE file *
 *                                                                            *
 * Removal or modification of this copyright notice is prohibited.            *
 *                                                                            *
 ******************************************************************************/
#include "cc/eval.h"
#include "crosschain.h"
#include "importcoin.h"
#include "main.h"
#include "notarizationdb.h"
#include "merkleblock.h"
#include "cc/CCinclude.h"
/*
 * The crosschain workflow.
 *
 * 3 chains, A, B, and HUSH. We would like to prove TX on B.
 * There is a notarization, nA0, which will include TX via an MoM.
 * The notarization nA0 must fall between 2 notarizations of B,
 * ie, nB0 and nB1. An MoMoM including this range is propagated to
 * B in notarization receipt (backnotarization) bnB2.
 *
 * A:                 TX   bnA0
 *                     \   /
 * HUSH:      nB0        nA0     nB1      nB2
 *              \                 \       \
 * B:          bnB0              bnB1     bnB2
 */

// XXX: There are potential crashes wherever we access chainActive without a lock,
// because it might be disconnecting blocks at the same time.
// TODO: this assumes a blocktime of 75 seconds for HUSH and 60 seconds for other chains
// DragonX: the HUSH3 (1152) branch is dead — SMART_CHAIN_SYMBOL is always "DRAGONX", and at
// static-init time it is empty, so this already always resolved to 1440. Pinned to 1440.
int NOTARISATION_SCAN_LIMIT_BLOCKS = 1440;
CBlockIndex *hush_getblockindex(uint256 hash);

/* On HUSH */
uint256 CalculateProofRoot(const char* symbol, uint32_t targetCCid, int hushHeight,
        std::vector<uint256> &moms, uint256 &destNotarizationTxid)
{
    /*
     * Notaries don't wait for confirmation on HUSH before performing a backnotarization,
     * but we need a determinable range that will encompass all merkle roots. Include MoMs
     * including the block height of the last notarization until the height before the
     * previous notarization.
     *
     *    hushHeight      notarizations-0      notarizations-1
     *                         *********************|
     *        > scan backwards >
     */

    if (targetCCid < 2)
        return uint256();

    if (hushHeight < 0 || hushHeight > chainActive.Height())
        return uint256();

    int seenOwnNotarizations = 0, i = 0;

    int authority = GetSymbolAuthority(symbol);
    std::set<uint256> tmp_moms;

    for (i=0; i<NOTARISATION_SCAN_LIMIT_BLOCKS; i++) {
        if (i > hushHeight) break;
        NotarizationsInBlock notarizations;
        uint256 blockHash = *chainActive[hushHeight-i]->phashBlock;
        if (!GetBlockNotarizations(blockHash, notarizations))
            continue;

        // See if we have an own notarization in this block
        BOOST_FOREACH(Notarization& nota, notarizations) {
            if (strcmp(nota.second.symbol, symbol) == 0)
            {
                seenOwnNotarizations++;
                if (seenOwnNotarizations == 1)
                    destNotarizationTxid = nota.first;
                else if (seenOwnNotarizations == 7)
                    goto end;
                //break;
            }
        }

        if (seenOwnNotarizations >= 1) {
            BOOST_FOREACH(Notarization& nota, notarizations) {
                if (GetSymbolAuthority(nota.second.symbol) == authority)
                    if (nota.second.ccId == targetCCid) {
                      tmp_moms.insert(nota.second.MoM);
                      //fprintf(stderr, "added mom: %s\n",nota.second.MoM.GetHex().data());
                    }
            }
        }
    }

    // Not enough own notarizations found to return determinate MoMoM
    destNotarizationTxid = uint256();
    moms.clear();
    return uint256();

end:
    // add set to vector. Set makes sure there are no dupes included. 
    moms.clear();
    std::copy(tmp_moms.begin(), tmp_moms.end(), std::back_inserter(moms));
    //fprintf(stderr, "SeenOwnNotarizations.%i moms.size.%li blocks scanned.%i\n",seenOwnNotarizations, moms.size(), i);
    return GetMerkleRoot(moms);
}


/*
 * Get a notarization from a given height
 *
 * Will scan notarizations leveldb up to a limit
 */
template <typename IsTarget>
int ScanNotarizationsFromHeight(int nHeight, const IsTarget f, Notarization &found)
{
    int limit = std::min(nHeight + NOTARISATION_SCAN_LIMIT_BLOCKS, chainActive.Height());
    int start = std::max(nHeight, 1);

    for (int h=start; h<limit; h++) {
        NotarizationsInBlock notarizations;

        if (!GetBlockNotarizations(*chainActive[h]->phashBlock, notarizations))
            continue;

        BOOST_FOREACH(found, notarizations) {
            if (f(found)) {
                return h;
            }
        }
    }
    return 0;
}


/* On HUSH */
TxProof GetCrossChainProof(const uint256 txid, const char* targetSymbol, uint32_t targetCCid,
        const TxProof assetChainProof, int32_t offset)
{
    /*
     * Here we are given a proof generated by an assetchain A which goes from given txid to
     * an assetchain MoM. We need to go from the notarizationTxid for A to the MoMoM range of the
     * backnotarization for B (given by hushHeight of notarization), find the MoM within the MoMs for
     * that range, and finally extend the proof to lead to the MoMoM (proof root).
     */
    EvalRef eval;
    uint256 MoM = assetChainProof.second.Exec(txid);

    // Get a Hush height for given notarization Txid
    int hushHeight;
    {
        CTransaction sourceNotarization;
        uint256 hashBlock;
        CBlockIndex blockIdx;
        if (!eval->GetTxConfirmed(assetChainProof.first, sourceNotarization, blockIdx))
            throw std::runtime_error("Notarization not found");
        hushHeight = blockIdx.GetHeight();
    }

    // We now have a hushHeight of the notarization from chain A. So we know that a MoM exists
    // at that height.
    // If we call CalculateProofRoot with that height, it'll scan backwards, until it finds
    // a notarization from B, and it might not include our notarization from A
    // at all. So, the thing we need to do is scan forwards to find the notarization for B,
    // that is inclusive of A.
    Notarization nota;
    auto isTarget = [&](Notarization &nota) {
        return strcmp(nota.second.symbol, targetSymbol) == 0;
    };
    hushHeight = ScanNotarizationsFromHeight(hushHeight, isTarget, nota);
    if (!hushHeight)
        throw std::runtime_error("Cannot find notarization for target inclusive of source");
        
    if ( offset != 0 )
        hushHeight += offset;

    // Get MoMs for Hush height and symbol
    std::vector<uint256> moms;
    uint256 targetChainNotarizationTxid;
    uint256 MoMoM = CalculateProofRoot(targetSymbol, targetCCid, hushHeight, moms, targetChainNotarizationTxid);
    if (MoMoM.IsNull())
        throw std::runtime_error("No MoMs found");

    // Find index of source MoM in MoMoM
    int nIndex;
    for (nIndex=0; nIndex<moms.size(); nIndex++) {
        if (moms[nIndex] == MoM)
            goto cont;
    }
    throw std::runtime_error("Couldn't find MoM within MoMoM set");
cont:

    // Create a branch
    std::vector<uint256> vBranch;
    {
        CBlock fakeBlock;
        for (int i=0; i<moms.size(); i++) {
            CTransaction fakeTx;
            // first value in CTransaction memory is it's hash
            memcpy((void*)&fakeTx, moms[i].begin(), 32);
            fakeBlock.vtx.push_back(fakeTx);
        }
        vBranch = fakeBlock.GetMerkleBranch(nIndex);
    }

    // Concatenate branches
    MerkleBranch newBranch = assetChainProof.second;
    newBranch << MerkleBranch(nIndex, vBranch);

    // Check proof
    if (newBranch.Exec(txid) != MoMoM)
        throw std::runtime_error("Proof check failed");

    return std::make_pair(targetChainNotarizationTxid,newBranch);
}

bool IsSameAssetChain(const Notarization &nota) {
    return strcmp(nota.second.symbol, SMART_CHAIN_SYMBOL) == 0;
};

/* On assetchain */
bool GetNextBacknotarization(uint256 hushNotarizationTxid, Notarization &out)
{
    /*
     * Here we are given a txid, and a proof.
     * We go from the HUSH notarization txid to the backnotarization,
     * then jump to the next backnotarization, which contains the corresponding MoMoM.
     */
    Notarization bn;
    if (!GetBackNotarization(hushNotarizationTxid, bn))
        return false;

    // Need to get block height of that backnotarization
    EvalRef eval;
    CBlockIndex block;
    CTransaction tx;
    if (!eval->GetTxConfirmed(bn.first, tx, block)){
        fprintf(stderr, "Can't get height of backnotarization, this should not happen\n");
        return false;
    }

    return (bool) ScanNotarizationsFromHeight(block.GetHeight()+1, &IsSameAssetChain, out);
}

bool CheckMoMoM(uint256 hushNotarizationHash, uint256 momom)
{
    /*
     * Given a notarization hash and an MoMoM. Backnotarizations may arrive out of order
     * or multiple in the same block. So dereference the notarization hash to the corresponding
     * backnotarization and scan around the hushheight to see if the MoMoM is a match.
     * This is a sledgehammer approach...
     */

    Notarization bn;
    if (!GetBackNotarization(hushNotarizationHash, bn))
        return false;

    // Need to get block height of that backnotarization
    EvalRef eval;
    CBlockIndex block;
    CTransaction tx;
    if (!eval->GetTxConfirmed(bn.first, tx, block)){
        fprintf(stderr, "Can't get height of backnotarization, this should not happen\n");
        return false;
    }

    Notarization nota;
    auto checkMoMoM = [&](Notarization &nota) {
        return nota.second.MoMoM == momom;
    };

    return (bool) ScanNotarizationsFromHeight(block.GetHeight()-100, checkMoMoM, nota);

}

/*
 * On assetchain
 * in: txid
 * out: pair<notarizationTxHash,merkleBranch>
 */

TxProof GetAssetchainProof(uint256 hash,CTransaction burnTx)
{
    int nIndex;
    CBlockIndex* blockIndex;
    Notarization nota;
    std::vector<uint256> branch;
    {
        uint256 blockHash;
        CTransaction tx;
        if (!GetTransaction(hash, tx, blockHash, true))
            throw std::runtime_error("cannot find transaction");

        if (blockHash.IsNull())
            throw std::runtime_error("tx still in mempool");

        blockIndex = hush_getblockindex(blockHash);
        int h = blockIndex->GetHeight();
        // The assumption here is that the first notarization for a height GTE than
        // the transaction block height will contain the corresponding MoM. If there
        // are sequence issues with the notarizations this may fail.
        auto isTarget = [&](Notarization &nota) {
            if (!IsSameAssetChain(nota)) return false;
            return nota.second.height >= blockIndex->GetHeight();
        };
        if (!ScanNotarizationsFromHeight(blockIndex->GetHeight(), isTarget, nota))
            throw std::runtime_error("backnotarization not yet confirmed");

        // index of block in MoM leaves
        nIndex = nota.second.height - blockIndex->GetHeight();
    }

    // build merkle chain from blocks to MoM
    {
        std::vector<uint256> leaves, tree;
        for (int i=0; i<nota.second.MoMDepth; i++) {
            uint256 mRoot = chainActive[nota.second.height - i]->hashMerkleRoot;
            leaves.push_back(mRoot);
        }
        bool fMutated;
        BuildMerkleTree(&fMutated, leaves, tree);
        branch = GetMerkleBranch(nIndex, leaves.size(), tree);

        // Check branch
        uint256 ourResult = SafeCheckMerkleBranch(blockIndex->hashMerkleRoot, branch, nIndex);
        if (nota.second.MoM != ourResult)
            throw std::runtime_error("Failed merkle block->MoM");
    }

    // Now get the tx merkle branch
    {
        CBlock block;

        if (fHavePruned && !(blockIndex->nStatus & BLOCK_HAVE_DATA) && blockIndex->nTx > 0)
            throw std::runtime_error("Block not available (pruned data)");

        if(!ReadBlockFromDisk(block, blockIndex,1))
            throw std::runtime_error("Can't read block from disk");

        // Locate the transaction in the block
        int nTxIndex;
        for (nTxIndex = 0; nTxIndex < (int)block.vtx.size(); nTxIndex++)
            if (block.vtx[nTxIndex].GetHash() == hash)
                break;

        if (nTxIndex == (int)block.vtx.size())
            throw std::runtime_error("Error locating tx in block");

        std::vector<uint256> txBranch = block.GetMerkleBranch(nTxIndex);

        // Check branch
        if (block.hashMerkleRoot != CBlock::CheckMerkleBranch(hash, txBranch, nTxIndex))
            throw std::runtime_error("Failed merkle tx->block");

        // concatenate branches
        nIndex = (nIndex << txBranch.size()) + nTxIndex;
        branch.insert(branch.begin(), txBranch.begin(), txBranch.end());
    }

    // Check the proof
    if (nota.second.MoM != CBlock::CheckMerkleBranch(hash, branch, nIndex))
        throw std::runtime_error("Failed validating MoM");

    // All done!
    CDataStream ssProof(SER_NETWORK, PROTOCOL_VERSION);
    return std::make_pair(nota.second.txHash, MerkleBranch(nIndex, branch));
}
