2018-02-17 14:27:49 +01:00
/*
* This file is part of the Flowee project
2025-02-12 22:16:17 +01:00
* Copyright (C) 2018-2025 Tom Zander <tom@flowee.org>
* Copyright (C) 2025 John Galt <johngaltbch@pm.me>
2018-02-17 14:27:49 +01:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
# include "AddressMonitorService.h"
2019-09-02 23:34:35 +02:00
// server 'lib'
# include <txmempool.h>
# include <DoubleSpendProof.h>
2019-04-11 11:33:27 +02:00
# include <encodings_legacy.h>
2019-09-02 23:34:35 +02:00
# include <chain.h>
# include <Application.h>
2018-02-17 14:27:49 +01:00
# include <NetworkManager.h>
2019-03-29 20:55:37 +01:00
# include <APIProtocol.h>
2018-02-17 14:27:49 +01:00
# include <Logger.h>
# include <Message.h>
# include <streaming/MessageBuilder.h>
# include <streaming/MessageParser.h>
2022-01-25 23:20:12 +01:00
# include <streaming/BufferPools.h>
2019-09-02 23:34:35 +02:00
# include <streaming/streams.h>
2021-11-02 10:23:33 +01:00
# include <primitives/Block.h>
2022-07-06 22:50:53 +02:00
# include <BitcoinVersion.h>
2019-05-06 14:50:19 +02:00
2018-02-17 14:27:49 +01:00
AddressMonitorService : : AddressMonitorService ( )
2023-12-21 15:13:56 +01:00
: NetworkService ( Api : : AddressMonitorService ) ,
m_pool ( std : : make_shared < Streaming : : BufferPool > ( ) )
2018-02-17 14:27:49 +01:00
{
ValidationNotifier ( ) . addListener ( this ) ;
}
AddressMonitorService : : ~ AddressMonitorService ( )
{
ValidationNotifier ( ) . removeListener ( this ) ;
}
2021-02-18 16:03:01 +01:00
void AddressMonitorService : : syncTx ( const Tx & tx )
2018-02-17 14:27:49 +01:00
{
2025-02-12 22:16:17 +01:00
auto rem = remotes ( ) ;
std : : map < int , Match > matchesPerRemote ;
2019-07-24 13:35:31 +02:00
Tx : : Iterator iter ( tx ) ;
2025-02-12 22:16:17 +01:00
if ( ! match ( iter , rem , matchesPerRemote ) )
2019-08-11 15:36:22 +02:00
return ;
2019-07-24 13:35:31 +02:00
2025-02-12 22:16:17 +01:00
for ( auto i = matchesPerRemote . begin ( ) ; i ! = matchesPerRemote . end ( ) ; + + i ) {
const Match & data = i - > second ;
if ( ! data . matches . empty ( ) )
sendMatchesToRemote ( rem [ i - > first ] , data , tx . createHash ( ) , - 1 , - 1 ) ;
2019-07-24 13:35:31 +02:00
}
2018-02-17 14:27:49 +01:00
}
2019-07-24 13:35:31 +02:00
bool AddressMonitorService : : match ( Tx : : Iterator & iter , const std : : deque < NetworkService : : Remote * > & remotes , std : : map < int , Match > & matchingRemotes ) const
2018-02-18 15:13:08 +01:00
{
2019-07-24 13:35:31 +02:00
if ( remotes . empty ( ) )
return false ;
2018-02-17 14:27:49 +01:00
auto type = iter . next ( ) ;
2019-07-24 13:35:31 +02:00
if ( type = = Tx : : End ) // then the second end means end of block
return false ;
2018-02-17 14:27:49 +01:00
2025-02-12 22:16:17 +01:00
MatchedOutput output ;
int index = 0 ; // output index
2019-07-24 13:35:31 +02:00
while ( type ! = Tx : : End ) {
2019-10-20 20:00:57 +02:00
if ( type = = Tx : : OutputValue ) {
2025-02-12 22:16:17 +01:00
output = MatchedOutput ( ) ;
output . index = index ;
output . amount = iter . longData ( ) ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : CashTokenBitfield ) {
uint8_t tokenbitfield = iter . bitfieldData ( ) ;
2025-02-12 22:16:17 +01:00
output . hasToken = true ;
output . tokenIsFT = ( tokenbitfield & 0x10 ) = = 0x10 ;
output . tokenIsNFT = ( tokenbitfield & 0x20 ) = = 0x20 ;
output . tokenIsImmutable = output . tokenIsNFT & & ( tokenbitfield & 0x0f ) = = 0x00 ;
output . tokenIsMutable = output . tokenIsNFT & & ( tokenbitfield & 0x0f ) = = 0x01 ;
output . tokenIsMinting = output . tokenIsNFT & & ( tokenbitfield & 0x0f ) = = 0x02 ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : CashTokenCategory ) {
2025-02-12 22:16:17 +01:00
output . tokenCategory = iter . byteData ( ) ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : CashTokenCommitment ) {
2025-02-12 22:16:17 +01:00
output . tokenCommitment = iter . byteData ( ) ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : CashTokenAmount ) {
2025-02-12 22:16:17 +01:00
output . tokenAmount = iter . longData ( ) ;
2019-10-20 20:00:57 +02:00
}
2018-02-17 14:27:49 +01:00
else if ( type = = Tx : : OutputScript ) {
2019-10-20 20:00:57 +02:00
uint256 hashedOutScript ;
iter . hashByteData ( hashedOutScript ) ;
for ( size_t i = 0 ; i < remotes . size ( ) ; + + i ) {
assert ( i < INT_MAX ) ;
RemoteWithKeys * rwk = static_cast < RemoteWithKeys * > ( remotes . at ( i ) ) ;
if ( rwk - > hashes . find ( hashedOutScript ) ! = rwk - > hashes . end ( ) ) {
Match & m = matchingRemotes [ static_cast < int > ( i ) ] ;
2025-08-11 14:28:21 +02:00
MatchedOutput copy = output ; // copy for this remote
copy . hashedOutScript = hashedOutScript ; // per-remote tweak is fine on the copy
m . matches . push_back ( std : : move ( copy ) ) ;
2018-02-17 14:27:49 +01:00
}
}
2025-02-12 13:35:34 +01:00
index + + ;
2018-02-17 14:27:49 +01:00
}
type = iter . next ( ) ;
}
2019-07-24 13:35:31 +02:00
return true ;
2018-02-17 14:27:49 +01:00
}
2025-02-12 22:16:17 +01:00
void AddressMonitorService : : sendMatchesToRemote ( Remote * remote , const Match & data , const uint256 & txid , int blockheight , int64_t offsetInBlock )
{
const int matchCount = data . matches . size ( ) ;
int messageSize = 0 ;
for ( const auto & output : data . matches ) {
messageSize + = output . serializedSize ( ) + 1 ;
}
if ( blockheight > 0 )
messageSize + = 10 ;
if ( offsetInBlock > 0 )
messageSize + = 10 ;
std : : lock_guard < std : : mutex > guard ( m_poolMutex ) ;
m_pool - > reserve ( messageSize ) ;
int count = 0 ;
Streaming : : MessageBuilder builder ( m_pool ) ;
builder . add ( Api : : AddressMonitor : : TxId , txid ) ;
if ( blockheight > 0 )
builder . add ( Api : : AddressMonitor : : BlockHeight , blockheight ) ;
if ( offsetInBlock > = 0 )
builder . add ( Api : : AddressMonitor : : OffsetInBlock , static_cast < uint64_t > ( offsetInBlock ) ) ;
for ( const auto & output : data . matches ) {
+ + count ;
output . serialize ( builder ) ;
if ( count ! = matchCount )
builder . add ( Api : : AddressMonitor : : Separator , true ) ;
}
logDebug ( Log : : MonitorService ) < < " Remote gets " < < data . matches . size ( ) < < " tx notification(s) " ;
remote - > connection . send ( builder . message ( Api : : AddressMonitorService , Api : : AddressMonitor : : TransactionFound ) ) ;
}
2021-11-02 10:18:24 +01:00
void AddressMonitorService : : syncAllTransactionsInBlock ( const Block & block , CBlockIndex * index )
2018-02-17 14:27:49 +01:00
{
2019-07-24 13:35:31 +02:00
assert ( index ) ;
Tx : : Iterator iter ( block ) ;
auto rem = remotes ( ) ;
while ( true ) {
2025-02-12 22:16:17 +01:00
std : : map < int , Match > matchesPerRemote ;
if ( ! match ( iter , rem , matchesPerRemote ) )
return ; // end of block
2025-02-12 13:35:34 +01:00
2025-02-12 22:16:17 +01:00
// one (or zero) message per transaction
for ( auto i = matchesPerRemote . begin ( ) ; i ! = matchesPerRemote . end ( ) ; + + i ) {
const Match & data = i - > second ;
if ( ! data . matches . empty ( ) ) {
auto tx = iter . prevTx ( ) ;
sendMatchesToRemote ( rem [ i - > first ] , data , tx . createHash ( ) , index - > nHeight , tx . offsetInBlock ( block ) ) ;
2025-02-12 13:35:34 +01:00
}
2019-07-24 13:35:31 +02:00
}
}
2018-02-17 14:27:49 +01:00
}
2021-02-18 16:03:01 +01:00
void AddressMonitorService : : doubleSpendFound ( const Tx & first , const Tx & duplicate )
2018-03-25 13:55:48 +02:00
{
2019-09-02 23:34:35 +02:00
logDebug ( Log : : MonitorService ) < < " Double spend found " < < first . createHash ( ) < < duplicate . createHash ( ) ;
2019-07-24 13:35:31 +02:00
const auto rem = remotes ( ) ;
2025-02-12 22:16:17 +01:00
std : : map < int , Match > matchesPerRemote ;
2019-07-24 13:35:31 +02:00
Tx : : Iterator iter ( first ) ;
2025-02-12 22:16:17 +01:00
if ( ! match ( iter , rem , matchesPerRemote ) )
return ; // returns if no listeners
2019-07-24 13:35:31 +02:00
Tx : : Iterator iter2 ( duplicate ) ;
2025-02-12 22:16:17 +01:00
bool m = match ( iter2 , rem , matchesPerRemote ) ;
2019-07-24 13:35:31 +02:00
assert ( m ) ; // our duplicate tx object should have data
2025-02-12 22:16:17 +01:00
for ( auto i = matchesPerRemote . begin ( ) ; i ! = matchesPerRemote . end ( ) ; + + i ) {
Match & data = i - > second ;
int messageSize = 0 ;
for ( const auto & output : data . matches ) {
messageSize + = output . serializedSize ( ) ;
2025-02-12 13:35:34 +01:00
}
2025-02-12 22:16:17 +01:00
std : : lock_guard < std : : mutex > guard ( m_poolMutex ) ;
m_pool - > reserve ( messageSize ) ;
2019-07-24 13:35:31 +02:00
Streaming : : MessageBuilder builder ( m_pool ) ;
2025-02-12 22:16:17 +01:00
for ( const auto & output : data . matches ) {
output . serialize ( builder ) ;
}
2019-07-24 13:35:31 +02:00
builder . add ( Api : : AddressMonitor : : TxId , first . createHash ( ) ) ;
2020-05-19 17:39:21 +02:00
builder . add ( Api : : AddressMonitor : : TransactionData , duplicate . data ( ) ) ;
2019-07-24 13:35:31 +02:00
rem [ i - > first ] - > connection . send ( builder . message ( Api : : AddressMonitorService , Api : : AddressMonitor : : DoubleSpendFound ) ) ;
}
2018-03-25 13:55:48 +02:00
}
2021-02-18 16:03:01 +01:00
void AddressMonitorService : : doubleSpendFound ( const Tx & txInMempool , const DoubleSpendProof & proof )
2019-09-02 23:34:35 +02:00
{
logDebug ( Log : : MonitorService ) < < " Double spend proof found. TxId: " < < txInMempool . createHash ( ) ;
const auto rem = remotes ( ) ;
2025-02-12 22:16:17 +01:00
std : : map < int , Match > matchesPerRemote ;
2019-09-02 23:34:35 +02:00
Tx : : Iterator iter ( txInMempool ) ;
2025-02-12 22:16:17 +01:00
if ( ! match ( iter , rem , matchesPerRemote ) )
2019-09-02 23:34:35 +02:00
return ; // returns false if no listeners
CDataStream stream ( SER_NETWORK , PROTOCOL_VERSION ) ;
stream < < proof ;
const std : : vector < uint8_t > serializedProof ( stream . begin ( ) , stream . end ( ) ) ;
2025-02-12 22:16:17 +01:00
for ( auto i = matchesPerRemote . begin ( ) ; i ! = matchesPerRemote . end ( ) ; + + i ) {
Match & data = i - > second ;
int messageSize = 0 ;
for ( const auto & output : data . matches ) {
messageSize + = output . serializedSize ( ) + 1 ;
2025-02-12 13:35:34 +01:00
}
2025-02-12 22:16:17 +01:00
std : : lock_guard < std : : mutex > guard ( m_poolMutex ) ;
m_pool - > reserve ( messageSize ) ;
2019-09-02 23:34:35 +02:00
Streaming : : MessageBuilder builder ( m_pool ) ;
2025-02-12 22:16:17 +01:00
for ( const auto & output : data . matches ) {
output . serialize ( builder ) ;
}
2019-09-02 23:34:35 +02:00
builder . add ( Api : : AddressMonitor : : TxId , txInMempool . createHash ( ) ) ;
2020-05-19 17:39:21 +02:00
builder . addByteArray ( Api : : AddressMonitor : : DoubleSpendProofData , & serializedProof [ 0 ] , serializedProof . size ( ) ) ;
2019-09-02 23:34:35 +02:00
rem [ i - > first ] - > connection . send ( builder . message ( Api : : AddressMonitorService , Api : : AddressMonitor : : DoubleSpendFound ) ) ;
}
}
2019-04-09 17:24:38 +02:00
void AddressMonitorService : : onIncomingMessage ( Remote * remote_ , const Message & message , const EndPoint & ep )
2018-02-17 14:27:49 +01:00
{
2019-03-10 12:53:44 +01:00
assert ( dynamic_cast < RemoteWithKeys * > ( remote_ ) ) ;
RemoteWithKeys * remote = static_cast < RemoteWithKeys * > ( remote_ ) ;
2018-02-17 14:27:49 +01:00
if ( message . messageId ( ) = = Api : : AddressMonitor : : Subscribe
| | message . messageId ( ) = = Api : : AddressMonitor : : Unsubscribe ) {
Streaming : : MessageParser parser ( message . body ( ) ) ;
std : : string error ;
2019-05-06 14:50:19 +02:00
int done = 0 ;
while ( parser . next ( ) = = Streaming : : FoundTag ) {
2019-10-20 20:00:57 +02:00
if ( parser . tag ( ) = = Api : : AddressMonitor : : BitcoinScriptHashed ) {
if ( parser . isByteArray ( ) & & parser . dataLength ( ) = = 32 ) {
uint256 hash = parser . uint256Data ( ) ;
2019-05-06 14:50:19 +02:00
2019-10-20 20:00:57 +02:00
+ + done ;
2018-02-18 15:13:08 +01:00
if ( message . messageId ( ) = = Api : : AddressMonitor : : Subscribe ) {
2021-02-26 15:14:14 +01:00
if ( m_maxAddressesPerConnection > 0 & & static_cast < int > ( remote - > hashes . size ( ) ) + 1 > = m_maxAddressesPerConnection ) {
logInfo ( Log : : MonitorService ) < < " Remote " < < ep . connectionId < < " hit limit of registrations " ;
error = " Maximum number of addresses registered for watching, ask your node operator to change limits " ;
break ;
}
2019-10-20 20:00:57 +02:00
remote - > hashes . insert ( hash ) ;
2018-02-18 15:13:08 +01:00
remote - > connection . postOnStrand ( std : : bind ( & AddressMonitorService : : findTxInMempool ,
2019-10-20 20:00:57 +02:00
this , remote - > connection . connectionId ( ) , hash ) ) ;
2019-05-06 14:50:19 +02:00
} else {
2019-10-20 20:00:57 +02:00
remote - > hashes . erase ( hash ) ;
2019-05-06 14:50:19 +02:00
}
2018-02-17 14:27:49 +01:00
}
2019-05-06 14:50:19 +02:00
else {
2019-10-20 20:00:57 +02:00
error = " BitcoinScriptHashed has to be a sha256 (bytearray of 32 bytes) " ;
2019-05-06 14:50:19 +02:00
}
2018-02-17 14:27:49 +01:00
}
}
2021-02-26 15:14:14 +01:00
if ( error . empty ( ) & & ! done )
2019-10-20 20:00:57 +02:00
error = " Missing required field BitcoinScriptHashed (2) " ;
2019-05-06 14:50:19 +02:00
2023-12-21 15:13:56 +01:00
auto pool = Streaming : : pool ( 10 + error . size ( ) ) ;
2022-01-25 23:20:12 +01:00
Streaming : : MessageBuilder builder ( pool ) ;
2019-05-06 14:50:19 +02:00
builder . add ( Api : : AddressMonitor : : Result , done ) ;
2019-09-02 23:34:35 +02:00
if ( message . messageId ( ) = = Api : : AddressMonitor : : Subscribe )
2021-02-26 15:14:14 +01:00
logInfo ( Log : : MonitorService ) < < " Remote " < < ep . connectionId < < " made " < < done < < " changes. Hashes count: "
< < remote - > hashes . size ( ) ;
2018-02-17 14:27:49 +01:00
if ( ! error . empty ( ) )
builder . add ( Api : : AddressMonitor : : ErrorMessage , error ) ;
2019-06-26 22:19:28 +02:00
remote - > connection . send ( builder . reply ( message ) ) ;
2018-02-17 14:27:49 +01:00
updateBools ( ) ;
}
}
void AddressMonitorService : : updateBools ( )
{
2019-10-20 20:00:57 +02:00
m_findByHash = false ;
2019-05-18 15:56:34 +02:00
for ( auto remote : remotes ( ) ) {
2019-03-10 12:53:44 +01:00
RemoteWithKeys * rwk = static_cast < RemoteWithKeys * > ( remote ) ;
2019-10-20 20:00:57 +02:00
m_findByHash = m_findByHash | | ! rwk - > hashes . empty ( ) ;
2018-02-17 14:27:49 +01:00
}
}
2018-02-18 15:13:08 +01:00
2019-10-20 20:00:57 +02:00
void AddressMonitorService : : findTxInMempool ( int connectionId , const uint256 & hash )
2018-02-18 15:13:08 +01:00
{
if ( m_mempool = = nullptr )
return ;
if ( manager ( ) = = nullptr )
return ;
auto connection = manager ( ) - > connection ( manager ( ) - > endPoint ( connectionId ) , NetworkManager : : OnlyExisting ) ;
if ( ! connection . isValid ( ) | | ! connection . isConnected ( ) )
return ;
LOCK ( m_mempool - > cs ) ;
for ( auto iter = m_mempool - > mapTx . begin ( ) ; iter ! = m_mempool - > mapTx . end ( ) ; + + iter ) {
Tx : : Iterator txIter ( iter - > tx ) ;
auto type = txIter . next ( ) ;
2025-02-12 13:35:34 +01:00
2025-02-12 22:16:17 +01:00
MatchedOutput output ;
int index = 0 ;
2025-02-12 13:35:34 +01:00
while ( type ! = Tx : : End ) {
if ( type = = Tx : : OutputValue ) {
2025-02-12 22:16:17 +01:00
output = MatchedOutput ( ) ;
output . index = index ;
output . amount = txIter . longData ( ) ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : CashTokenBitfield ) {
uint8_t tokenbitfield = txIter . bitfieldData ( ) ;
2025-02-12 22:16:17 +01:00
output . tokenIsFT = ( tokenbitfield & 0x10 ) = = 0x10 ;
output . tokenIsNFT = ( tokenbitfield & 0x20 ) = = 0x20 ;
output . tokenIsImmutable = output . tokenIsNFT & & ( tokenbitfield & 0x0f ) = = 0x00 ;
output . tokenIsMutable = output . tokenIsNFT & & ( tokenbitfield & 0x0f ) = = 0x01 ;
output . tokenIsMinting = output . tokenIsNFT & & ( tokenbitfield & 0x0f ) = = 0x02 ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : CashTokenCategory ) {
2025-02-12 22:16:17 +01:00
output . tokenCategory = txIter . byteData ( ) ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : CashTokenCommitment ) {
2025-02-12 22:16:17 +01:00
output . tokenCommitment = txIter . byteData ( ) ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : CashTokenAmount ) {
2025-02-12 22:16:17 +01:00
output . tokenAmount = txIter . longData ( ) ;
2025-02-12 13:35:34 +01:00
}
else if ( type = = Tx : : OutputScript ) {
uint256 hashedOutScript ;
txIter . hashByteData ( hashedOutScript ) ;
if ( txIter . hashedByteData ( ) = = hash ) {
2018-02-18 15:13:08 +01:00
logDebug ( Log : : MonitorService ) < < " + Sending to peers tx from mempool! " ;
2025-02-12 22:16:17 +01:00
output . hashedOutScript = hash ; // to serialize it
2020-03-19 21:59:54 +01:00
std : : lock_guard < std : : mutex > guard ( m_poolMutex ) ;
2025-02-12 22:16:17 +01:00
m_pool - > reserve ( output . serializedSize ( ) + 40 ) ;
2018-02-18 15:13:08 +01:00
Streaming : : MessageBuilder builder ( m_pool ) ;
2025-02-22 16:38:59 +01:00
builder . add ( Api : : AddressMonitor : : TxId , iter - > tx . createHash ( ) ) ;
2025-02-12 22:16:17 +01:00
output . serialize ( builder ) ;
2018-02-18 15:13:08 +01:00
Message message = builder . message ( Api : : AddressMonitorService , Api : : AddressMonitor : : TransactionFound ) ;
connection . send ( message ) ;
}
2025-02-12 13:35:34 +01:00
index + + ;
2018-02-18 15:13:08 +01:00
}
type = txIter . next ( ) ;
}
}
}
2021-02-26 15:14:14 +01:00
int AddressMonitorService : : maxAddressesPerConnection ( ) const
{
return m_maxAddressesPerConnection ;
}
void AddressMonitorService : : setMaxAddressesPerConnection ( int maxAddressesPerConnection )
{
m_maxAddressesPerConnection = maxAddressesPerConnection ;
}
2025-02-12 22:16:17 +01:00
int AddressMonitorService : : MatchedOutput : : serializedSize ( ) const
{
int count = 40 ;
if ( hasToken )
count + = 55 + tokenCommitment . size ( ) ;
return count ;
}
void AddressMonitorService : : MatchedOutput : : serialize ( Streaming : : MessageBuilder & builder ) const
{
builder . add ( Api : : AddressMonitor : : BitcoinScriptHashed , hashedOutScript ) ; // 22 bytes
builder . add ( Api : : AddressMonitor : : Amount , amount ) ; // up to 10 bytes
builder . add ( Api : : AddressMonitor : : Tx_Out_Index , index ) ; //up to 6 bytes
builder . add ( Api : : AddressMonitor : : Tx_Out_CT_IsFT , tokenIsFT ) ; //1 byte
builder . add ( Api : : AddressMonitor : : Tx_Out_CT_IsNFT , tokenIsNFT ) ; //1 byte
if ( hasToken ) {
assert ( tokenCategory . size ( ) = = 32 ) ;
builder . add ( Api : : AddressMonitor : : Tx_Out_CT_Category , tokenCategory ) ; // 35 bytes
builder . add ( Api : : AddressMonitor : : Tx_Out_CT_IsMutable , tokenIsMutable ) ; //1 byte
builder . add ( Api : : AddressMonitor : : Tx_Out_CT_IsImmutable , tokenIsImmutable ) ; //1 byte
builder . add ( Api : : AddressMonitor : : Tx_Out_CT_IsMinting , tokenIsMinting ) ; //2 bytes
if ( ! tokenCommitment . isValid ( ) )
builder . add ( Api : : AddressMonitor : : Tx_Out_CT_Commitment , tokenCommitment ) ; // 4 + commitment length (assuming 2 bytes for the length indicator (14 bits) should be enough
builder . add ( Api : : AddressMonitor : : Tx_Out_CT_Amount , tokenAmount ) ; // up to 10 bytes
}
}