/*
 *
 * Copyright (c) 2006 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "qpid/acl/AclValidator.h"
#include "qpid/acl/AclData.h"
#include "qpid/acl/AclLexer.h"
#include "qpid/Exception.h"
#include "qpid/log/Statement.h"
#include "qpid/sys/IntegerTypes.h"
#include "qpid/StringUtils.h"
#include <boost/lexical_cast.hpp>
#include <boost/bind.hpp>
#include <numeric>
#include <sstream>
#include <iomanip>

namespace qpid {
namespace acl {

    AclValidator::IntPropertyType::IntPropertyType(int64_t i,int64_t j) : min(i), max(j){
    }

    bool AclValidator::IntPropertyType::validate(const std::string& val) {
        int64_t v;
        try
        {
            v = boost::lexical_cast<int64_t>(val);
        }catch(const boost::bad_lexical_cast&){
            return 0;
        }

        if (v < min || v >= max){
            return 0;
        }else{
            return 1;
        }
    }

    std::string AclValidator::IntPropertyType::allowedValues() {
        return "values should be between " +
            boost::lexical_cast<std::string>(min) + " and " +
            boost::lexical_cast<std::string>(max);
    }

    AclValidator::EnumPropertyType::EnumPropertyType(std::vector<std::string>& allowed): values(allowed){
    }

    bool AclValidator::EnumPropertyType::validate(const std::string& val) {
        for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); ++itr ){
            if (val.compare(*itr) == 0){
                return 1;
            }
        }

        return 0;
    }

    std::string AclValidator::EnumPropertyType::allowedValues() {
        std::ostringstream oss;
        oss << "possible values are one of { ";
        for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); itr++ ){
            oss << "'" << *itr << "' ";
        }
        oss << "}";
        return oss.str();
    }

    AclValidator::AclValidator() : propertyIndex(1) {
        validators.insert(Validator(acl::SPECPROP_MAXQUEUESIZELOWERLIMIT,
                          boost::shared_ptr<PropertyType>(
                            new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT,
                          boost::shared_ptr<PropertyType>(
                            new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT,
                                    boost::shared_ptr<PropertyType>(
                                        new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT,
                                    boost::shared_ptr<PropertyType>(
                                        new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXFILESIZELOWERLIMIT,
                                    boost::shared_ptr<PropertyType>(
                                        new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXFILESIZEUPPERLIMIT,
                                    boost::shared_ptr<PropertyType>(
                                        new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXFILECOUNTLOWERLIMIT,
                                    boost::shared_ptr<PropertyType>(
                                        new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXFILECOUNTUPPERLIMIT,
                                    boost::shared_ptr<PropertyType>(
                                        new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXPAGESLOWERLIMIT,
                          boost::shared_ptr<PropertyType>(
                            new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXPAGESUPPERLIMIT,
                          boost::shared_ptr<PropertyType>(
                            new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXPAGEFACTORLOWERLIMIT,
                          boost::shared_ptr<PropertyType>(
                            new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        validators.insert(Validator(acl::SPECPROP_MAXPAGEFACTORUPPERLIMIT,
                          boost::shared_ptr<PropertyType>(
                            new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));

        std::string policyTypes[] = {"ring", "self-destruct", "reject"};
        std::vector<std::string> v(policyTypes, policyTypes + sizeof(policyTypes) / sizeof(std::string));
        validators.insert(Validator(acl::SPECPROP_POLICYTYPE,
                          boost::shared_ptr<PropertyType>(
                            new EnumPropertyType(v))));

        // Insert allowed action/object/property sets (generated manually 20140712)
#define RP registerProperties
        RP( "Broker::getTimestampConfig",
            "User querying message timestamp setting ",
            ACT_ACCESS,  OBJ_BROKER);
        RP( "ExchangeHandlerImpl::query",
            "AMQP 0-10 protocol received 'query'     ",
            ACT_ACCESS,  OBJ_EXCHANGE, "name");
        RP( "ExchangeHandlerImpl::bound",
            "AMQP 0-10 query binding                 ",
            ACT_ACCESS,  OBJ_EXCHANGE, "name queuename routingkey");
        RP( "ExchangeHandlerImpl::declare",
            "AMQP 0-10 exchange declare              ",
            ACT_ACCESS,  OBJ_EXCHANGE, "name type alternate durable autodelete");
        RP( "Authorise::access",
            "AMQP 1.0 exchange access                ",
            ACT_ACCESS,  OBJ_EXCHANGE, "name type durable");
        RP( "Authorise::access",
            "AMQP 1.0 node resolution                ",
            ACT_ACCESS,  OBJ_EXCHANGE, "name");
        RP( "ManagementAgent::handleMethodRequest",
            "Management method request               ",
            ACT_ACCESS,  OBJ_METHOD, "name schemapackage schemaclass");
        RP( "ManagementAgent::authorizeAgentMessage",
            "Management agent method request         ",
            ACT_ACCESS,  OBJ_METHOD, "name schemapackage schemaclass");
        RP( "ManagementAgent::handleGetQuery",
            "Management agent query                  ",
            ACT_ACCESS,  OBJ_QUERY, "name schemaclass");
        RP( "Broker::queryQueue",
            "QMF 'query queue' method                ",
            ACT_ACCESS,  OBJ_QUEUE, "name");
        RP( "QueueHandlerImpl::query",
            "AMQP 0-10 query                         ",
            ACT_ACCESS,  OBJ_QUEUE, "name");
        RP( "QueueHandlerImpl::declare",
            "AMQP 0-10 queue declare                 ",
            ACT_ACCESS,  OBJ_QUEUE, "name alternate durable exclusive autodelete policytype maxqueuecount maxqueuesize");
        RP( "Authorise::access",
            "AMQP 1.0 queue access                   ",
            ACT_ACCESS,  OBJ_QUEUE, "name alternate durable exclusive autodelete policytype maxqueuecount maxqueuesize");
        RP( "Authorise::access",
            "AMQP 1.0 node resolution                ",
            ACT_ACCESS,  OBJ_QUEUE, "name");
        RP( "Broker::bind",
            "AMQP 0-10 or QMF bind request           ",
            ACT_BIND,    OBJ_EXCHANGE, "name queuename routingkey");
        RP( "Authorise::outgoing",
            "AMQP 1.0 new outgoing link from exchange",
            ACT_BIND,    OBJ_EXCHANGE, "name queuename routingkey");
        RP( "MessageHandlerImpl::subscribe",
            "AMQP 0-10 subscribe request             ",
            ACT_CONSUME, OBJ_QUEUE, "name");
        RP( "Authorise::outgoing",
            "AMQP 1.0 new outgoing link from queue   ",
            ACT_CONSUME, OBJ_QUEUE, "name");
        RP( "ConnectionHandler",
            "TCP/IP connection creation              ",
            ACT_CREATE,  OBJ_CONNECTION, "host");
        RP( "Broker::createExchange",
            "Create exchange                         ",
            ACT_CREATE,  OBJ_EXCHANGE, "name type alternate durable autodelete");
        RP( "ConnectionHandler::Handler::open",
            "Interbroker link creation               ",
            ACT_CREATE,  OBJ_LINK);
        RP( "Authorise::interlink",
            "Interbroker link creation               ",
            ACT_CREATE,  OBJ_LINK);
        RP( "Broker::createQueue",
            "Create queue                            ",
            ACT_CREATE,  OBJ_QUEUE, "name alternate durable exclusive autodelete policytype paging maxpages maxpagefactor maxqueuecount maxqueuesize maxfilecount maxfilesize");
        RP( "Broker::deleteExchange",
            "Delete exchange                         ",
            ACT_DELETE,  OBJ_EXCHANGE, "name type alternate durable");
        RP( "Broker::deleteQueue",
            "Delete queue                            ",
            ACT_DELETE,  OBJ_QUEUE, "name alternate durable exclusive autodelete policytype");
        RP( "Broker::queueMoveMessages",
            "Management 'move queue' request         ",
            ACT_MOVE,    OBJ_QUEUE, "name queuename");
        RP( "SemanticState::route",
            "AMQP 0-10 received message processing   ",
            ACT_PUBLISH, OBJ_EXCHANGE, "name routingkey");
        RP( "Authorise::incoming",
            "AMQP 1.0 establish sender link to queue ",
            ACT_PUBLISH, OBJ_EXCHANGE, "routingkey");
        RP( "Authorise::route",
            "AMQP 1.0 received message processing    ",
            ACT_PUBLISH, OBJ_EXCHANGE, "name routingkey");
        RP( "Queue::ManagementMethod",
            "Management 'purge queue' request        ",
            ACT_PURGE,   OBJ_QUEUE, "name");
        RP( "QueueHandlerImpl::purge",
            "Management 'purge queue' request        ",
            ACT_PURGE,   OBJ_QUEUE, "name");
        RP( "Broker::queueRedirect",
            "Management 'redirect queue' request     ",
            ACT_REDIRECT,OBJ_QUEUE, "name queuename");
        RP( "Queue::ManagementMethod",
            "Management 'reroute queue' request      ",
            ACT_REROUTE, OBJ_QUEUE, "name exchangename");
        RP( "Broker::unbind",
            "Management 'unbind exchange' request    ",
            ACT_UNBIND,  OBJ_EXCHANGE, "name queuename routingkey");
        RP( "Broker::setTimestampConfig",
            "User modifying message timestamp setting",
            ACT_UPDATE,  OBJ_BROKER);
    }

    AclValidator::~AclValidator(){
    }

    /* Iterate through the data model and validate the parameters. */
    void AclValidator::validate(boost::shared_ptr<AclData> d) {

        for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){

            if (d->actionList[cnt]){

                for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++){

                    if (d->actionList[cnt][cnt1]){

                        std::for_each(d->actionList[cnt][cnt1]->begin(),
                                      d->actionList[cnt][cnt1]->end(),
                                      boost::bind(&AclValidator::validateRuleSet, this, _1));
                    }
                }
            }
        }
    }

    void AclValidator::validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules){
        std::for_each(rules.second.begin(),
            rules.second.end(),
            boost::bind(&AclValidator::validateRule, this, _1));
    }

    void AclValidator::validateRule(qpid::acl::AclData::Rule& rule){
        std::for_each(rule.props.begin(),
            rule.props.end(),
            boost::bind(&AclValidator::validateProperty, this, _1));
    }

    void AclValidator::validateProperty(std::pair<const qpid::acl::SpecProperty, std::string>& prop){
        ValidatorItr itr = validators.find(prop.first);
        if (itr != validators.end()){
            QPID_LOG(debug,"ACL: Found validator for property '" << acl::AclHelper::getPropertyStr(itr->first)
                     << "'. " << itr->second->allowedValues());

            if (!itr->second->validate(prop.second)){
                QPID_LOG(debug, "ACL: Property failed validation. '" << prop.second << "' is not a valid value for '"
                    << AclHelper::getPropertyStr(prop.first) << "'");

                throw Exception( prop.second + " is not a valid value for '" +
                    AclHelper::getPropertyStr(prop.first) + "', " +
                    itr->second->allowedValues());
            }
        }
    }

    /**
     * validateAllowedProperties
     * verify that at least one lookup definition can satisfy this
     * action/object/props tuple.
     * Return false and conditionally emit a warning log entry if the
     * incoming definition can not be matched.
     */
    bool AclValidator::validateAllowedProperties(qpid::acl::Action action,
                                                 qpid::acl::ObjectType object,
                                                 const AclData::specPropertyMap& props,
                                                 bool emitLog) const {
        // No rules defined means no match
        if (!allowedSpecProperties[action][object].get()) {
            if (emitLog) {
                QPID_LOG(warning, "ACL rule ignored: Broker never checks for rules with action: '"
                    << AclHelper::getActionStr(action) << "' and object: '"
                    << AclHelper::getObjectTypeStr(object) << "'");
            }
            return false;
        }
        // two empty property sets is a match
        if (allowedSpecProperties[action][object]->size() == 0) {
            if ((props.size() == 0) ||
                (props.size() == 1 && props.find(acl::SPECPROP_NAME) != props.end())) {
                return true;
            }
        }
        // Scan vector of rules looking for one that matches all properties
        bool validRuleFound = false;
        for (std::vector<AclData::Rule>::const_iterator
            ruleItr  = allowedSpecProperties[action][object]->begin();
            ruleItr != allowedSpecProperties[action][object]->end() && !validRuleFound;
            ruleItr++) {
            // Scan one rule
            validRuleFound = true;
            for(AclData::specPropertyMapItr itr = props.begin();
                itr != props.end();
                itr++) {
                if ((*itr).first != acl::SPECPROP_NAME &&
                    ruleItr->props.find((*itr).first) ==
                    ruleItr->props.end()) {
                    // Test property not found in this rule
                    validRuleFound = false;
                    break;
                }
            }
        }
        if (!validRuleFound) {
            if (emitLog) {
                QPID_LOG(warning, "ACL rule ignored: Broker checks for rules with action: '"
                    << AclHelper::getActionStr(action) << "' and object: '"
                    << AclHelper::getObjectTypeStr(object)
                    << "' but will never match with property set: "
                    << AclHelper::propertyMapToString(&props));
            }
            return false;
        }
        return true;
    }

    /**
     * Return a list of indexes of definitions that this lookup might match
     */
    void AclValidator::findPossibleLookupMatch(qpid::acl::Action action,
                                               qpid::acl::ObjectType object,
                                               const AclData::specPropertyMap& props,
                                               std::vector<int>& result) const {
        if (!allowedSpecProperties[action][object].get()) {
            return;
        } else {
            // Scan vector of rules returning the indexes of all that match
            bool validRuleFound;
            for (std::vector<AclData::Rule>::const_iterator
                ruleItr  = allowedSpecProperties[action][object]->begin();
                ruleItr != allowedSpecProperties[action][object]->end();
                ruleItr++) {
                // Scan one rule
                validRuleFound = true;
                for(AclData::specPropertyMapItr
                    itr = props.begin(); itr != props.end(); itr++) {
                    if ((*itr).first != acl::SPECPROP_NAME &&
                        ruleItr->props.find((*itr).first) ==
                        ruleItr->props.end()) {
                        // Test property not found in this rule
                        validRuleFound = false;
                        break;
                    }
                }
                if (validRuleFound) {
                    result.push_back(ruleItr->rawRuleNum);
                }
            }
        }
        return;
    }

    /**
     * Emit trace log of original property definitions
     */
    void AclValidator::tracePropertyDefs() {
        QPID_LOG(trace, "ACL: Definitions of action, object, (allowed properties) lookups");
        for (int iA=0; iA<acl::ACTIONSIZE; iA++) {
            for (int iO=0; iO<acl::OBJECTSIZE; iO++) {
                if (allowedSpecProperties[iA][iO].get()) {
                    for (std::vector<AclData::Rule>::const_iterator
                        ruleItr  = allowedSpecProperties[iA][iO]->begin();
                        ruleItr != allowedSpecProperties[iA][iO]->end();
                        ruleItr++) {
                        std::string pstr;
                        for (AclData::specPropertyMapItr pMItr  = ruleItr->props.begin();
                            pMItr != ruleItr->props.end();
                            pMItr++) {
                            pstr += AclHelper::getPropertyStr((SpecProperty) pMItr-> first);
                            pstr += ",";
                        }
                        QPID_LOG(trace, "ACL: Lookup "
                            << std::setfill(' ') << std::setw(2)
                            << ruleItr->rawRuleNum << ": "
                            << ruleItr->lookupHelp << " "
                            << std::setfill(' ') << std::setw(acl::ACTION_STR_WIDTH +1) << std::left
                            << AclHelper::getActionStr(acl::Action(iA))
                            << std::setfill(' ') << std::setw(acl::OBJECTTYPE_STR_WIDTH) << std::left
                            << AclHelper::getObjectTypeStr(acl::ObjectType(iO))
                            << " (" << pstr.substr(0, pstr.length()-1) << ")");
                    }
                }
            }
        }
    }

    /**
     * Construct a record of all the calls that the broker will
     * make to acl::authorize and the properties for each call.
     * From that create the list of all the spec properties that
     * users are then allowed to specify in acl rule files.
     */
    void AclValidator::registerProperties(
        const std::string& source,
        const std::string& description,
        Action action,
        ObjectType object,
        const std::string& properties) {
        if (!allowedProperties[action][object].get()) {
            boost::shared_ptr<std::set<Property> > t1(new std::set<Property>());
            allowedProperties[action][object] = t1;
            boost::shared_ptr<std::vector<AclData::Rule> > t2(new std::vector<AclData::Rule>());
            allowedSpecProperties[action][object] = t2;
        }
        std::vector<std::string> props = split(properties, " ");
        AclData::specPropertyMap spm;
        for (size_t i=0; i<props.size(); i++) {
            Property prop = AclHelper::getProperty(props[i]);
            allowedProperties[action][object]->insert(prop);
            // Given that the broker will be calling with this property,
            // determine what user rule settings are allowed.
            switch (prop) {
                // Cases where broker and Acl file share property name and meaning
                case PROP_NAME:
                    spm[SPECPROP_NAME]="";
                    break;
                case PROP_DURABLE:
                    spm[SPECPROP_DURABLE]="";
                    break;
                case PROP_OWNER:
                    spm[SPECPROP_OWNER]="";
                    break;
                case PROP_ROUTINGKEY:
                    spm[SPECPROP_ROUTINGKEY]="";
                    break;
                case PROP_AUTODELETE:
                    spm[SPECPROP_AUTODELETE]="";
                    break;
                case PROP_EXCLUSIVE:
                    spm[SPECPROP_EXCLUSIVE]="";
                    break;
                case PROP_TYPE:
                    spm[SPECPROP_TYPE]="";
                    break;
                case PROP_ALTERNATE:
                    spm[SPECPROP_ALTERNATE]="";
                    break;
                case PROP_QUEUENAME:
                    spm[SPECPROP_QUEUENAME]="";
                    break;
                case PROP_EXCHANGENAME:
                    spm[SPECPROP_EXCHANGENAME]="";
                    break;
                case PROP_SCHEMAPACKAGE:
                    spm[SPECPROP_SCHEMAPACKAGE]="";
                    break;
                case PROP_SCHEMACLASS:
                    spm[SPECPROP_SCHEMACLASS]="";
                    break;
                case PROP_POLICYTYPE:
                    spm[SPECPROP_POLICYTYPE]="";
                    break;
                case PROP_PAGING:
                    spm[SPECPROP_PAGING]="";
                    break;
                case PROP_HOST:
                    spm[SPECPROP_HOST]="";
                    break;
                // Cases where broker supplies a property but Acl has upper/lower limit for it
                case PROP_MAXPAGES:
                    spm[SPECPROP_MAXPAGESLOWERLIMIT]="";
                    spm[SPECPROP_MAXPAGESUPPERLIMIT]="";
                    break;
                case PROP_MAXPAGEFACTOR:
                    spm[SPECPROP_MAXPAGEFACTORLOWERLIMIT]="";
                    spm[SPECPROP_MAXPAGEFACTORUPPERLIMIT]="";
                    break;
                case PROP_MAXQUEUESIZE:
                    spm[SPECPROP_MAXQUEUESIZELOWERLIMIT]="";
                    spm[SPECPROP_MAXQUEUESIZEUPPERLIMIT]="";
                    break;
                case PROP_MAXQUEUECOUNT:
                    spm[SPECPROP_MAXQUEUECOUNTLOWERLIMIT]="";
                    spm[SPECPROP_MAXQUEUECOUNTUPPERLIMIT]="";
                    break;
                case PROP_MAXFILESIZE:
                    spm[SPECPROP_MAXFILESIZELOWERLIMIT]="";
                    spm[SPECPROP_MAXFILESIZEUPPERLIMIT]="";
                    break;
                case PROP_MAXFILECOUNT:
                    spm[SPECPROP_MAXFILECOUNTLOWERLIMIT]="";
                    spm[SPECPROP_MAXFILECOUNTUPPERLIMIT]="";
                    break;
                default:
                    throw Exception( "acl::RegisterProperties no case for property: " +
                        AclHelper::getPropertyStr(prop) );
            }
        }
        AclData::Rule someProps(propertyIndex, acl::ALLOW, spm, source, description);
        propertyIndex++;
        allowedSpecProperties[action][object]->push_back(someProps);
    }

}}


#include <boost/test/unit_test.hpp>

#include <node/protocol/exceptions.hpp>

#include <node/chain/database.hpp>
#include <node/chain/hardfork.hpp>
#include <node/chain/node_objects.hpp>

#include <fc/crypto/digest.hpp>

#include "../common/database_fixture.hpp"

#include <iostream>

using namespace node;
using namespace node::chain;
using namespace node::protocol;

#ifndef IS_TEST_NET

BOOST_FIXTURE_TEST_SUITE( live_tests, live_database_fixture )

/*
BOOST_AUTO_TEST_CASE( SCORE_stock_split )
{
   try
   {
      BOOST_TEST_MESSAGE( "Gathering state prior to split" );

      uint32_t magnitude = 1000000;

      flat_map< string, share_type > accountSCORE;
      flat_map< string, share_type > account_SCOREfundTMEbalance_votes;
      const auto& acnt_idx = db.get_index< account_index >().indices().get< by_name >();
      auto acnt_itr = acnt_idx.begin();

      BOOST_TEST_MESSAGE( "Saving account VESTS" );

      while( acnt_itr != acnt_idx.end() )
      {
         accountSCORE[acnt_itr->name] = acnt_itr->SCORE.amount;
         account_SCOREfundTMEbalance_votes[acnt_itr->name] = acnt_itr->proxied_SCOREfundTMEbalance_votes_total().value;
         acnt_itr++;
      }

      auto old_virtual_supply = db.get_dynamic_global_properties().virtual_supply;
      auto old_current_supply = db.get_dynamic_global_properties().current_supply;
      auto old_SCORE_fund = db.get_dynamic_global_properties().totalTMEfundForSCORE;
      auto old_SCORE = db.get_dynamic_global_properties().totalSCORE;
      auto old_SCOREreward2 = db.get_dynamic_global_properties().totalSCOREreward2;
      auto old_reward_fund = db.get_dynamic_global_properties().total_reward_fund_TME;

      flat_map< std::tuple< account_name_type, string >, share_type > comment_net_SCOREreward;
      flat_map< std::tuple< account_name_type, string >, share_type > comment_abs_SCOREreward;
      flat_map< comment_id_type, uint64_t > total_vote_weights;
      flat_map< comment_id_type, uint64_t > orig_vote_weight;
      flat_map< comment_id_type, uint64_t > expected_reward;
      fc::uint128_t totalSCOREreward2 = 0;
      const auto& com_idx = db.get_index< comment_index >().indices().get< by_permlink >();
      auto com_itr = com_idx.begin();
      auto gpo = db.get_dynamic_global_properties();

      BOOST_TEST_MESSAGE( "Saving comment rshare values" );

      while( com_itr != com_idx.end() )
      {
         comment_net_SCOREreward[ std::make_tuple( com_itr->author, com_itr->permlink ) ] = com_itr->net_SCOREreward;
         comment_abs_SCOREreward[ std::make_tuple( com_itr->author, com_itr->permlink ) ] = com_itr->abs_SCOREreward;
         total_vote_weights[ com_itr->id ] = 0;
         orig_vote_weight[ com_itr->id ] = com_itr->total_vote_weight;

         if( com_itr->net_SCOREreward.value > 0 )
         {
            totalSCOREreward2 += com_itr->net_SCOREreward.value > 0 ? fc::uint128_t( com_itr->net_SCOREreward.value ) * com_itr->net_SCOREreward.value * magnitude * magnitude : 0;
            u256 rs( com_itr->net_SCOREreward.value );
            u256 rf( gpo.total_reward_fund_TME.amount.value );
            auto rs2 = rs * rs;
            u256 SCOREreward2 = old_SCOREreward2.hi;
            SCOREreward2 = SCOREreward2 << 64;
            SCOREreward2 += old_SCOREreward2.lo;
            expected_reward[ com_itr->id ] = static_cast< uint64_t >( rf * rs2 / SCOREreward2 );
         }
         com_itr++;
      }

      BOOST_TEST_MESSAGE( "Saving category SCOREreward" );

      const auto& cat_idx = db.get_index< category_index >().indices();
      flat_map< category_id_type, share_type > category_SCOREreward;

      for( auto cat_itr = cat_idx.begin(); cat_itr != cat_idx.end(); cat_itr++ )
      {
         category_SCOREreward[ cat_itr->id ] = cat_itr->abs_SCOREreward;
      }

      BOOST_TEST_MESSAGE( "Perform split" );
      fc::time_point start = fc::time_point::now();
      db.perform_SCORE_split( magnitude );
      fc::time_point end = fc::time_point::now();
      ilog( "VESTS split execution time: ${t} us", ("t",end - start) );

      BOOST_TEST_MESSAGE( "Verify split took place correctly" );

      BOOST_REQUIRE( db.get_dynamic_global_properties().current_supply == old_current_supply );
      BOOST_REQUIRE( db.get_dynamic_global_properties().virtual_supply == old_virtual_supply );
      BOOST_REQUIRE( db.get_dynamic_global_properties().totalTMEfundForSCORE == old_SCORE_fund );
      BOOST_REQUIRE( db.get_dynamic_global_properties().totalSCORE.amount == old_SCORE.amount * magnitude );
      BOOST_REQUIRE( db.get_dynamic_global_properties().totalSCOREreward2 == totalSCOREreward2 );
      BOOST_REQUIRE( db.get_dynamic_global_properties().total_reward_fund_TME == old_reward_fund );

      BOOST_TEST_MESSAGE( "Check accounts were updated" );
      acnt_itr = acnt_idx.begin();
      while( acnt_itr != acnt_idx.end() )
      {
         BOOST_REQUIRE( acnt_itr->SCORE.amount == accountSCORE[ acnt_itr->name ] * magnitude );
         BOOST_REQUIRE( acnt_itr->proxied_SCOREfundTMEbalance_votes_total().value == account_SCOREfundTMEbalance_votes[ acnt_itr->name ] * magnitude );
         acnt_itr++;
      }

      gpo = db.get_dynamic_global_properties();

      com_itr = com_idx.begin();
      while( com_itr != com_idx.end() )
      {
         BOOST_REQUIRE( com_itr->net_SCOREreward == comment_net_SCOREreward[ std::make_tuple( com_itr->author, com_itr->permlink ) ] * magnitude );
         BOOST_REQUIRE( com_itr->abs_SCOREreward == comment_abs_SCOREreward[ std::make_tuple( com_itr->author, com_itr->permlink ) ] * magnitude );
         BOOST_REQUIRE( com_itr->total_vote_weight == total_vote_weights[ com_itr->id ] );

         if( com_itr->net_SCOREreward.value > 0 )
         {
            u256 rs( com_itr->net_SCOREreward.value );
            u256 rf( gpo.total_reward_fund_TME.amount.value );
            u256 SCOREreward2 = totalSCOREreward2.hi;
            SCOREreward2 = ( SCOREreward2 << 64 ) + totalSCOREreward2.lo;
            auto rs2 = rs * rs;

            BOOST_REQUIRE( static_cast< uint64_t >( ( rf * rs2 ) / SCOREreward2 ) == expected_reward[ com_itr->id] );
         }
         com_itr++;
      }

      for( auto cat_itr = cat_idx.begin(); cat_itr != cat_idx.end(); cat_itr++ )
      {
         BOOST_REQUIRE( cat_itr->abs_SCOREreward.value == category_SCOREreward[ cat_itr->id ].value * magnitude );
      }

      validate_database();
   }
   FC_LOG_AND_RETHROW()
}*/

BOOST_AUTO_TEST_CASE( retally_votes )
{
   try
   {
      flat_map< witness_id_type, share_type > expected_votes;

      const auto& by_account_witness_idx = db.get_index< witness_vote_index >().indices();

      for( auto vote: by_account_witness_idx )
      {
         if( expected_votes.find( vote.witness ) == expected_votes.end() )
            expected_votes[ vote.witness ] = db.get( vote.account ).witness_vote_weight();
         else
            expected_votes[ vote.witness ] += db.get( vote.account ).witness_vote_weight();
      }

      db.retally_witness_votes();

      const auto& witness_idx = db.get_index< witness_index >().indices();

      for( auto witness: witness_idx )
      {
         BOOST_REQUIRE_EQUAL( witness.votes.value, expected_votes[ witness.id ].value );
      }
   }
   FC_LOG_AND_RETHROW()
}

BOOST_AUTO_TEST_SUITE_END()

#endif


#ifndef FAST_GICP_FAST_VGICP_CUDA_HPP
#define FAST_GICP_FAST_VGICP_CUDA_HPP

#include <Eigen/Core>
#include <Eigen/Geometry>

#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/search/kdtree.h>
#include <pcl/registration/registration.h>

#include <sophus/so3.hpp>
#include <fast_gicp/gicp/gicp_settings.hpp>

namespace fast_gicp {

class FastVGICPCudaCore;

enum NearestNeighborMethod { CPU_PARALLEL_KDTREE, GPU_BRUTEFORCE };

/**
 * @brief Fast Voxelized GICP algorithm boosted with CUDA
 */
template<typename PointSource, typename PointTarget>
class FastVGICPCuda : public pcl::Registration<PointSource, PointTarget, float> {
public:
  using Scalar = float;
  using Matrix4 = typename pcl::Registration<PointSource, PointTarget, Scalar>::Matrix4;

  using PointCloudSource = typename pcl::Registration<PointSource, PointTarget, Scalar>::PointCloudSource;
  using PointCloudSourcePtr = typename PointCloudSource::Ptr;
  using PointCloudSourceConstPtr = typename PointCloudSource::ConstPtr;

  using PointCloudTarget = typename pcl::Registration<PointSource, PointTarget, Scalar>::PointCloudTarget;
  using PointCloudTargetPtr = typename PointCloudTarget::Ptr;
  using PointCloudTargetConstPtr = typename PointCloudTarget::ConstPtr;

  using Ptr = boost::shared_ptr<FastVGICPCuda<PointSource, PointTarget>>;

  using pcl::Registration<PointSource, PointTarget, Scalar>::reg_name_;
  using pcl::Registration<PointSource, PointTarget, Scalar>::input_;
  using pcl::Registration<PointSource, PointTarget, Scalar>::target_;

  using pcl::Registration<PointSource, PointTarget, Scalar>::nr_iterations_;
  using pcl::Registration<PointSource, PointTarget, Scalar>::max_iterations_;
  using pcl::Registration<PointSource, PointTarget, Scalar>::final_transformation_;
  using pcl::Registration<PointSource, PointTarget, Scalar>::transformation_epsilon_;
  using pcl::Registration<PointSource, PointTarget, Scalar>::converged_;
  using pcl::Registration<PointSource, PointTarget, Scalar>::corr_dist_threshold_;

  FastVGICPCuda();
  virtual ~FastVGICPCuda() override;

  void setRotationEpsilon(double eps);

  void setResolution(double resolution);

  void setCorrespondenceRandomness(int k);

  void setRegularizationMethod(RegularizationMethod method);

  void setNearesetNeighborSearchMethod(NearestNeighborMethod method);

  void swapSourceAndTarget();

  void clearSource();

  void clearTarget();

  virtual void setInputSource(const PointCloudSourceConstPtr& cloud) override;

  virtual void setInputTarget(const PointCloudTargetConstPtr& cloud) override;

protected:
  virtual void computeTransformation(PointCloudSource& output, const Matrix4& guess) override;

  template<typename PointT>
  std::vector<int> find_neighbors_parallel_kdtree(int k, const boost::shared_ptr<const pcl::PointCloud<PointT>>& cloud, pcl::search::KdTree<PointT>& kdtree) const;

private:
private:
  int k_correspondences_;
  double rotation_epsilon_;

  pcl::search::KdTree<PointSource> source_kdtree;
  pcl::search::KdTree<PointTarget> target_kdtree;

  double voxel_resolution_;
  RegularizationMethod regularization_method_;
  NearestNeighborMethod neighbor_search_method_;

  std::unique_ptr<FastVGICPCudaCore> vgicp_cuda;
  };
}  // namespace fast_gicp

#endif


#pragma once
#include "criteria.hpp"
#include <boost/heap/pairing_heap.hpp>

namespace sssp {

class smallest_tentative_distance : public criteria {
  public:
    smallest_tentative_distance(const sssp::graph* graph, size_t start_node);
    virtual void relaxable_nodes(todo_output& output) const override;
    virtual void changed_predecessor(size_t node, size_t predecessor, double distance) override;
    virtual void relaxed_node(size_t node) override;
    virtual bool is_complete() const override { return true; }

  private:
    struct node_info;

    struct node_info_compare {
        bool operator()(const node_info* a, const node_info* b) const;
    };

    using queue = boost::heap::pairing_heap<node_info*, boost::heap::compare<node_info_compare>>;

    struct node_info {
        node_info(size_t index) : index(index) {}
        size_t index;
        double tentative_distance = INFINITY;
        queue::handle_type queue_handle;
    };

    node_map<node_info> m_node_info;
    queue m_queue;
};

} // namespace sssp


//---------------------------------------------------------------------------//
//  MIT License
//
//  Copyright (c) 2020-2021 Mikhail Komarov <nemo@nil.foundation>
//  Copyright (c) 2020-2021 Nikita Kaskov <nemo@nil.foundation>

//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef FILECOIN_STORAGE_PROOFS_POREP_STACKED_VANILLA_PROOF_HPP
#define FILECOIN_STORAGE_PROOFS_POREP_STACKED_VANILLA_PROOF_HPP

#include <boost/filesystem/path.hpp>
#include <boost/assert.hpp>
#include <boost/log/trivial.hpp>

#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/column.hpp>
#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/params.hpp>
#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/porep.hpp>
#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/challenges.hpp>
#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/create_label.hpp>
#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/encoding_proof.hpp>
#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/labelling_proof.hpp>

#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/detail/processing/naive/params.hpp>
#include <nil/filecoin/storage/proofs/porep/stacked/vanilla/detail/processing/naive/labelling_proof.hpp>

namespace nil {
    namespace filecoin {
        namespace stacked {
            namespace vanilla {
                constexpr static const std::size_t TOTAL_PARENTS = 37;

                template<typename MerkleTreeType, typename Hash>
                struct StackedDrg {
                    typedef MerkleTreeType tree_type;
                    typedef Hash hash_type;

                    typedef typename tree_type::hash_type tree_hash_type;

                    using merkle_proof_type = merkletree::MerkleProof<merkletree::MerkleTree<hash_type>::Hasher,
                                                                      merkletree::MerkleTree<hash_type>::Arity,
                                                                      merkletree::MerkleTree<hash_type>::SubTreeArity,
                                                                      merkletree::MerkleTree<hash_type>::TopTreeArity>;

                    std::vector<std::vector<Proof<tree_type, hash_type>>> prove_layers(
                        const StackedBucketGraph<tree_hash_type> &graph,
                        const PublicInputs<typename tree_hash_type::digest_type, typename hash_type::digest_type>
                            &pub_inputs,
                        const PersistentAux<typename tree_hash_type::digest_type> &p_aux,
                        const TemporaryAuxCache<tree_type, hash_type> &t_aux,
                        const LayerChallenges &layer_challenges,
                        std::size_t layers,
                        std::size_t total_layers,
                        std::size_t partition_count) {

                        assert(layers > 0);
                        assert(t_aux.labels.size() == layers);

                        std::size_t graph_size = graph.size();

                        // Sanity checks on restored trees.
                        assert(pub_inputs.tau.is_some());
                        assert(pub_inputs.tau.comm_d == t_aux.tree_d.root());

                        auto get_drg_parents_columns = [&](std::size_t x) -> std::vector<Column<tree_hash_type>> {
                            std::size_t base_degree = graph.base_graph().degree();

                            std::vector<Column<tree_hash_type>> columns(base_degree);

                            std::vector<std::uint64_t> parents(0, base_degree);
                            graph.base_parents(x, parents);

                            for (parents::iterator parent_it = parents.begin(); parent_it != parents.end();
                                 ++parent_it) {
                                columns.push_back(t_aux.column(*parent_it));
                            }

                            assert(columns.size() == base_degree);

                            return columns;
                        };

                        std::vector<Column<tree_hash_type>> get_exp_parents_columns(std::size_t x) {
                            std::vector<auto> parents(graph.expansion_degree(), 0);
                            graph.expanded_parents(x, parents);

                            std::vector<Column<tree_hash_type>> result;
                            result.reserve(parents.size());

                            for (parents::iterator parent_it = parents.begin(); parent_it != parents.end();
                                 ++parent_it) {
                                result.push_back(t_aux.column(*parent_it));
                            }

                            return result;
                        }

                        std::vector<std::vector<>> result;

                        for (std::size_t k = 0; k < partition_count; k++) {
                            std::vector<auto> result_k;
                            result_k.reserve(challenges.size());    // not sure about actual size of result_k

                            BOOST_LOG_TRIVIAL(trace) << std::format("proving partition %d/%d", k + 1, partition_count);

                            // Derive the set of challenges we are proving over.
                            std::vector<std::size_t> challenges =
                                pub_inputs.challenges(layer_challenges, graph_size, Some(k));

                            // Stacked commitment specifics
                            for (std::size_t challenge_index = 0,
                                             challenges::iterator challenge_it = challenges.begin();
                                 challenge_it != challenges.end(); ++challenge_index, ++challenge_it) {

                                BOOST_LOG_TRIVIAL(trace)
                                    << std::format(" challenge %d (%d)", *challenge_it, challenge_index);
                                BOOST_ASSERT_MSG(*challenge_it < graph.size(), "Invalid challenge");
                                BOOST_ASSERT_MSG(*challenge_it > 0, "Invalid challenge");

                                // Initial data layer openings (c_X in Comm_D)
                                merkle_proof_type<auto> comm_d_proof =
                                    merkletree::processing::naive::MerkleTree_gen_proof(t_aux.tree_d, *challenge_it);

                                BOOST_ASSERT(comm_d_proof.validate(*challenge_it));

                                // Stacked replica column openings
                                BOOST_ASSERT(p_aux.comm_c == t_aux.tree_c.root());
                                auto tree_c = &t_aux.tree_c;

                                // All labels in C_X
                                BOOST_LOG_TRIVIAL(trace) << "  c_x";
                                auto c_x = t_aux.column(std::uint32_t(*challenge_it)).into_proof(tree_c);

                                // All labels in the DRG parents.
                                BOOST_LOG_TRIVIAL(trace) << "  drg_parents";
                                std::vector<auto> drg_parents;
                                drg_parents.reserve();

                                std::vector<auto> drg_parents_columns = get_drg_parents_columns(*challenge_it);

                                for (drg_parents_columns::iterator column_it = drg_parents_columns.begin();
                                     column_it != drg_parents_columns.end();
                                     ++column_it) {
                                    drg_parents.push((*column_it).into_proof(tree_c));
                                }

                                // Labels for the expander parents
                                BOOST_LOG_TRIVIAL(trace) << "  exp_parents";
                                std::vector<auto> exp_parents;
                                exp_parents.reserve();

                                std::vector<auto> exp_parents_columns = get_exp_parents_columns(*challenge_it);

                                for (exp_parents_columns::iterator column_it = exp_parents_columns.begin();
                                     column_it != exp_parents_columns.end();
                                     ++column_it) {
                                    exp_parents.push((*column_it).into_proof(tree_c));
                                }

                                ReplicaColumnProof rcp = {c_x, drg_parents, exp_parents};

                                // Final replica layer openings
                                BOOST_LOG_TRIVIAL(trace) << "final replica layer openings";

                                merkle_proof_type<auto> comm_r_last_proof =
                                    merkletree::processing::naive::MerkleTree_gen_cached_proof(
                                        t_aux.tree_r_last, *challenge_it,
                                        Some(t_aux.tree_r_last_config_rows_to_discard), );

                                BOOST_ASSERT(comm_r_last_proof.validate(*challenge_it));

                                // Labeling Proofs Layer 1..l
                                std::vector<auto> labeling_proofs;
                                labeling_proofs.reserve(layers);
                                auto encoding_proof = None;

                                for (int layer = 1; layer != layers; layer++) {
                                    BOOST_LOG_TRIVIAL(trace) << std::format("  encoding proof layer %d", layer);
                                    std::vector<typename tree_hash_type::digest_type> parents_data;

                                    if (layer == 1) {
                                        std::vector<auto> parents(graph.base_graph().degree(), 0);
                                        graph.base_parents(*challenge_it, parents);

                                        parents_data.reserve(parents.size());

                                        for (parents::iterator parent_it = parents.begin(); parent_it != parents.end();
                                             ++parent_it) {

                                            parents_data.push_back(t_aux.domain_node_at_layer(layer, *parent_it));
                                        }
                                    } else {
                                        std::vector<auto> parents(graph.degree(), 0);
                                        graph.parents(*challenge_it, parents);
                                        auto base_parents_count = graph.base_graph().degree();

                                        parents_data.reserve(parents.size());

                                        for (std::size_t i = 0, parents::iterator parent_it = parents.begin();
                                             parent_it != parents.end(); ++i, ++parent_it) {

                                            if (i < base_parents_count) {
                                                // parents data for base parents is from the current
                                                // layer
                                                parents_data.push_back(t_aux.domain_node_at_layer(layer, *parent_it));
                                            } else {
                                                // parents data for exp parents is from the previous
                                                // layer
                                                parents_data.push_back(
                                                    t_aux.domain_node_at_layer(layer - 1, *parent_it));
                                            }
                                        }
                                    }

                                    // repeat parents
                                    std::vector<auto> parents_data_full(TOTAL_PARENTS, Default::default());
                                    for (chunk : parents_data_full.chunks_mut(parents_data.size())) {
                                        chunk.copy_from_slice(&parents_data[..chunk.size()]);
                                    }

                                    const LabelingProof<typename MerkleTreeType::hash_type> labeling_proof(
                                        std::uint32_t(layer), std::uint64_t(*challenge_it), parents_data_full.clone());

                                    const auto labeled_node = rcp.c_x.get_node_at_layer(layer);
                                    BOOST_ASSERT_MSG(
                                        LabelingProof_naive_verify(labeling_proof, &pub_inputs.replica_id,
                                                                   &labeled_node),
                                        std::format("Invalid encoding proof generated at layer {}", layer));
                                    BOOST_LOG_TRIVIAL(trace)
                                        << std::format("Valid encoding proof generated at layer %d", layer);

                                    labeling_proofs.push(labeling_proof);

                                    if (layer == layers) {
                                        encoding_proof = Some(EncodingProof(
                                            std::uint32_t(layer), std::uint64_t(*challenge_it), parents_data_full));
                                    }
                                }

                                result_k.push_back(Proof({.comm_d_proofs = comm_d_proof,
                                                          .replica_column_proofs = rcp,
                                                          .comm_r_last_proof,
                                                          .labeling_proofs,
                                                          .encoding_proof = encoding_proof}));
                            }
                            result.push_back(result_k);
                        }
                    }

                    void extract_and_invert_transform_layers(const StackedBucketGraph<tree_hash_type> &graph,
                                                             const LayerChallenges &layer_challenges,
                                                             const typename tree_hash_type::digest_type &replica_id,
                                                             const std::vector<std::uint8_t> &data,
                                                             const StoreConfig &config) {
                        BOOST_LOG_TRIVIAL(trace) << "extract_and_invert_transform_layers";

                        const auto layers = layer_challenges.layers();
                        assert(layers > 0);

                        // generate labels
                        const auto labels = std::get<0>(generate_labels(graph, layer_challenges, replica_id, config));

                        const auto last_layer_labels = labels.labels_for_last_layer();
                        const auto size = merkletree::store::Store::len(last_layer_labels);

                        for ((key, encoded_node_bytes) :
                             last_layer_labels.read_range(0..size).into_iter().zip(data.chunks_mut(NODE_SIZE))) {

                            const auto encoded_node =
                                MerkleTreeType::hash_type::digest_type::try_from_bytes(encoded_node_bytes);
                            const auto data_node =
                                decode::<typename MerkleTreeType::hash_type::digest_type>(key, encoded_node);

                            // store result in the data
                            encoded_node_bytes.copy_from_slice(AsRef::<[u8]>::as_ref(&data_node));
                        }
                    }

                    std::tuple<LabelsCache<tree_type>, Labels<tree_type>> generate_labels(
                        const StackedBucketGraph<tree_hash_type> &graph, const LayerChallenges &layer_challenges,
                        const typename tree_hash_type::digest_type &replica_id, const StoreConfig &config) {

                        BOOST_LOG_TRIVIAL(info) << "generate labels";

                        const auto layers = layer_challenges.layers();
                        // For now, we require it due to changes in encodings structure.
                        std::vector<DiskStore<typename MerkleTreeType::hash_type::digest_type>> labels;
                        labels.reserve(layers);

                        std::vector<StoreConfig> label_configs;
                        label_configs.reserve(layers);

                        const auto layer_size = graph.size() * NODE_SIZE;
                        // NOTE: this means we currently keep 2x sector size around, to improve speed.
                        std::vector<auto> labels_buffer(2 * layer_size, 0u8);

                        const auto use_cache = settings::SETTINGS.lock().maximize_caching;
                        auto cache = use_cache ? Some(graph.parent_cache()) : None;

                        for (std::size_t layer = 1; layer <= layers; ++layer) {
                            BOOST_LOG_TRIVIAL(info) << std::format("generating layer: %d", layer);
                            if (const auto Some(ref mut cache) = cache) {
                                cache.reset();
                            }

                            if (layer == 1) {
                                const auto layer_labels = labels_buffer[..layer_size];
                                for (std::size_t node = 0; node < graph.size(); ++node) {
                                    create_label(graph, cache, replica_id, layer_labels, layer, node);
                                }
                            } else {
                                const auto(layer_labels, exp_labels) = labels_buffer.split_at_mut(layer_size);
                                for (std::size_t node = 0; node < graph.size(); ++node) {
                                    create_label_exp(graph, cache, replica_id, exp_labels, layer_labels, layer, node);
                                }
                            }

                            BOOST_LOG_TRIVIAL(info) << "  setting exp parents";
                            labels_buffer.copy_within(..layer_size, layer_size);

                            // Write the result to disk to avoid keeping it in memory all the time.
                            const auto layer_config =
                                StoreConfig::from_config(&config, cache_key::label_layer(layer), Some(graph.size()));

                            BOOST_LOG_TRIVIAL(info) << "  storing labels on disk";
                            // Construct and persist the layer data.
                            DiskStore<typename tree_hash_type::digest_type> layer_store =
                                DiskStore::new_from_slice_with_config(graph.size(), MerkleTreeType::base_arity,
                                                                      &labels_buffer[..layer_size],
                                                                      layer_config.clone());
                            BOOST_LOG_TRIVIAL(info)
                                << std::format("  generated layer {} store with id {}", layer, layer_config.id);

                            // Track the layer specific store and StoreConfig for later retrieval.
                            labels.push(layer_store);
                            label_configs.push(layer_config);
                        }

                        BOOST_ASSERT_MSG(labels.len() == layers, "Invalid amount of layers encoded expected");

                        return (LabelsCache<Tree> {labels}, Labels<Tree> {.labels = label_configs});
                    }

                    template<typename TreeHash>
                    BinaryMerkleTree<TreeHash> build_binary_tree(const std::vector<std::uint8_t> &tree_data,
                                                                 const StoreConfig &config) {
                        BOOST_LOG_TRIVIAL(trace) << std::format("building tree (size: %d)", tree_data.len());

                        std::size_t leafs = tree_data.size() / NODE_SIZE;
                        assert(tree_data.size() % NODE_SIZE == 0);

                        std::vector<auto> build_tree_vector;
                        build_tree_vector.reserve(leafs);

                        for (std::size_t i = 0; i < leafs; ++i) {
                            build_tree_vector.push(get_node::<K>(tree_data, i));
                        }

                        return MerkleTree::from_par_iter_with_config(build_tree_vector, config);
                    }

                    template<typename MerkleTreeType>
                    DiskTree<typename MerkleTreeType::hash_type, MerkleTreeType::base_arity,
                             MerkleTreeType::sub_tree_arity, MerkleTreeType::top_tree_arity>
                        generate_tree_c(std::size_t layers, std::size_t nodes_count, std::size_t tree_count,
                                        const std::vector<StoreConfig> &configs, const LabelsCache<tree_type> &labels) {
                        if (settings ::SETTINGS.lock().use_gpu_column_builder) {
                            return generate_tree_c_gpu<MerkleTreeType>(layers, nodes_count, tree_count, configs,
                                                                       labels);
                        } else {
                            return generate_tree_c_cpu<MerkleTreeType>(layers, nodes_count, tree_count, configs,
                                                                       labels);
                        }
                    }

                    // gather all layer data in parallel.
                    static auto generate_tree_c_gpu_spawn_0 (???) {
                        for (std::size_t layer_index = 0, layer_data::iterator layer_elements_it = layer_data.begin();
                             layer_elements_it != layer_data.end(); ++layer_index, ++layer_elements_it) {

                            const auto store = labels.labels_for_layer(layer_index + 1);
                            const auto start = (i * nodes_count) + node_index;
                            const auto end = start + chunked_nodes_count;
                            const std::vector<typename MerkleTreeType::hash_type::digest_type> elements =
                                store.read_range(std::ops::Range {start, end});
                            (*layer_elements_it).extend(elements);
                        }
                    }

                    static auto generate_tree_c_gpu_spawn_1 (???) {

                        auto column_tree_builder = ColumnTreeBuilder::<ColumnArity, TreeArity>(
                            Some(BatcherType::GPU), nodes_count, max_gpu_column_batch_size, max_gpu_tree_batch_size);

                        std::size_t i = 0;
                        auto config = &configs[i];

                        // Loop until all trees for all configs have been built.
                        while (i < configs.size()) {
                            std::vector<GenericArray<Fr, ColumnArity>> columns;
                            bool is_final;

                            std::tie(columns, is_final) = builder_rx.recv();

                            // Just add non-final column batches.
                            if (!is_final) {
                                column_tree_builder.add_columns(&columns);
                                continue;
                            };

                            // If we get here, this is a final column: build a sub-tree.
                            auto base_data, tree_data;

                            std::tie(base_data, tree_data) = column_tree_builder.add_final_columns(&columns);
                            BOOST_LOG_TRIVIAL(trace)
                                << std::format("base data len {}, tree data len {}", base_data.len(), tree_data.len());

                            const auto tree_len = base_data.len() + tree_data.len();

                            BOOST_LOG_TRIVIAL(info) << std::format("persisting base tree_c {}/{} of length {}", i + 1,
                                                                   tree_count, tree_len);
                            BOOST_ASSERT(base_data.len() == nodes_count);
                            BOOST_ASSERT(tree_len == config.size);

                            // Persist the base and tree data to disk based using the current store config.
                            const auto tree_c_store =
                                DiskStore::<typename MerkleTreeType::hash_type::digest_type>::new_with_config(
                                    tree_len, MerkleTreeType::base_arity, config.clone());

                            const auto store = Arc(RwLock(tree_c_store));
                            const auto batch_size = std::cmp::min(base_data.len(), column_write_batch_size);
                            const auto flatten_and_write_store = | data : &Vec<Fr>,
                                       offset | {data.into_par_iter()
                                                     .chunks(column_write_batch_size)
                                                     .enumerate()
                                                     .try_for_each(| (index, fr_elements) | {
                                                         std::vector<auto> buf buf.reserve(batch_size * NODE_SIZE);

                                                         for (fr_elements::iterator fr_it = fr_elements.begin();
                                                              fr_it != fr_elements.end();
                                                              ++fr_it) {

                                                             buf.extend(fr_into_bytes(*fr_it));
                                                         }
                                                         store.write().copy_from_slice(&buf[..],
                                                                                offset + (batch_size * index))
                                                     })};

                            BOOST_LOG_TRIVIAL(trace)
                                << std::format("flattening tree_c base data of {} nodes using batch size {}",
                                               base_data.len(),
                                               batch_size);
                            flatten_and_write_store(&base_data, 0);
                            BOOST_LOG_TRIVIAL(trace) << "done flattening tree_c base data";

                            const auto base_offset = base_data.len();
                            BOOST_LOG_TRIVIAL(trace) << std::format(
                                "flattening tree_c tree data of {} nodes using batch size {} and base "
                                "offset "
                                "{}",
                                tree_data.len(), batch_size, base_offset);
                            flatten_and_write_store(&tree_data, base_offset);
                            BOOST_LOG_TRIVIAL(trace) << "done flattening tree_c tree data";

                            BOOST_LOG_TRIVIAL(trace) << "writing tree_c store data";
                            store.write().sync();
                            BOOST_LOG_TRIVIAL(trace) << "done writing tree_c store data";

                            // Move on to the next config.
                            i += 1;
                            if (i == configs.size()) {
                                break;
                            }
                            config = &configs[i];
                        }

                        return ? ? ? ;
                    }

                    static auto generate_tree_c_gpu_spawn_2 (???) {

                        for (int i = 0; i < config_count; ++i) {
                            auto node_index = 0;
                            const auto builder_tx = builder_tx.clone();
                            while (node_index != nodes_count) {
                                const auto chunked_nodes_count =
                                    std::cmp::min(nodes_count - node_index, max_gpu_column_batch_size);
                                BOOST_LOG_TRIVIAL(trace) << std::format("processing config {}/{} with column nodes {}",
                                                                        i + 1, tree_count, chunked_nodes_count);
                                std::vector<GenericArray<Fr, ColumnArity>> columns(
                                    chunked_nodes_count,
                                    GenericArray::<Fr, ColumnArity>::generate(| _i
                                                                              : usize | Fr::zero()));

                                // Allocate layer data array and insert a placeholder for each layer.
                                std::vector<std::vector<Fr>> layer_data(layers);
                                std::vector<Fr> layer_data_internal;
                                layer_data_internal.reserve(chunked_nodes_count);
                                layer_data.fill(layer_data.begin(), layer_data.end(), layer_data_internal);

                                auto ? ? ? = generate_tree_c_gpu_spawn_0(? ? ?);

                                // Copy out all layer data arranged into columns.
                                for (int layer_index = 0; layer_index < layer; layer_index++) {
                                    for (int index = 0; index < chunked_nodes_count) {
                                        columns[index][layer_index] = layer_data[layer_index][index];
                                    }
                                }

                                drop(layer_data);

                                node_index += chunked_nodes_count;
                                BOOST_LOG_TRIVIAL(trace)
                                    << std::format("node index {}/{}/{}", node_index, chunked_nodes_count, nodes_count);

                                const auto is_final = node_index == nodes_count;
                                builder_tx.send((columns, is_final));
                            }
                        }

                        auto ? ? ? = generate_tree_c_gpu_spawn_1(? ? ?);

                        return ? ? ? ;
                    }

                    template<typename MerkleTreeType>
                    DiskTree<typename MerkleTreeType::hash_type, MerkleTreeType::base_arity,
                             MerkleTreeType::sub_tree_arity, MerkleTreeType::top_tree_arity>
                        generate_tree_c_gpu(std::size_t layers, std::size_t nodes_count, std::size_t tree_count,
                                            const std::vector<StoreConfig> &configs,
                                            const LabelsCache<tree_type> &labels) {
                        BOOST_LOG_TRIVIAL(info) << "generating tree c using the GPU";
                        // Build the tree for CommC

                        BOOST_LOG_TRIVIAL(info) << "Building column hashes";

                        // NOTE: The max number of columns we recommend sending to the GPU at once is
                        // 400000 for columns and 700000 for trees (conservative soft-limits discussed).
                        //
                        // 'column_write_batch_size' is how many nodes to chunk the base layer of data
                        // into when persisting to disk.
                        //
                        // Override these values with care using environment variables:
                        // FIL_PROOFS_MAX_GPU_COLUMN_BATCH_SIZE, FIL_PROOFS_MAX_GPU_TREE_BATCH_SIZE, and
                        // FIL_PROOFS_COLUMN_WRITE_BATCH_SIZE respectively.
                        const auto max_gpu_column_batch_size = settings::SETTINGS.lock().max_gpu_column_batch_size;
                        const auto max_gpu_tree_batch_size = settings::SETTINGS.lock().max_gpu_tree_batch_size;
                        const auto column_write_batch_size = settings::SETTINGS.lock().column_write_batch_size;

                        // This channel will receive batches of columns and add them to the ColumnTreeBuilder.
                        const auto(builder_tx, builder_rx) = mpsc::sync_channel(0);
                        mpsc::sync_channel::<(std::vector<GenericArray<Fr, ColumnArity>>, bool)>(
                            max_gpu_column_batch_size * ColumnArity::to_usize() * 32);

                        const auto config_count = configs.len();    // Don't move config into closure below.

                        auto ? ? ? = generate_tree_c_gpu_spawn_2(? ? ?);

                        return create_disk_tree<
                            DiskTree<typename MerkleTreeType::hash_type, MerkleTreeType::base_arity,
                                     MerkleTreeType::sub_tree_arity, MerkleTreeType::top_tree_arity>>(configs[0].size,
                                                                                &configs);
                    }

                    static void generate_tree_c_cpu_spawn_0(stf::size_t chunk, auto &hashes_chunk) {
                        for (std::size_t j = 0, hashes_chunk::iterator hash_it = hashes_chunk.begin();
                             hash_it != hashes_chunk.end(); ++j, ++hash_it) {

                            const std::vector<auto> data;
                            data.reserve(layers);

                            for (std::size_t layer = 1; layer <= layers; ++layer) {
                                const auto store = labels.labels_for_layer(layer);
                                const typename MerkleTreeType::hash_type::digest_type el =
                                    store.read_at((i * nodes_count) + j + chunk * chunk_size);
                                data.push(el);
                            }

                            (*hash_it) = hash_single_column(data.begin(), data.end());
                        }
                    }

                    template<typename MerkleTreeType>
                    DiskTree<typename MerkleTreeType::hash_type, MerkleTreeType::base_arity,
                             MerkleTreeType::sub_tree_arity, MerkleTreeType::top_tree_arity>
                        generate_tree_c_cpu(std::size_t layers, std::size_t nodes_count, std::size_t tree_count,
                                            const std::vector<StoreConfig> &configs,
                                            const LabelsCache<tree_type> &labels) {
                        BOOST_LOG_TRIVIAL(info) << "generating tree c using the CPU";

                        BOOST_LOG_TRIVIAL(info) << "Building column hashes";

                        std::vector<auto> trees;
                        trees.reserve(tree_count);

                        for (std::size_t i = 0, configs::iterator config_it = configs.begin();
                             config_it != configs.end(); ++i, ++config_it) {

                            std::vector<typename MerkleTreeType::hash_type::digest_type> hashes(
                                nodes_count, MerkleTreeType::hash_type::digest_type::default());

                            const auto n = num_cpus::get();

                            // only split if we have at least two elements per thread
                            std::size_t num_chunks = (n > nodes_count * 2) ? 1 : n;

                            // chunk into n chunks
                            std::size_t chunk_size =
                                std::ceil(static_cast<double>(nodes_count) / static_cast<double>(num_chunks));

                            // calculate all n chunks in parallel
                            for ((chunk, hashes_chunk) : hashes.chunks_mut(chunk_size).enumerate()) {

                                generate_tree_c_cpu_spawn_0(chunk, hashes_chunk);
                            }

                            BOOST_LOG_TRIVIAL(info) << std::format("building base tree_c %d/%d", i + 1, tree_count);
                            trees.push(DiskTree<typename MerkleTreeType::hash_type, MerkleTreeType::base_arity,
                                                MerkleTreeType::sub_tree_arity, MerkleTreeType::top_tree_arity>::
                                           from_par_iter_with_config(hashes.into_par_iter(), (*config_it).clone()));
                        }

                        BOOST_ASSERT(tree_count == trees.len());
                        return create_disk_tree<
                            DiskTree<typename MerkleTreeType::hash_type, MerkleTreeType::base_arity,
                                     MerkleTreeType::sub_tree_arity, MerkleTreeType::top_tree_arity>>(configs[0].size,
                                                                                &configs);
                    }

                    static auto generate_tree_r_last_spawn_0(
                            const DiskStore<typename MerkleTreeType::hash_type::digest_type> last_layer_labels,
                            ???){

                        for (int i = 0; i < config_count; i++) {
                            std::size_t node_index = 0;
                            while (node_index != nodes_count) {
                                const std::size_t chunked_nodes_count =
                                    std::cmp::min(nodes_count - node_index, max_gpu_tree_batch_size);
                                const std::size_t start = (i * nodes_count) + node_index;
                                const std::size_t end = start + chunked_nodes_count;

                                BOOST_LOG_TRIVIAL(trace) << std::format(
                                    "processing config %d/%d with leaf nodes {} [%d, %d, %d-%d]", i + 1, tree_count,
                                    chunked_nodes_count, node_index, nodes_count, start, end);

                                const auto encoded_data =
                                    last_layer_labels.read_range(start..end)
                                        .into_par_iter()
                                        .zip(data[(start * NODE_SIZE)..(end * NODE_SIZE)].par_chunks_mut(NODE_SIZE))
                                        .map(| (key, data_node_bytes) | {
                                            const auto data_node =
                                                MerkleTreeType::hash_type::digest_type::try_from_bytes(data_node_bytes);
                                            const auto encoded_node =
                                                encode::<typename MerkleTreeType::hash_type::digest_type>(key,
                                                                                data_node);
                                            data_node_bytes.copy_from_slice(AsRef::<[u8]>::as_ref(&encoded_node));

                                            encoded_node
                                        });

                                node_index += chunked_nodes_count;
                                BOOST_LOG_TRIVIAL(trace)
                                    << std::format("node index %d/%d/%d", node_index, chunked_nodes_count, nodes_count);

                                std::vector<_> encoded = encoded_data.into_par_iter().map(| x | x.into()).collect();

                                const auto is_final = node_index == nodes_count;
                                builder_tx.send((encoded, is_final));
                            }
                        }
                    }

                    static auto generate_tree_r_last_spawn_1(StoreConfig &tree_r_last_config, ???){

                        auto tree_builder = TreeBuilder::<MerkleTreeType::base_arity>(
                            Some(BatcherType::GPU), nodes_count, max_gpu_tree_batch_size,
                            tree_r_last_config.rows_to_discard);

                        std::size_t i = 0;
                        auto config = &configs[i];

                        // Loop until all trees for all configs have been built.
                        while (i < configs.size()) {

                            const auto(encoded, is_final) = builder_rx.recv();

                            // Just add non-final leaf batches.
                            if (!is_final) {
                                tree_builder.add_leaves(&encoded);
                                continue;
                            };

                            // If we get here, this is a final leaf batch: build a sub-tree.
                            BOOST_LOG_TRIVIAL(info)
                                << std::format("building base tree_r_last with GPU %d/%d", i + 1, tree_count);
                            const auto tree_data = std::get<1>(tree_builder.add_final_leaves(&encoded));

                            const auto tree_data_len = tree_data.len();
                            const auto cache_size = get_merkle_tree_cache_size(
                                get_merkle_tree_leafs(config.size, MerkleTreeType::base_arity),
                                MerkleTreeType::base_arity, config.rows_to_discard);

                            BOOST_ASSERT(tree_data_len == cache_size);

                            const std::vector<_> flat_tree_data =
                                tree_data.into_par_iter().flat_map(| el | fr_into_bytes(&el)).collect();

                            // Persist the data to the store based on the current config.
                            const boost::filesystem::path tree_r_last_path =
                                StoreConfig::data_path(&config.path, &config.id);

                            BOOST_LOG_TRIVIAL(trace)
                                << std::format("persisting tree r of len %d with {} rows to discard at path %s",
                                               tree_data_len,
                                               config.rows_to_discard,
                                               tree_r_last_path.string());

                            boost::filesystem::ofstream f(tree_r_last_path);

                            f << flat_tree_data;

                            // Move on to the next config.
                            i += 1;
                            if (i == configs.size()) {
                                break;
                            }
                            config = &configs[i];
                        }
                    }

                    template<typename TreeArity = PoseidonArity>
                    LCTree<tree_hash_type, typename tree_type::Arity, typename tree_type::SubTreeArity,
                           typename tree_type::TopTreeArity>
                        generate_tree_r_last(Data &data, std::size_t nodes_count, std::size_t tree_count,
                                             const StoreConfig &tree_r_last_config,
                                             const boost::filesystem::path &replica_path,
                                             const LabelsCache<Tree> &labels) {

                        std::vector<StoreConfig> configs;
                        ReplicaConfig replica_config;
                        std::tie(configs, replica_config) =
                            split_config_and_replica(tree_r_last_config.clone(), replica_path, nodes_count, tree_count);

                        data.ensure_data();
                        const DiskStore<typename MerkleTreeType::hash_type::digest_type> last_layer_labels =
                            labels.labels_for_last_layer();

                        if (settings ::SETTINGS.lock().use_gpu_tree_builder) {

                            BOOST_LOG_TRIVIAL(info) << "generating tree r last using the GPU";

                            std::uint max_gpu_tree_batch_size = settings::SETTINGS.lock().max_gpu_tree_batch_size;

                            auto builder_tx, builder_rx;
                            // This channel will receive batches of leaf nodes and add them to the TreeBuilder.
                            std::tie(builder_tx, builder_rx) = mpsc::sync_channel::<(Vec<Fr>, bool)>(0);
                            const auto config_count = configs.len();    // Don't move config into closure below.

                            auto ? ? ? = generate_tree_r_last_spawn_0(last_layer_labels, ? ? ?);

                            auto ? ? ? = generate_tree_r_last_spawn_1(tree_r_last_config, ? ? ?);
                        } else {
                            BOOST_LOG_TRIVIAL(info) << "generating tree r last using the CPU";

                            const auto size = Store::len(last_layer_labels);

                            auto start = 0;
                            auto end = size / tree_count;

                            for (std::size_t i = 0, configs::iterator config_it = configs.begin();
                                 config_it != configs.end(); ++i, ++config_it) {

                                const auto encoded_data =
                                    last_layer_labels.read_range(start..end)
                                        .into_par_iter()
                                        .zip(data[(start * NODE_SIZE)..(end * NODE_SIZE)].par_chunks_mut(NODE_SIZE))
                                        .map(| (key, data_node_bytes) | {
                                            const auto data_node =
                                                MerkleTreeType::hash_type::digest_type::try_from_bytes(data_node_bytes);

                                            const auto encoded_node =
                                                encode::<typename MerkleTreeType::hash_type::digest_type>(key,
                                                                                data_node);
                                            data_node_bytes.copy_from_slice(AsRef::<[u8]>::as_ref(&encoded_node));

                                            encoded_node
                                        });

                                BOOST_LOG_TRIVIAL(info)
                                    << std::format("building base tree_r_last with CPU %d/%d", i + 1, tree_count);
                                LCTree<typename MerkleTreeType::hash_type, MerkleTreeType::base_arity, 0,
                                       0>::from_par_iter_with_config(encoded_data, (*config_it).clone());

                                start = end;
                                end += size / tree_count;
                            }
                        };

                        return create_lc_tree<LCTree<typename MerkleTreeType::hash_type, MerkleTreeType::base_arity,
                                                     MerkleTreeType::sub_tree_arity, MerkleTreeType::top_tree_arity>>(
                            tree_r_last_config.size, &configs, &replica_config);
                    }

                    TransformedLayers<tree_type, hash_type> transform_and_replicate_layers(
                        const StackedBucketGraph<tree_hash_type> &graph, const LayerChallenges &layer_challenges,
                        const typename hash_type::digest_type &replica_id, const Data &data,
                        const BinaryMerkleTree<G> &data_tree, const StoreConfig &config,
                        const boost::filesystem::path &replica_path) {
                        // Generate key layers.
                        const Labels<tree_type> labels =
                            std::get<1>(generate_labels(graph, layer_challenges, replica_id, config.clone()));

                        return transform_and_replicate_layers_inner(graph, layer_challenges, data, data_tree, config,
                                                                    replica_path, labels);
                    }

                    TransformedLayers<tree_type, hash_type> transform_and_replicate_layers_inner(
                        const StackedBucketGraph<tree_hash_type> &graph, const LayerChallenges &layer_challenges,
                        const typename hash_type::digest_type &replica_id, const Data &data,
                        const BinaryMerkleTree<G> &data_tree, const StoreConfig &config,
                        const boost::filesystem::path &replica_path, const Labels<tree_type> &label_configs) {

                        BOOST_LOG_TRIVIAL(trace) << "transform_and_replicate_layers";

                        std::size_t nodes_count = graph.size();

                        assert(data.len() == nodes_count * NODE_SIZE);
                        BOOST_LOG_TRIVIAL(trace) << std::format("nodes count %d, data len {}", nodes_count, data.len());

                        std::size_t tree_count = get_base_tree_count::<Tree>();
                        std::size_t nodes_count = graph.size() / tree_count;

                        // Ensure that the node count will work for binary and oct arities.
                        bool binary_arity_valid = is_merkle_tree_size_valid(nodes_count, BINARY_ARITY);
                        bool other_arity_valid = is_merkle_tree_size_valid(nodes_count, MerkleTreeType::base_arity);

                        BOOST_LOG_TRIVIAL(trace) << std::format("is_merkle_tree_size_valid(%d, BINARY_ARITY) = {}",
                                                                nodes_count, binary_arity_valid);
                        BOOST_LOG_TRIVIAL(trace) << std::format("is_merkle_tree_size_valid(%d, {}) = {}", nodes_count,
                                                                MerkleTreeType::base_arity, other_arity_valid);

                        assert(binary_arity_valid);
                        assert(other_arity_valid);

                        std::size_t layers = layer_challenges.layers();
                        assert(layers > 0);

                        // Generate all store configs that we need based on the
                        // cache_path in the specified config.
                        StoreConfig tree_d_config = StoreConfig::from_config(
                            config, cache_key::CommDTree.to_string(), get_merkle_tree_len(nodes_count, BINARY_ARITY));
                        tree_d_config.rows_to_discard = default_rows_to_discard(nodes_count, BINARY_ARITY);

                        StoreConfig tree_r_last_config =
                            StoreConfig::from_config(config, cache_key::CommRLastTree.to_string(),
                                                     get_merkle_tree_len(nodes_count, MerkleTreeType::base_arity));

                        // A default 'rows_to_discard' value will be chosen for tree_r_last, unless the user overrides
                        // this value via the environment setting (FIL_PROOFS_ROWS_TO_DISCARD).  If this value is
                        // specified, no checking is done on it and it may result in a broken configuration.  Use with
                        // caution.
                        tree_r_last_config.rows_to_discard =
                            default_rows_to_discard(nodes_count, MerkleTreeType::base_arity);

                        BOOST_LOG_TRIVIAL(trace)
                            << std::format("tree_r_last using rows_to_discard={}", tree_r_last_config.rows_to_discard);

                        StoreConfig tree_c_config = StoreConfig::from_config(
                            &config, cache_key::CommCTree.to_string(),
                            Some(get_merkle_tree_len(nodes_count, MerkleTreeType::base_arity)), );
                        tree_c_config.rows_to_discard =
                            default_rows_to_discard(nodes_count, MerkleTreeType::base_arity);

                        LabelsCache<tree_type> labels(&label_configs);
                        const auto configs = split_config(tree_c_config.clone(), tree_count);

                        typename tree_hash_type::digest_type tree_c_root;
                        if (layers == 2) {
                            const auto tree_c = generate_tree_c::<U2, MerkleTreeType::base_arity>(
                                layers, nodes_count, tree_count, configs, &labels);
                            tree_c_root = tree_c.root();
                        } else if (layers == 8) {
                            const auto tree_c = generate_tree_c::<U8, MerkleTreeType::base_arity>(
                                layers, nodes_count, tree_count, configs, &labels);
                            tree_c_root = tree_c.root();
                        } else if (layers == 11) {
                            const auto tree_c = generate_tree_c::<U11, MerkleTreeType::base_arity>(
                                layers, nodes_count, tree_count, configs, &labels);
                            tree_c_root = tree_c.root();
                        } else {
                            throw "Unsupported column arity";
                        }

                        BOOST_LOG_TRIVIAL(info) << "tree_c done";

                        // Build the MerkleTree over the original data (if needed).
                        BinaryMerkleTree<Hash> tree_d;
                        if (data_tree.empty()) {
                            BOOST_LOG_TRIVIAL(trace) << "building merkle tree for the original data";
                            data.ensure_data();
                            build_binary_tree::<G>(data, tree_d_config.clone());
                        } else {
                            BOOST_LOG_TRIVIAL(trace) << "using existing original data merkle tree";
                            BOOST_ASSERT(t.len() == 2 * (data.len() / NODE_SIZE) - 1);
                            tree_d = t;
                        }    // namespace stacked

                        tree_d_config.size = Some(tree_d.len());
                        BOOST_ASSERT(tree_d_config.size == tree_d.size());
                        auto tree_d_root = tree_d.root();
                        drop(tree_d);

                        // Encode original data into the last layer.
                        BOOST_LOG_TRIVIAL(info) << "building tree_r_last";
                        auto tree_r_last = generate_tree_r_last::<MerkleTreeType::base_arity>(
                            data, nodes_count, tree_count, tree_r_last_config.clone(), replica_path.clone(), &labels);

                        BOOST_LOG_TRIVIAL(info) << "tree_r_last done";

                        const auto tree_r_last_root = tree_r_last.root();
                        drop(tree_r_last);

                        data.drop_data();

                        // comm_r = H(comm_c || comm_r_last)
                        typename MerkleTreeType::hash_type::digest_type comm_r =
                            <typename MerkleTreeType::hash_type>::Function::hash2(&tree_c_root, &tree_r_last_root);

                        return std::make_tuple(
                            Tau<typename MerkleTreeType::hash_type::digest_type, typename Hash::digest_type>(
                                {.comm_d = tree_d_root, .comm_r}),
                            PersistentAux<typename MerkleTreeType::hash_type::digest_type>(
                                {.comm_c = tree_c_root, .comm_r_last = tree_r_last_root}),
                            TemporaryAux<MerkleTreeType, Hash>(
                                {.labels = label_configs, .tree_d_config, .tree_r_last_config, .tree_c_config}));
                    }

                    /// Phase1 of replication.
                    Labels<tree_type> replicate_phase1(const PublicParams<tree_type> &pp,
                                                       const typename tree_hash_type::digest_type &replica_id,
                                                       const StoreConfig &config) {
                        BOOST_LOG_TRIVIAL(info) << "replicate_phase1";

                        return std::get<1>(generate_labels(&pp.graph, &pp.layer_challenges, replica_id, config));
                    }

                    std::tuple << Self as PoRep <'a, typename MerkleTreeType::hash_type, G>>::Tau,  <
                        Self as PoRep <'a, typename MerkleTreeType::hash_type, G> >::ProverAux >  replicate_phase2(
                            const PublicParams<tree_type> &pp, const Labels<tree_type> &labels, const Data &data,
                            const BinaryMerkleTree<hash_type> &data_tree, const StoreConfig &config,
                            const boost::filesystem::path &replica_path) {
                        BOOST_LOG_TRIVIAL(info) << "replicate_phase2";

                        return transform_and_replicate_layers_inner(&pp.graph, &pp.layer_challenges, data,
                                                                    Some(data_tree), config, replica_path, labels);
                    }

                    tree_type &_a;
                    hash_type &_b;
                };    // StackedDrg
            }         // namespace vanilla
        }             // namespace stacked
    }                 // namespace filecoin
}    // namespace nil

#endif