@ -18,7 +18,8 @@ import java.util.concurrent.atomic.AtomicLong;
public class SimpleElectrumServerRpc implements ElectrumServerRpc {
private static final Logger log = LoggerFactory . getLogger ( SimpleElectrumServerRpc . class ) ;
private static final int MAX_TARGET_BLOCKS = 25 ;
private static final int PER_REQUEST_DELAY_MILLIS = 50 ;
private static final int MAX_RETRIES = 3 ;
private static final int RETRY_DELAY = 0 ;
private final AtomicLong idCounter = new AtomicLong ( ) ;
@ -26,8 +27,9 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
public void ping ( Transport transport ) {
try {
JsonRpcClient client = new JsonRpcClient ( transport ) ;
client . createRequest ( ) . method ( "server.ping" ) . id ( idCounter . incrementAndGet ( ) ) . executeNullable ( ) ;
} catch ( JsonRpcException | IllegalStateException | IllegalArgumentException e ) {
new RetryLogic < > ( MAX_RETRIES , RETRY_DELAY , IllegalStateException . class ) . getResult ( ( ) - >
client . createRequest ( ) . method ( "server.ping" ) . id ( idCounter . incrementAndGet ( ) ) . executeNullable ( ) ) ;
} catch ( Exception e ) {
throw new ElectrumServerRpcException ( "Error pinging server" , e ) ;
}
}
@ -36,9 +38,10 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
public List < String > getServerVersion ( Transport transport , String clientName , String [ ] supportedVersions ) {
try {
JsonRpcClient client = new JsonRpcClient ( transport ) ;
//Using 1.4 as the version number as EPS tries to parse this number to a float
return client . createRequest ( ) . returnAsList ( String . class ) . method ( "server.version" ) . id ( idCounter . incrementAndGet ( ) ) . params ( clientName , "1.4" ) . execute ( ) ;
} catch ( JsonRpcException | IllegalStateException | IllegalArgumentException e ) {
//Using 1.4 as the version number as EPS tries to parse this number to a float :(
return new RetryLogic < List < String > > ( MAX_RETRIES , RETRY_DELAY , IllegalStateException . class ) . getResult ( ( ) - >
client . createRequest ( ) . returnAsList ( String . class ) . method ( "server.version" ) . id ( idCounter . incrementAndGet ( ) ) . params ( clientName , "1.4" ) . execute ( ) ) ;
} catch ( Exception e ) {
throw new ElectrumServerRpcException ( "Error getting server version" , e ) ;
}
}
@ -47,8 +50,9 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
public String getServerBanner ( Transport transport ) {
try {
JsonRpcClient client = new JsonRpcClient ( transport ) ;
return client . createRequest ( ) . returnAs ( String . class ) . method ( "server.banner" ) . id ( idCounter . incrementAndGet ( ) ) . execute ( ) ;
} catch ( JsonRpcException | IllegalStateException | IllegalArgumentException e ) {
return new RetryLogic < String > ( MAX_RETRIES , RETRY_DELAY , IllegalStateException . class ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( String . class ) . method ( "server.banner" ) . id ( idCounter . incrementAndGet ( ) ) . execute ( ) ) ;
} catch ( Exception e ) {
throw new ElectrumServerRpcException ( "Error getting server banner" , e ) ;
}
}
@ -57,8 +61,9 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
public BlockHeaderTip subscribeBlockHeaders ( Transport transport ) {
try {
JsonRpcClient client = new JsonRpcClient ( transport ) ;
return client . createRequest ( ) . returnAs ( BlockHeaderTip . class ) . method ( "blockchain.headers.subscribe" ) . id ( idCounter . incrementAndGet ( ) ) . execute ( ) ;
} catch ( JsonRpcException | IllegalStateException | IllegalArgumentException e ) {
return new RetryLogic < BlockHeaderTip > ( MAX_RETRIES , RETRY_DELAY , IllegalStateException . class ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( BlockHeaderTip . class ) . method ( "blockchain.headers.subscribe" ) . id ( idCounter . incrementAndGet ( ) ) . execute ( ) ) ;
} catch ( Exception e ) {
throw new ElectrumServerRpcException ( "Error subscribing to block headers" , e ) ;
}
}
@ -71,10 +76,10 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
for ( String path : pathScriptHashes . keySet ( ) ) {
EventManager . get ( ) . post ( new WalletHistoryStatusEvent ( false , "Loading transactions for " + path ) ) ;
try {
ScriptHashTx [ ] scriptHashTxes = client . createRequest ( ) . returnAs ( ScriptHashTx [ ] . class ) . method ( "blockchain.scripthash.get_history" ) . id ( path + "-" + idCounter . incrementAndGet ( ) ) . params ( pathScriptHashes . get ( path ) ) . execute ( ) ;
ScriptHashTx [ ] scriptHashTxes = new RetryLogic < ScriptHashTx [ ] > ( MAX_RETRIES , RETRY_DELAY , List . of ( IllegalStateException . class , IllegalArgumentException . class ) ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( ScriptHashTx [ ] . class ) . method ( "blockchain.scripthash.get_history" ) . id ( path + "-" + idCounter . incrementAndGet ( ) ) . params ( pathScriptHashes . get ( path ) ) . execute ( ) ) ;
result . put ( path , scriptHashTxes ) ;
Thread . sleep ( PER_REQUEST_DELAY_MILLIS ) ;
} catch ( JsonRpcException | IllegalStateException | IllegalArgumentException | InterruptedException e ) {
} catch ( Exception e ) {
if ( failOnError ) {
throw new ElectrumServerRpcException ( "Failed to retrieve reference for path: " + path , e ) ;
}
@ -93,10 +98,10 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
Map < String , ScriptHashTx [ ] > result = new LinkedHashMap < > ( ) ;
for ( String path : pathScriptHashes . keySet ( ) ) {
try {
ScriptHashTx [ ] scriptHashTxes = client . createRequest ( ) . returnAs ( ScriptHashTx [ ] . class ) . method ( "blockchain.scripthash.get_mempool" ) . id ( path + "-" + idCounter . incrementAndGet ( ) ) . params ( pathScriptHashes . get ( path ) ) . execute ( ) ;
ScriptHashTx [ ] scriptHashTxes = new RetryLogic < ScriptHashTx [ ] > ( MAX_RETRIES , RETRY_DELAY , List . of ( IllegalStateException . class , IllegalArgumentException . class ) ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( ScriptHashTx [ ] . class ) . method ( "blockchain.scripthash.get_mempool" ) . id ( path + "-" + idCounter . incrementAndGet ( ) ) . params ( pathScriptHashes . get ( path ) ) . execute ( ) ) ;
result . put ( path , scriptHashTxes ) ;
Thread . sleep ( PER_REQUEST_DELAY_MILLIS ) ;
} catch ( JsonRpcException | IllegalStateException | IllegalArgumentException | InterruptedException e ) {
} catch ( Exception e ) {
if ( failOnError ) {
throw new ElectrumServerRpcException ( "Failed to retrieve reference for path: " + path , e ) ;
}
@ -116,10 +121,10 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
for ( String path : pathScriptHashes . keySet ( ) ) {
EventManager . get ( ) . post ( new WalletHistoryStatusEvent ( false , "Finding transactions for " + path ) ) ;
try {
String scriptHash = client . createRequest ( ) . returnAs ( String . class ) . method ( "blockchain.scripthash.subscribe" ) . id ( path + "-" + idCounter . incrementAndGet ( ) ) . params ( pathScriptHashes . get ( path ) ) . executeNullable ( ) ;
String scriptHash = new RetryLogic < String > ( MAX_RETRIES , RETRY_DELAY , List . of ( IllegalStateException . class , IllegalArgumentException . class ) ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( String . class ) . method ( "blockchain.scripthash.subscribe" ) . id ( path + "-" + idCounter . incrementAndGet ( ) ) . params ( pathScriptHashes . get ( path ) ) . executeNullable ( ) ) ;
result . put ( path , scriptHash ) ;
Thread . sleep ( PER_REQUEST_DELAY_MILLIS ) ;
} catch ( JsonRpcException | IllegalStateException | IllegalArgumentException | InterruptedException e ) {
} catch ( Exception e ) {
//Even if we have some successes, failure to subscribe for all script hashes will result in outdated wallet view. Don't proceed.
throw new ElectrumServerRpcException ( "Failed to retrieve reference for path: " + path , e ) ;
}
@ -136,13 +141,13 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
for ( Integer blockHeight : blockHeights ) {
EventManager . get ( ) . post ( new WalletHistoryStatusEvent ( false , "Retrieving block at height " + blockHeight ) ) ;
try {
String blockHeader = client . createRequest ( ) . returnAs ( String . class ) . method ( "blockchain.block.header" ) . id ( idCounter . incrementAndGet ( ) ) . params ( blockHeight ) . execute ( ) ;
String blockHeader = new RetryLogic < String > ( MAX_RETRIES , RETRY_DELAY , List . of ( IllegalStateException . class , IllegalArgumentException . class ) ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( String . class ) . method ( "blockchain.block.header" ) . id ( idCounter . incrementAndGet ( ) ) . params ( blockHeight ) . execute ( ) ) ;
result . put ( blockHeight , blockHeader ) ;
Thread . sleep ( PER_REQUEST_DELAY_MILLIS ) ;
} catch ( IllegalStateException | IllegalArgumentException | InterruptedException e ) {
log . warn ( "Failed to retrieve block header for block height: " + blockHeight + " (" + e . getMessage ( ) + ")" ) ;
} catch ( JsonRpcException e ) {
log . warn ( "Failed to retrieve block header for block height: " + blockHeight + " (" + e . getErrorMessage ( ) + ")" ) ;
} catch ( Exception e ) {
log . warn ( "Failed to retrieve block header for block height: " + blockHeight + " (" + e . getMessage ( ) + ")" ) ;
}
}
@ -157,10 +162,10 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
for ( String txid : txids ) {
EventManager . get ( ) . post ( new WalletHistoryStatusEvent ( false , "Retrieving transaction [" + txid . substring ( 0 , 6 ) + "]" ) ) ;
try {
String rawTxHex = client . createRequest ( ) . returnAs ( String . class ) . method ( "blockchain.transaction.get" ) . id ( idCounter . incrementAndGet ( ) ) . params ( txid ) . execute ( ) ;
String rawTxHex = new RetryLogic < String > ( MAX_RETRIES , RETRY_DELAY , List . of ( IllegalStateException . class , IllegalArgumentException . class ) ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( String . class ) . method ( "blockchain.transaction.get" ) . id ( idCounter . incrementAndGet ( ) ) . params ( txid ) . execute ( ) ) ;
result . put ( txid , rawTxHex ) ;
Thread . sleep ( PER_REQUEST_DELAY_MILLIS ) ;
} catch ( JsonRpcException | IllegalStateException | IllegalArgumentException | InterruptedException e ) {
} catch ( Exception e ) {
result . put ( txid , Sha256Hash . ZERO_HASH . toString ( ) ) ;
}
}
@ -175,9 +180,9 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
Map < String , VerboseTransaction > result = new LinkedHashMap < > ( ) ;
for ( String txid : txids ) {
try {
VerboseTransaction verboseTransaction = client . createRequest ( ) . returnAs ( VerboseTransaction . class ) . method ( "blockchain.transaction.get" ) . id ( idCounter . incrementAndGet ( ) ) . params ( txid , true ) . execute ( ) ;
VerboseTransaction verboseTransaction = new RetryLogic < VerboseTransaction > ( MAX_RETRIES , RETRY_DELAY , IllegalStateException . class ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( VerboseTransaction . class ) . method ( "blockchain.transaction.get" ) . id ( idCounter . incrementAndGet ( ) ) . params ( txid , true ) . execute ( ) ) ;
result . put ( txid , verboseTransaction ) ;
Thread . sleep ( PER_REQUEST_DELAY_MILLIS ) ;
} catch ( Exception e ) {
//electrs-esplora does not currently support the verbose parameter, so try to fetch an incomplete VerboseTransaction without it
//Note that without the script hash associated with the transaction, we can't get a block height as there is no way in the Electrum RPC protocol to do this
@ -223,14 +228,14 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
for ( Integer targetBlock : targetBlocks ) {
if ( targetBlock < = MAX_TARGET_BLOCKS ) {
try {
Double targetBlocksFeeRateBtcKb = client . createRequest ( ) . returnAs ( Double . class ) . method ( "blockchain.estimatefee" ) . id ( idCounter . incrementAndGet ( ) ) . params ( targetBlock ) . execute ( ) ;
Double targetBlocksFeeRateBtcKb = new RetryLogic < Double > ( MAX_RETRIES , RETRY_DELAY , IllegalStateException . class ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( Double . class ) . method ( "blockchain.estimatefee" ) . id ( idCounter . incrementAndGet ( ) ) . params ( targetBlock ) . execute ( ) ) ;
result . put ( targetBlock , targetBlocksFeeRateBtcKb ) ;
Thread . sleep ( PER_REQUEST_DELAY_MILLIS ) ;
} catch ( IllegalStateException | IllegalArgumentException | InterruptedException e ) {
log . warn ( "Failed to retrieve fee rate for target blocks: " + targetBlock + " (" + e . getMessage ( ) + ")" ) ;
result . put ( targetBlock , result . values ( ) . stream ( ) . mapToDouble ( v - > v ) . min ( ) . orElse ( 0 . 0001d ) ) ;
} catch ( JsonRpcException e ) {
throw new ElectrumServerRpcException ( "Failed to retrieve fee rate for target blocks: " + targetBlock , e ) ;
} catch ( Exception e ) {
log . warn ( "Failed to retrieve fee rate for target blocks: " + targetBlock + " (" + e . getMessage ( ) + ")" ) ;
result . put ( targetBlock , result . values ( ) . stream ( ) . mapToDouble ( v - > v ) . min ( ) . orElse ( 0 . 0001d ) ) ;
}
} else {
result . put ( targetBlock , result . values ( ) . stream ( ) . mapToDouble ( v - > v ) . min ( ) . orElse ( 0 . 0001d ) ) ;
@ -244,8 +249,9 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
public Double getMinimumRelayFee ( Transport transport ) {
try {
JsonRpcClient client = new JsonRpcClient ( transport ) ;
return client . createRequest ( ) . returnAs ( Double . class ) . method ( "blockchain.relayfee" ) . id ( idCounter . incrementAndGet ( ) ) . execute ( ) ;
} catch ( JsonRpcException e ) {
return new RetryLogic < Double > ( MAX_RETRIES , RETRY_DELAY , IllegalStateException . class ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( Double . class ) . method ( "blockchain.relayfee" ) . id ( idCounter . incrementAndGet ( ) ) . execute ( ) ) ;
} catch ( Exception e ) {
throw new ElectrumServerRpcException ( "Error getting minimum relay fee" , e ) ;
}
}
@ -254,11 +260,12 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
public String broadcastTransaction ( Transport transport , String txHex ) {
try {
JsonRpcClient client = new JsonRpcClient ( transport ) ;
return client . createRequest ( ) . returnAs ( String . class ) . method ( "blockchain.transaction.broadcast" ) . id ( idCounter . incrementAndGet ( ) ) . params ( txHex ) . execute ( ) ;
} catch ( IllegalStateException | IllegalArgumentException e ) {
throw new ElectrumServerRpcException ( e . getMessage ( ) , e ) ;
return new RetryLogic < String > ( MAX_RETRIES , RETRY_DELAY , IllegalStateException . class ) . getResult ( ( ) - >
client . createRequest ( ) . returnAs ( String . class ) . method ( "blockchain.transaction.broadcast" ) . id ( idCounter . incrementAndGet ( ) ) . params ( txHex ) . execute ( ) ) ;
} catch ( JsonRpcException e ) {
throw new ElectrumServerRpcException ( e . getErrorMessage ( ) . getMessage ( ) , e ) ;
} catch ( Exception e ) {
throw new ElectrumServerRpcException ( e . getMessage ( ) , e ) ;
}
}
}