Skip to content

account/GeniusAccount.cpp

Namespaces

Name
sgns

Attributes

Name
std::string_view SECURE_STORAGE_PREFIX

Attributes Documentation

variable SECURE_STORAGE_PREFIX

std::string_view SECURE_STORAGE_PREFIX = "SGNS";

Source code

// Keep these include files here to prevent errors within crypto3's headers
#include <nil/crypto3/algebra/marshalling.hpp>
#include <nil/crypto3/pubkey/algorithm/sign.hpp>
#include <nil/crypto3/pubkey/algorithm/verify.hpp>

#include "GeniusAccount.hpp"

#include <TrustWalletCore/TWCoinType.h>
#include <TrustWalletCore/TWDerivation.h>
#include <WalletCore/Coin.h>
#include <WalletCore/HDWallet.h>
#include <WalletCore/Hash.h>
#include <WalletCore/PrivateKey.h>
#include <ipfs_pubsub/gossip_pubsub.hpp>
#include <string>

#include "base/hexutil.hpp"
#include "local_secure_storage/impl/json/JSONSecureStorage.hpp"
#include "local_secure_storage/SecureStorage.hpp"
#include "outcome/outcome.hpp"
#include "account/AccountMessenger.hpp"
#include "crdt/globaldb/globaldb.hpp"
#include "crdt/graphsync_dagsyncer.hpp"
#include "primitives/cid/cid.hpp"

constexpr std::string_view SECURE_STORAGE_PREFIX = "SGNS";

namespace
{
    using namespace sgns;

    std::array<uint8_t, 32> get_elgamal_pubkey()
    {
        const auto              elgamal_key = KeyGenerator::ElGamal( 0x1234_cppui256 ).GetPublicKey().public_key_value;
        std::array<uint8_t, 32> exported;
        export_bits( elgamal_key, exported.begin(), 8, false );

        return exported;
    }

    base::Logger genius_account_logger()
    {
        // Always call base::createLogger to get the current logger
        // This will return existing logger or create new one as needed
        return base::createLogger( "GeniusAccount" );
    }

    boost::filesystem::path SetupStoragePath( const boost::filesystem::path &base_path )
    {
        constexpr std::string_view FILE_NAME = "secure_storage_id";
        boost::filesystem::create_directories( base_path );
        return boost::filesystem::canonical( boost::filesystem::absolute( base_path ) ) / FILE_NAME;
    }

    outcome::result<std::shared_ptr<ISecureStorage>> MigrateSecureStorage( const boost::filesystem::path &base_path )
    {
        JSONSecureStorage json_storage( base_path.generic_string() );
        auto              old_json = json_storage.LoadJSON();

        if ( old_json.has_error() )
        {
            if ( old_json.error() == std::errc::no_such_file_or_directory )
            {
                genius_account_logger()->debug( "There were no legacy JSON storage file to migrate from" );
                return std::errc::no_such_file_or_directory;
            }
            genius_account_logger()->error( "Could not load legacy JSON storage at {}: {}",
                                            base_path.generic_string(),
                                            old_json.error().message() );
            return std::errc::bad_file_descriptor;
        }

        auto maybe_field = old_json.value().FindMember( "GeniusAccount" );
        if ( maybe_field == old_json.value().MemberEnd() || !maybe_field->value.IsObject() )
        {
            genius_account_logger()->error( "Failed to find GeniusAccount member in old JSON storage" );
            return std::errc::bad_message;
        }

        auto maybe_key = maybe_field->value.FindMember( "sgns_key" );
        if ( maybe_key == maybe_field->value.MemberEnd() || !maybe_key->value.IsString() )
        {
            genius_account_logger()->error( "Failed to find GeniusAccount public key in old JSON storage" );
            return std::errc::bad_message;
        }

        // Create storage and keys
        nil::crypto3::multiprecision::uint256_t key_seed( maybe_key->value.GetString() );
        ethereum::EthereumKeyGenerator          eth_key( key_seed );
        auto                                    pub_key = eth_key.GetEntirePubValue();
        BOOST_OUTCOME_TRY( std::vector<uint8_t> pub_key_vec, base::unhex( pub_key ) );

        auto secure_storage = std::make_shared<SecureStorageImpl>( std::string( SECURE_STORAGE_PREFIX ) +
                                                                   libp2p::multi::detail::encodeBase58( pub_key_vec ) );

        auto          path = SetupStoragePath( base_path );
        std::ofstream file( path.c_str() );
        file << pub_key << std::endl;
        file.close();

        rj::Document new_doc;
        new_doc.CopyFrom( maybe_field->value, new_doc.GetAllocator() );
        BOOST_OUTCOME_TRY( secure_storage->SaveJSON( std::move( new_doc ) ) );
        genius_account_logger()->debug( "Successfully migrated JSON secure storage" );

        return secure_storage;
    }
}

namespace sgns
{
    const std::array<uint8_t, 32> GeniusAccount::ELGAMAL_PUBKEY_PREDEFINED = get_elgamal_pubkey();

    std::shared_ptr<GeniusAccount> GeniusAccount::CreateInstanceFromResponse( TokenID            token_id,
                                                                              StorageWithAddress response_value,
                                                                              bool               full_node )
    {
        auto [storage, addresses]           = std::move( response_value );
        auto [elgamal_address, eth_address] = std::move( addresses );

        auto instance = std::shared_ptr<GeniusAccount>(
            new GeniusAccount( std::make_shared<ethereum::EthereumKeyGenerator>( std::move( eth_address ) ),
                               token_id,
                               std::move( storage ),
                               full_node ) );
        instance->elgamal_address_ = std::make_shared<KeyGenerator::ElGamal>( std::move( elgamal_address ) );

        return instance;
    }

    // Refactored New methods
    std::shared_ptr<GeniusAccount> GeniusAccount::New( TokenID                        token_id,
                                                       const char                    *eth_private_key,
                                                       const boost::filesystem::path &base_path,
                                                       bool                           full_node )
    {
        if ( auto response = LoadGeniusAccount( base_path ); response.has_value() )
        {
            genius_account_logger()->debug( "Loaded existing Genius address" );
            return CreateInstanceFromResponse( token_id, std::move( response.value() ), full_node );
        }

        genius_account_logger()->info(
            "Could not load Genius address from storage, attempting to generate from ethereum private key" );

        auto response = GenerateGeniusAddress( eth_private_key, base_path );
        if ( response.has_error() )
        {
            genius_account_logger()->error( "Failed to generate Genius address from private key" );
            return nullptr;
        }

        genius_account_logger()->debug( "Generated a Genius address from private key" );
        return CreateInstanceFromResponse( token_id, std::move( response.value() ), full_node );
    }

    std::shared_ptr<GeniusAccount> GeniusAccount::NewFromMnemonic( TokenID                        token_id,
                                                                   const std::string             &mnemonic,
                                                                   const boost::filesystem::path &base_path,
                                                                   bool                           full_node )
    {
        if ( auto response = LoadGeniusAccount( base_path ); response.has_value() )
        {
            genius_account_logger()->debug( "Loaded existing Genius address" );
            return CreateInstanceFromResponse( token_id, std::move( response.value() ), full_node );
        }

        genius_account_logger()->info(
            "Could not load Genius address from storage, attempting to generate from mnemonic" );

        try
        {
            TW::HDWallet   wallet( mnemonic, "", true );
            auto           derivation_path = TW::derivationPath( TWCoinTypeEthereum );
            TW::PrivateKey private_key     = wallet.getKey( TWCoinTypeEthereum, derivation_path );

            auto response = GenerateGeniusAddress( private_key, base_path );
            if ( response.has_error() )
            {
                genius_account_logger()->error( "Failed to generate Genius address from private key" );
                return nullptr;
            }

            genius_account_logger()->debug( "Generated a Genius address from private key" );
            auto account = CreateInstanceFromResponse( token_id, std::move( response.value() ), full_node );

            account->storage_->Save( "mnemonic", wallet.getMnemonic() );

            return account;
        }
        catch ( const std::invalid_argument & )
        {
            genius_account_logger()->error( "Tried to create private key from invalid mnemonic" );
        }

        return nullptr;
    }

    std::shared_ptr<GeniusAccount> GeniusAccount::NewFromPublicKey( TokenID          token_id,
                                                                    std::string_view public_key,
                                                                    bool             full_node )
    {
        if ( auto response = LoadGeniusAccount( public_key ); response.has_value() )
        {
            genius_account_logger()->debug( "Loaded existing Genius address" );
            return CreateInstanceFromResponse( token_id, std::move( response.value() ), full_node );
        }

        genius_account_logger()->error( "Could not load Genius address from storage" );

        return nullptr;
    }

    std::shared_ptr<GeniusAccount> GeniusAccount::New( TokenID                        token_id,
                                                       const boost::filesystem::path &base_path,
                                                       bool                           full_node )
    {
        if ( auto response = LoadGeniusAccount( base_path ); response.has_value() )
        {
            genius_account_logger()->debug( "Loaded existing Genius address" );
            return CreateInstanceFromResponse( token_id, std::move( response.value() ), full_node );
        }

        genius_account_logger()->error(
            "Could not find existing Genius address, generating one from a random mnemonic" );

        TW::HDWallet wallet( 128, "" );

        return GeniusAccount::NewFromMnemonic( token_id, wallet.getMnemonic(), base_path, full_node );
    }

    std::vector<std::string> GeniusAccount::GetAvailableAccounts( const boost::filesystem::path &base_path )
    {
        auto file_path = SetupStoragePath( base_path );
        genius_account_logger()->info( "Secure storage ID path: {}", file_path.string() );
        std::ifstream file( file_path.string() );

        if ( !file.is_open() )
        {
            genius_account_logger()->debug( "Could not find ID file" );
            return {};
        }

        std::vector<std::string> addresses;
        std::string              line;

        while ( std::getline( file, line ) )
        {
            addresses.push_back( std::move( line ) );
        }

        genius_account_logger()->debug( "Found {} public addresses in storage", addresses.size() );

        return addresses;
    }

    outcome::result<GeniusAccount::StorageWithAddress> GeniusAccount::LoadGeniusAccount(
        const boost::filesystem::path &base_path )
    {
        auto file_path = SetupStoragePath( base_path );
        genius_account_logger()->info( "Secure storage ID path: {}", file_path.string() );

        std::shared_ptr<ISecureStorage> storage;
        std::ifstream                   file( file_path.string() );
        std::string                     public_key;
        bool                            loaded_from_storage = false;

        if ( file.is_open() )
        {
            file >> public_key;
            genius_account_logger()->info( "Loaded public key from file: {} (length: {})",
                                           public_key.substr( 0, 16 ) + "...",
                                           public_key.length() );

            BOOST_OUTCOME_TRY( std::vector<uint8_t> vec, base::unhex( public_key ) );

            storage = std::make_shared<SecureStorageImpl>( std::string( SECURE_STORAGE_PREFIX ) +
                                                           libp2p::multi::detail::encodeBase58( vec ) );

            file.close();
            loaded_from_storage = true;
        }
        else
        {
            genius_account_logger()->debug( "Secure storage ID file does not exist, will try migration" );
            BOOST_OUTCOME_TRY( storage, MigrateSecureStorage( base_path ) );
        }

        auto load_res = storage->Load( "sgns_key" );
        if ( !load_res )
        {
            genius_account_logger()->warn( "Could not load sgns_key from secure storage" );
            return std::errc::no_such_file_or_directory;
        }

        auto key_seed = nil::crypto3::multiprecision::uint256_t( load_res.value() );
        genius_account_logger()->info( "Successfully loaded key_seed from storage" );

        if ( loaded_from_storage )
        {
            // Validate loaded key_seed matches stored public key
            if ( ethereum::EthereumKeyGenerator( key_seed ).GetEntirePubValue() != public_key )
            {
                genius_account_logger()->error( "Validation failed: key_seed does not match stored public key" );
                return std::errc::bad_message;
            }
            genius_account_logger()->info( "Validation successful: key_seed matches stored public key" );
        }

        ethereum::EthereumKeyGenerator eth_key( key_seed );

        return std::make_pair( std::move( storage ),
                               std::make_pair( KeyGenerator::ElGamal( std::move( key_seed ) ), std::move( eth_key ) ) );
    }

    outcome::result<GeniusAccount::StorageWithAddress> GeniusAccount::LoadGeniusAccount( std::string_view public_key )
    {
        BOOST_OUTCOME_TRY( std::vector<uint8_t> vec, base::unhex( public_key ) );

        std::shared_ptr<ISecureStorage> storage = std::make_shared<SecureStorageImpl>(
            std::string( SECURE_STORAGE_PREFIX ) + libp2p::multi::detail::encodeBase58( vec ) );

        auto load_res = storage->Load( "sgns_key" );
        if ( !load_res )
        {
            genius_account_logger()->warn( "Could not load sgns_key from secure storage" );
            return std::errc::no_such_file_or_directory;
        }

        auto key_seed = nil::crypto3::multiprecision::uint256_t( load_res.value() );
        genius_account_logger()->info( "Successfully loaded key_seed from storage" );

        // Validate loaded key_seed matches stored public key
        if ( ethereum::EthereumKeyGenerator( key_seed ).GetEntirePubValue() != public_key )
        {
            genius_account_logger()->error( "Validation failed: key_seed does not match stored public key" );
            return std::errc::bad_message;
        }
        genius_account_logger()->info( "Validation successful: key_seed matches stored public key" );

        ethereum::EthereumKeyGenerator eth_key( key_seed );

        return std::make_pair( std::move( storage ),
                               std::make_pair( KeyGenerator::ElGamal( std::move( key_seed ) ), std::move( eth_key ) ) );
    }

    outcome::result<GeniusAccount::StorageWithAddress> GeniusAccount::GenerateGeniusAddress(
        const char                    *eth_private_key,
        const boost::filesystem::path &base_path )
    {
        genius_account_logger()->trace( "Key seed from ethereum private key" );

        if ( eth_private_key == nullptr )
        {
            genius_account_logger()->error( "No ethereum address to generate from" );
            return std::errc::invalid_argument;
        }

        auto private_key_vec = base::unhex( eth_private_key );
        if ( private_key_vec.has_error() )
        {
            genius_account_logger()->error( "Could not extract private key from hexadecimal" );
            return std::errc::invalid_argument;
        }

        TW::PrivateKey tw_private_key( private_key_vec.value() );

        return GenerateGeniusAddress( tw_private_key, base_path );
    }

    outcome::result<GeniusAccount::StorageWithAddress> GeniusAccount::GenerateGeniusAddress(
        const TW::PrivateKey          &private_key,
        const boost::filesystem::path &base_path )
    {
        genius_account_logger()->trace( "Key seed from TW private key" );

        auto signed_secret = private_key.sign(
            TW::Data( ELGAMAL_PUBKEY_PREDEFINED.cbegin(), ELGAMAL_PUBKEY_PREDEFINED.cend() ),
            TWCurveSECP256k1 );

        if ( signed_secret.empty() )
        {
            genius_account_logger()->error( "Cannot sign secret" );
            return outcome::failure( std::errc::invalid_argument );
        }

        auto key_seed = nil::crypto3::multiprecision::uint256_t( TW::Hash::sha256( signed_secret ) );

        // Create storage and keys
        ethereum::EthereumKeyGenerator eth_key( key_seed );
        auto                           pub_key = eth_key.GetEntirePubValue();
        BOOST_OUTCOME_TRY( std::vector<uint8_t> pub_key_vec, base::unhex( pub_key ) );

        auto storage = std::make_shared<SecureStorageImpl>( std::string( SECURE_STORAGE_PREFIX ) +
                                                            libp2p::multi::detail::encodeBase58( pub_key_vec ) );
        BOOST_OUTCOME_TRY( storage->Save( "sgns_key", key_seed.str() ) );

        // Save public key to file
        auto file_path = SetupStoragePath( base_path );
        genius_account_logger()->info( "Secure storage ID path: {}", file_path.string() );

        std::ofstream out_file( file_path.string() );
        if ( !out_file.is_open() )
        {
            return outcome::failure( std::errc::bad_file_descriptor );
        }

        auto elgamal = KeyGenerator::ElGamal( std::move( key_seed ) );
        out_file << eth_key.GetEntirePubValue() << std::endl;

        return std::make_pair( std::move( storage ), std::make_pair( std::move( elgamal ), std::move( eth_key ) ) );
    }

    bool GeniusAccount::InitMessenger( std::shared_ptr<ipfs_pubsub::GossipPubSub> pubsub )
    {
        bool                               ret = false;
        AccountMessenger::InterfaceMethods methods;
        methods.sign_ =
            [weakptr( weak_from_this() )]( const std::vector<uint8_t> &data ) -> outcome::result<std::vector<uint8_t>>
        {
            if ( auto self = weakptr.lock() )
            {
                return self->Sign( data );
            }

            return outcome::failure( std::errc::owner_dead );
        };
        methods.verify_signature_ = []( const std::string          &address,
                                        std::string_view            sig,
                                        const std::vector<uint8_t> &data ) -> outcome::result<bool>
        { return VerifySignature( address, sig, data ); };
        methods.get_local_nonce_ =
            [weakptr( weak_from_this() )]( const std::string &address ) -> outcome::result<uint64_t>
        {
            if ( auto self = weakptr.lock() )
            {
                return self->GetPeerNonce( address );
            }

            return outcome::failure( std::errc::owner_dead );
        };
        methods.get_block_cid_ = [weakptr( weak_from_this() )](
                                     uint8_t            block_index,
                                     const std::string &address ) -> outcome::result<std::string>
        {
            if ( auto self = weakptr.lock() )
            {
                std::lock_guard lock( self->get_cids_mutex_ );
                if ( self->get_cids_method_ )
                {
                    return self->get_cids_method_( block_index, address );
                }

                return outcome::failure( AccountMessenger::Error::GENESIS_REQUEST_ERROR );
            }

            return outcome::failure( std::errc::owner_dead );
        };
        methods.has_block_cid_ = [weakptr( weak_from_this() )]( const std::string &cid ) -> outcome::result<bool>
        {
            if ( auto self = weakptr.lock() )
            {
                std::lock_guard lock( self->get_cids_mutex_ );
                if ( self->has_cid_method_ )
                {
                    return self->has_cid_method_( cid );
                }

                return outcome::failure( AccountMessenger::Error::GENESIS_REQUEST_ERROR );
            }

            return outcome::failure( std::errc::owner_dead );
        };
        methods.get_utxos_ =
            [weakptr( weak_from_this() )]( const std::string &address ) -> outcome::result<std::vector<std::string>>
        {
            if ( auto self = weakptr.lock() )
            {
                std::vector<std::string> results;
                auto                     utxos = self->GetUTXOManager().GetUTXOs( address );
                results.reserve( utxos.size() );

                for ( const auto &utxo : utxos )
                {
                    results.push_back( utxo.GetTxID().toReadableString() );
                }

                return results;
            }

            return outcome::failure( std::errc::owner_dead );
        };
        methods.get_validator_weight_ =
            [weakptr( weak_from_this() )]( const std::string &address ) -> outcome::result<std::optional<uint64_t>>
        {
            if ( auto self = weakptr.lock() )
            {
                std::lock_guard lock( self->get_cids_mutex_ );
                if ( self->get_validator_weight_method_ )
                {
                    return self->get_validator_weight_method_( address );
                }

                return outcome::failure( AccountMessenger::Error::UTXO_REQUEST_ERROR );
            }

            return outcome::failure( std::errc::owner_dead );
        };
        methods.get_transaction_cid_ =
            [weakptr( weak_from_this() )]( const std::string &tx_hash ) -> outcome::result<std::string>
        {
            if ( auto self = weakptr.lock() )
            {
                std::lock_guard lock( self->get_cids_mutex_ );
                if ( self->get_transaction_cid_method_ )
                {
                    return self->get_transaction_cid_method_( tx_hash );
                }

                return outcome::failure( AccountMessenger::Error::GENESIS_REQUEST_ERROR );
            }

            return outcome::failure( std::errc::owner_dead );
        };
        messenger_ = AccountMessenger::New( eth_keypair_->GetEntirePubValue(),
                                            std::move( pubsub ),
                                            std::move( methods ) );

        if ( messenger_ )
        {
            genius_account_logger()->debug( "Created AccountMessenger" );
            ret = true;
        }
        return ret;
    }

    bool GeniusAccount::ConfigureDatabaseDependencies( std::shared_ptr<crdt::GlobalDB> global_db )
    {
        bool ret = false;

        SetNonceStore( global_db->GetDataStore() );
        if ( messenger_ )
        {
            messenger_->RegisterBlockResponseHandler(
                [weakptr{ std::weak_ptr<crdt::PubSubBroadcasterExt>( global_db->GetBroadcaster() ) }](
                    const std::string &cid,
                    const std::string &peer_id,
                    const std::string &address )
                {
                    if ( auto strong = weakptr.lock() )
                    {
                        return strong->AddSingleCIDInfo( cid, peer_id, address );
                    }
                    return false;
                } );

            messenger_->RegisterHeadRequestHandler(
                [weak_globaldb = std::weak_ptr<crdt::GlobalDB>( global_db )]( const std::set<std::string> &topics )
                {
                    if ( auto globaldb = weak_globaldb.lock() )
                    {
                        auto result = globaldb->RequestHeadBroadcast( topics );
                        if ( result.has_error() )
                        {
                            auto logger = base::createLogger( "GeniusAccount" );
                            logger->error( "Failed to request head broadcast for {} topics", topics.size() );
                        }
                    }
                } );

            SetHasBlockCidMethod(
                [weakptr{ std::weak_ptr<crdt::PubSubBroadcasterExt>( global_db->GetBroadcaster() ) }](
                    const std::string &cid ) -> outcome::result<bool>
                {
                    if ( auto strong = weakptr.lock() )
                    {
                        auto cid_result = CID::fromString( cid );
                        if ( cid_result.has_error() )
                        {
                            return outcome::failure( std::errc::invalid_argument );
                        }
                        auto dag_syncer = std::static_pointer_cast<crdt::GraphsyncDAGSyncer>( strong->GetDagSyncer() );
                        if ( !dag_syncer )
                        {
                            return outcome::failure( std::errc::no_such_device );
                        }
                        auto has_block = dag_syncer->HasBlock( cid_result.value() );
                        if ( has_block.has_error() )
                        {
                            return outcome::failure( has_block.error() );
                        }
                        return has_block.value();
                    }
                    return outcome::failure( std::errc::owner_dead );
                } );
            genius_account_logger()->debug( "Registered block response handler" );
            ret = true;
        }
        return ret;
    }

    void GeniusAccount::DeconfigureDatabaseDependencies()
    {
        SetNonceStore( nullptr );

        if ( messenger_ )
        {
            messenger_->ClearBlockResponseHandler();
            messenger_->ClearHeadRequestHandler();
        }

        ClearHasBlockCidMethod();
        genius_account_logger()->debug( "Cleared database dependency handlers" );
    }

    GeniusAccount::GeniusAccount( std::shared_ptr<ethereum::EthereumKeyGenerator> eth_keypair,
                                  TokenID                                         token_id,
                                  std::shared_ptr<ISecureStorage>                 storage,
                                  bool                                            full_node ) :
        token( token_id ),
        is_full_node_( full_node ),
        eth_keypair_( std::move( eth_keypair ) ),
        storage_( std::move( storage ) ),
        utxo_manager_(
            is_full_node_,
            GetAddress(),
            [this]( const std::vector<uint8_t> &data ) { return this->Sign( data ); },
            []( const std::string &address, const std::vector<uint8_t> &signature, const std::vector<uint8_t> &data )
            {
                return GeniusAccount::VerifySignature( address,
                                                       std::string( signature.begin(), signature.end() ),
                                                       data );
            } ),
        nonce_request_in_progress_( false ),
        cached_nonce_timestamp_( std::chrono::steady_clock::time_point{} )
    {
    }

    GeniusAccount::~GeniusAccount() {}

    std::string GeniusAccount::GetAddress() const
    {
        return eth_keypair_->GetEntirePubValue();
    }

    TokenID GeniusAccount::GetToken() const
    {
        return token;
    }

    bool GeniusAccount::VerifySignature( const std::string          &address,
                                         std::string_view            sig,
                                         const std::vector<uint8_t> &data )
    {
        bool ret = false;

        do
        {
            if ( sig.size() != SIGNATURE_EXP_SIZE )
            {
                genius_account_logger()->error( "Incorrect signature size {}, expected ",
                                                sig.size(),
                                                SIGNATURE_EXP_SIZE );
                break;
            }
            std::vector<uint8_t> vec_sig( sig.cbegin(), sig.cend() );

            std::array<uint8_t, 32> hashed = nil::crypto3::hash<nil::crypto3::hashes::sha2<256>>( data );

            auto [r_success, r] =
                nil::marshalling::bincode::field<ecdsa_t::scalar_field_type>::field_element_from_bytes(
                    vec_sig.cbegin(),
                    vec_sig.cbegin() + 32 );

            if ( !r_success )
            {
                break;
            }
            auto [s_success, s] =
                nil::marshalling::bincode::field<ecdsa_t::scalar_field_type>::field_element_from_bytes(
                    vec_sig.cbegin() + 32,
                    vec_sig.cbegin() + 64 );

            if ( !s_success )
            {
                break;
            }
            ethereum::signature_type sig( r, s );
            auto                     eth_pubkey = ethereum::EthereumKeyGenerator::BuildPublicKey( address );
            ret                                 = nil::crypto3::verify( hashed, sig, eth_pubkey );
        } while ( 0 );

        return ret;
    }

    std::vector<uint8_t> GeniusAccount::Sign( const std::vector<uint8_t> &data ) const
    {
        std::array<uint8_t, 32> hashed = nil::crypto3::hash<nil::crypto3::hashes::sha2<256>>( data );

        ethereum::signature_type  signature = nil::crypto3::sign( hashed, eth_keypair_->get_private_key() );
        std::vector<std::uint8_t> signed_vector( SIGNATURE_EXP_SIZE );

        nil::marshalling::bincode::field<ecdsa_t::scalar_field_type>::field_element_to_bytes<
            std::vector<std::uint8_t>::iterator>( std::get<0>( signature ),
                                                  signed_vector.begin(),
                                                  signed_vector.begin() + 32 );
        nil::marshalling::bincode::field<ecdsa_t::scalar_field_type>::field_element_to_bytes<
            std::vector<std::uint8_t>::iterator>( std::get<1>( signature ),
                                                  signed_vector.begin() + 32,
                                                  signed_vector.end() );

        return signed_vector;
    }

    void GeniusAccount::SetLocalConfirmedNonce( uint64_t nonce )
    {
        genius_account_logger()->debug( "Setting local confirmed nonce to {}", nonce );
        SetPeerConfirmedNonce( nonce, eth_keypair_->GetEntirePubValue() );
        std::lock_guard lock( nonce_mutex_ );
    }

    void GeniusAccount::SetPeerConfirmedNonce( uint64_t nonce, const std::string &address )
    {
        std::unique_lock lock( nonce_mutex_ );
        auto             current_confirmed_nonce = confirmed_nonces_[address];
        genius_account_logger()->debug( "Setting the max value between {} and {} as a confirmed nonce for address {}",
                                        current_confirmed_nonce,
                                        nonce,
                                        address.substr( 0, 8 ) );
        auto updated_nonce         = std::max( nonce, current_confirmed_nonce );
        confirmed_nonces_[address] = updated_nonce;

        if ( address == eth_keypair_->GetEntirePubValue() )
        {
            if ( !local_confirmed_nonce_ || updated_nonce > local_confirmed_nonce_.value() )
            {
                local_confirmed_nonce_ = updated_nonce;
            }
            auto it = pending_nonces_.begin();
            while ( it != pending_nonces_.end() &&
                    ( !local_confirmed_nonce_ || *it <= local_confirmed_nonce_.value() ) )
            {
                it = pending_nonces_.erase( it );
            }
        }

        lock.unlock();
        PersistConfirmedNonce( address, updated_nonce );
    }

    void GeniusAccount::RollBackPeerConfirmedNonce( uint64_t nonce, const std::string &address )
    {
        std::lock_guard lock( nonce_mutex_ );
        auto            it                      = confirmed_nonces_.find( address );
        uint64_t        current_confirmed_nonce = 0;
        if ( it != confirmed_nonces_.end() )
        {
            current_confirmed_nonce = it->second;
        }
        genius_account_logger()->debug( "Rolling back nonce {} for address {} (current confirmed {})",
                                        nonce,
                                        address.substr( 0, 8 ),
                                        current_confirmed_nonce );
        if ( it != confirmed_nonces_.end() && nonce == current_confirmed_nonce )
        {
            if ( current_confirmed_nonce > 0 )
            {
                it->second = current_confirmed_nonce - 1;
            }
            else
            {
                confirmed_nonces_.erase( it );
            }
        }

        if ( address == eth_keypair_->GetEntirePubValue() )
        {
            if ( local_confirmed_nonce_.has_value() && ( nonce == local_confirmed_nonce_.value() ) )
            {
                if ( local_confirmed_nonce_.value() > 0 )
                {
                    local_confirmed_nonce_ = local_confirmed_nonce_.value() - 1;
                }
                else
                {
                    local_confirmed_nonce_.reset();
                }
            }
            pending_nonces_.erase( nonce );
        }
    }

    uint64_t GeniusAccount::GetNextNonceLocked() const
    {
        uint64_t next = local_confirmed_nonce_.has_value() ? local_confirmed_nonce_.value() + 1 : 0;
        while ( pending_nonces_.count( next ) != 0 )
        {
            ++next;
        }
        return next;
    }

    uint64_t GeniusAccount::GetProposedNonce() const
    {
        std::shared_lock lock( nonce_mutex_ );
        return GetNextNonceLocked();
    }

    uint64_t GeniusAccount::ReserveNextNonce()
    {
        std::lock_guard lock( nonce_mutex_ );
        auto            nonce = GetNextNonceLocked();
        pending_nonces_.insert( nonce );
        return nonce;
    }

    void GeniusAccount::ReleaseNonce( uint64_t nonce )
    {
        std::lock_guard lock( nonce_mutex_ );
        pending_nonces_.erase( nonce );
    }

    outcome::result<uint64_t> GeniusAccount::GetPeerNonce( const std::string &address ) const
    {
        std::unordered_map<std::string, uint64_t> nonces_copy;
        {
            std::shared_lock lock( nonce_mutex_ );
            nonces_copy = confirmed_nonces_;
        }
        if ( auto it = nonces_copy.find( address ); it != nonces_copy.end() )
        {
            return it->second;
        }

        return outcome::failure( std::errc::invalid_argument );
    }

    outcome::result<uint64_t> GeniusAccount::GetLocalConfirmedNonce() const
    {
        return GetPeerNonce( eth_keypair_->GetEntirePubValue() );
    }

    outcome::result<std::optional<uint64_t>> GeniusAccount::FetchNetworkNonce( uint64_t timeout_ms ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        genius_account_logger()->debug( "Fetching nonce from the network with timeout {} ms", timeout_ms );

        auto result = messenger_->GetLatestNonce( timeout_ms );
        if ( result.has_value() )
        {
            genius_account_logger()->debug( "Nonce replied with value {}", result.value() );
            return result.value();
        }
        if ( result.error() == AccountMessenger::Error::RESPONSE_WITHOUT_NONCE )
        {
            genius_account_logger()->debug( "Network didn't answer nonce request" );
            return outcome::success( std::nullopt );
        }

        return outcome::failure( result.error() );
    }

    outcome::result<uint64_t> GeniusAccount::GetConfirmedNonce( uint64_t timeout_ms ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        std::unique_lock<std::mutex> lock( nonce_request_mutex_ );

        // Check if we have a fresh cached result (within 5 seconds)
        if ( cached_nonce_result_.has_value() )
        {
            auto     now = std::chrono::steady_clock::now();
            uint64_t cache_age_ms =
                std::chrono::duration_cast<std::chrono::milliseconds>( now - cached_nonce_timestamp_ ).count();

            if ( cache_age_ms < NONCE_CACHE_DURATION_MS )
            {
                genius_account_logger()->debug( "Returning cached nonce result (age: {} ms)", cache_age_ms );
                return cached_nonce_result_.value();
            }
            genius_account_logger()->debug( "Cached nonce expired (age: {} ms), fetching fresh nonce", cache_age_ms );
        }

        // If a request is already in progress, wait for it
        if ( nonce_request_in_progress_ )
        {
            genius_account_logger()->debug( "Nonce request already in progress, waiting for result..." );

            // Wait for the in-progress request to complete
            nonce_request_cv_.wait( lock, [this]() { return !nonce_request_in_progress_; } );

            // Return the cached result if available
            if ( cached_nonce_result_.has_value() )
            {
                genius_account_logger()->debug( "Returning cached nonce result from completed request" );
                return cached_nonce_result_.value();
            }
        }

        // Mark that we're starting a request
        nonce_request_in_progress_ = true;
        cached_nonce_result_.reset();

        // Release lock while making the network call
        lock.unlock();

        genius_account_logger()->info( "Requesting nonce from the network with timeout {} ms", timeout_ms );

        auto latest_nonce_result = messenger_->GetLatestNonce( timeout_ms );

        outcome::result<uint64_t> result = outcome::failure( std::errc::io_error );
        if ( latest_nonce_result.has_value() )
        {
            result = latest_nonce_result.value();
            genius_account_logger()->debug( "Nonce replied with value {}", result.value() );
        }
        else if ( latest_nonce_result.error() == AccountMessenger::Error::NO_RESPONSE_RECEIVED )
        {
            genius_account_logger()->debug( "Network didn't answer nonce request" );
            result = latest_nonce_result;
        }
        else if ( latest_nonce_result.error() == AccountMessenger::Error::RESPONSE_WITHOUT_NONCE )
        {
            genius_account_logger()->debug( "No nonce information on the network, get local data" );
            result = GetLocalConfirmedNonce();
        }
        else
        {
            result = latest_nonce_result;
        }

        // Re-acquire lock to update state
        lock.lock();
        nonce_request_in_progress_ = false;

        // Only cache successful results
        if ( result.has_value() )
        {
            cached_nonce_result_    = result;
            cached_nonce_timestamp_ = std::chrono::steady_clock::now();
            genius_account_logger()->debug( "Cached successful nonce result: {}", result.value() );
        }
        else
        {
            genius_account_logger()->debug( "Not caching failed nonce request" );
        }

        // Notify all waiting threads
        lock.unlock();
        nonce_request_cv_.notify_all();

        return result;
    }

    outcome::result<void> GeniusAccount::RequestGenesis(
        uint64_t                                            timeout_ms,
        std::function<void( outcome::result<std::string> )> callback ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        genius_account_logger()->debug( "Requesting Genesis block from the network" );

        return messenger_->RequestGenesis( timeout_ms, std::move( callback ) );
    }

    outcome::result<void> GeniusAccount::RequestAccountCreation(
        uint64_t                                            timeout_ms,
        std::function<void( outcome::result<std::string> )> callback ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        genius_account_logger()->debug( "Requesting Genesis block from the network" );

        return messenger_->RequestAccountCreation( timeout_ms, std::move( callback ) );
    }

    outcome::result<void> GeniusAccount::RequestValidatorRegistry(
        uint64_t                                            timeout_ms,
        std::function<void( outcome::result<std::string> )> callback ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        genius_account_logger()->debug( "Requesting Validator Registry block from the network" );

        return messenger_->RequestValidatorRegistry( timeout_ms, std::move( callback ) );
    }

    outcome::result<void> GeniusAccount::RequestHeads( const std::unordered_set<std::string> &topics ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        genius_account_logger()->debug( "Requesting heads broadcast for {} topics", topics.size() );

        return messenger_->RequestHeads( topics );
    }

    outcome::result<void> GeniusAccount::RequestRegularBlock(
        uint64_t                                            timeout_ms,
        const std::string                                  &cid,
        std::function<void( outcome::result<std::string> )> callback ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        genius_account_logger()->debug( "Requesting block by CID {}", cid );

        return messenger_->RequestRegularBlock( timeout_ms, cid, std::move( callback ) );
    }

    outcome::result<void> GeniusAccount::RequestTransaction(
        uint64_t                                            timeout_ms,
        const std::string                                  &tx_hash,
        std::function<void( outcome::result<std::string> )> callback ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        genius_account_logger()->debug( "Requesting transaction {}", tx_hash.substr( 0, 8 ) );

        return messenger_->RequestTransaction( timeout_ms, tx_hash, std::move( callback ) );
    }

    outcome::result<std::unordered_set<std::string>> GeniusAccount::RequestUTXOs( uint64_t           timeout_ms,
                                                                                  const std::string &address,
                                                                                  uint64_t silent_time_ms ) const
    {
        if ( !messenger_ )
        {
            return outcome::failure( std::errc::no_such_device );
        }
        genius_account_logger()->debug( "Requesting UTXOs for {}", address.substr( 0, 8 ) );

        return messenger_->RequestUTXOs( timeout_ms, address, silent_time_ms );
    }

    void GeniusAccount::SetGetBlockChainCIDMethod(
        std::function<outcome::result<std::string>( uint8_t, const std::string & )> method )
    {
        std::lock_guard lock( get_cids_mutex_ );
        get_cids_method_ = std::move( method );
    }

    void GeniusAccount::ClearGetBlockChainCIDMethod( void )
    {
        std::lock_guard lock( get_cids_mutex_ );
        get_cids_method_ = nullptr;
    }

    void GeniusAccount::SetHasBlockCidMethod( std::function<outcome::result<bool>( const std::string & )> method )
    {
        std::lock_guard lock( get_cids_mutex_ );
        has_cid_method_ = std::move( method );
    }

    void GeniusAccount::ClearHasBlockCidMethod( void )
    {
        std::lock_guard lock( get_cids_mutex_ );
        has_cid_method_ = nullptr;
    }

    void GeniusAccount::SetGetValidatorWeightMethod(
        std::function<outcome::result<std::optional<uint64_t>>( const std::string & )> method )
    {
        std::lock_guard lock( get_cids_mutex_ );
        get_validator_weight_method_ = std::move( method );
    }

    void GeniusAccount::ClearGetValidatorWeightMethod()
    {
        std::lock_guard lock( get_cids_mutex_ );
        get_validator_weight_method_ = nullptr;
    }

    void GeniusAccount::SetGetTransactionCIDMethod(
        std::function<outcome::result<std::string>( const std::string & )> method )
    {
        std::lock_guard lock( get_cids_mutex_ );
        get_transaction_cid_method_ = std::move( method );
    }

    void GeniusAccount::ClearGetTransactionCIDMethod()
    {
        std::lock_guard lock( get_cids_mutex_ );
        get_transaction_cid_method_ = nullptr;
    }

    void GeniusAccount::SetNonceStore( std::shared_ptr<storage::rocksdb> db )
    {
        nonce_db_ = std::move( db );
        LoadConfirmedNonces();
    }

    void GeniusAccount::LoadConfirmedNonces()
    {
        if ( !nonce_db_ )
        {
            return;
        }

        base::Buffer prefix;
        prefix.put( std::string( NONCE_KEY_PREFIX ) );
        auto query_res = nonce_db_->query( prefix );
        if ( query_res.has_error() )
        {
            return;
        }

        std::unordered_map<std::string, uint64_t> loaded;
        uint64_t                                  max_local = 0;
        bool                                      has_local = false;

        for ( const auto &[key_buf, val_buf] : query_res.value() )
        {
            const auto key = std::string( key_buf.toString() );

            if ( key.rfind( std::string( NONCE_KEY_PREFIX ) ) != 0 )
            {
                continue;
            }

            auto address = key.substr( std::string( NONCE_KEY_PREFIX ).size() );
            try
            {
                uint64_t nonce  = std::stoull( std::string( val_buf.toString() ) );
                loaded[address] = nonce;
            }
            catch ( const std::exception & )
            {
                genius_account_logger()->error( "Failed to parse nonce for {}: ",
                                                address.substr( 0, 8 ),
                                                val_buf.toString() );
            }
        }

        std::lock_guard lock( nonce_mutex_ );
        for ( const auto &[address, nonce] : loaded )
        {
            confirmed_nonces_[address] = nonce;
        }

        if ( has_local )
        {
            local_confirmed_nonce_ = max_local;
        }
    }

    void GeniusAccount::PersistConfirmedNonce( const std::string &address, uint64_t nonce )
    {
        if ( !nonce_db_ )
        {
            return;
        }

        base::Buffer key;

        key.put( std::string( NONCE_KEY_PREFIX ) + address );

        base::Buffer value;
        value.put( std::to_string( nonce ) );
        auto put_res = nonce_db_->put( key, value );
        if ( put_res.has_error() )
        {
            genius_account_logger()->error( "Failed to persist nonce for {}", address.substr( 0, 8 ) );
        }
    }
}

Updated on 2026-04-15 at 11:00:39 -0700