Skip to content

account/MigrationAllowList.cpp

Namespaces

Name
sgns

Source code

#include "account/MigrationAllowList.hpp"

#include "storage/database_error.hpp"

#include <algorithm>
#include <atomic>
#include <limits>
#include <system_error>

namespace sgns
{
    namespace
    {
        constexpr std::string_view kPrefixBase = "/migration-allowlist";
        std::atomic_bool           g_migration_allowlist_enabled_for_tests{ true };
    }

    MigrationAllowList::MigrationAllowList( std::shared_ptr<storage::rocksdb> db, std::string migration_version ) :
        db_( std::move( db ) ),
        migration_version_( std::move( migration_version ) ),
        prefix_( BuildPrefix( migration_version_ ) )
    {
    }

    void MigrationAllowList::SetEligibilityCheckEnabledForTests( bool enabled )
    {
        g_migration_allowlist_enabled_for_tests.store( enabled, std::memory_order_release );
    }

    bool MigrationAllowList::IsEligibilityCheckEnabledForTests()
    {
        return g_migration_allowlist_enabled_for_tests.load( std::memory_order_acquire );
    }

    outcome::result<void> MigrationAllowList::StoreObservedBalance( const std::string &address, uint64_t balance )
    {
        if ( !db_ )
        {
            return outcome::failure( storage::DatabaseError::UNITIALIZED );
        }

        base::Buffer key_buf;
        key_buf.put( BuildKey( migration_version_, address ) );

        base::Buffer value_buf;
        value_buf.putUint64( balance );

        BOOST_OUTCOME_TRY( db_->put( key_buf, value_buf ) );
        return outcome::success();
    }

    outcome::result<void> MigrationAllowList::StoreObservedBalances( const std::vector<AddressBalance> &balances )
    {
        for ( const auto &[address, balance] : balances )
        {
            BOOST_OUTCOME_TRY( StoreObservedBalance( address, balance ) );
        }

        return outcome::success();
    }

    outcome::result<std::optional<uint64_t>> MigrationAllowList::LoadObservedBalance( const std::string &address ) const
    {
        if ( !db_ )
        {
            return outcome::failure( storage::DatabaseError::UNITIALIZED );
        }

        base::Buffer key_buf;
        key_buf.put( BuildKey( migration_version_, address ) );
        auto value = db_->get( key_buf );
        if ( value.has_error() )
        {
            if ( value.error() == storage::DatabaseError::NOT_FOUND )
            {
                return std::optional<uint64_t>{};
            }
            return value.error();
        }

        BOOST_OUTCOME_TRY( auto balance, DecodeBalance( value.value() ) );
        return std::optional<uint64_t>{ balance };
    }

    outcome::result<bool> MigrationAllowList::IsEligible( const std::string &address, uint64_t claimed_balance ) const
    {
        if ( !IsEligibilityCheckEnabledForTests() )
        {
            return true;
        }

        BOOST_OUTCOME_TRY( auto maybe_balance, LoadObservedBalance( address ) );
        if ( !maybe_balance.has_value() )
        {
            return false;
        }

        const auto observed_balance = maybe_balance.value();
        const auto max_claim = observed_balance > ( std::numeric_limits<uint64_t>::max() / 2 )
                                   ? std::numeric_limits<uint64_t>::max()
                                   : observed_balance * 2;
        return claimed_balance <= max_claim;
    }

    outcome::result<std::vector<MigrationAllowList::AddressBalance>> MigrationAllowList::ListObservedBalances() const
    {
        if ( !db_ )
        {
            return outcome::failure( storage::DatabaseError::UNITIALIZED );
        }

        base::Buffer prefix_buf;
        prefix_buf.put( prefix_ );
        auto entries = db_->query( prefix_buf );
        if ( entries.has_error() )
        {
            if ( entries.error() == storage::DatabaseError::NOT_FOUND )
            {
                return std::vector<AddressBalance>{};
            }
            return entries.error();
        }

        std::vector<AddressBalance> balances;
        balances.reserve( entries.value().size() );
        for ( const auto &[key, value] : entries.value() )
        {
            const auto key_str = std::string( key.toString() );
            if ( key_str.size() <= prefix_.size() + 1 || key_str.compare( 0, prefix_.size(), prefix_ ) != 0 ||
                 key_str[prefix_.size()] != '/' )
            {
                logger_->warn( "Skipping malformed migration allowlist key {}", key_str );
                continue;
            }

            BOOST_OUTCOME_TRY( auto balance, DecodeBalance( value ) );
            balances.emplace_back( key_str.substr( prefix_.size() + 1 ), balance );
        }

        std::sort( balances.begin(),
                   balances.end(),
                   []( const AddressBalance &lhs, const AddressBalance &rhs ) { return lhs.first < rhs.first; } );

        return balances;
    }

    std::string MigrationAllowList::BuildPrefix( std::string_view migration_version )
    {
        return std::string( kPrefixBase ) + "/" + std::string( migration_version );
    }

    std::string MigrationAllowList::BuildKey( std::string_view migration_version, std::string_view address )
    {
        return BuildPrefix( migration_version ) + "/" + std::string( address );
    }

    outcome::result<uint64_t> MigrationAllowList::DecodeBalance( const base::Buffer &buffer )
    {
        if ( buffer.size() != sizeof( uint64_t ) )
        {
            return outcome::failure( std::errc::bad_message );
        }

        uint64_t value = 0;
        for ( size_t i = 0; i < sizeof( uint64_t ); ++i )
        {
            value = ( value << 8u ) | buffer[i];
        }
        return value;
    }
}

Updated on 2026-06-05 at 17:22:19 -0700