Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement sending transaction using Blockchair API #655

Merged
merged 2 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.horizontalsystems.bitcoincore

import io.horizontalsystems.bitcoincore.apisync.blockchair.BlockchairApi
import io.horizontalsystems.bitcoincore.blocks.IPeerSyncListener
import io.horizontalsystems.bitcoincore.core.DataProvider
import io.horizontalsystems.bitcoincore.core.IConnectionManager
Expand Down Expand Up @@ -519,4 +520,9 @@ class BitcoinCore(
object ReadOnlyCore : CoreError()
}

sealed class SendType {
object P2P: SendType()
class API(val blockchairApi: BlockchairApi): SendType()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import io.horizontalsystems.bitcoincore.network.peer.PeerGroup
import io.horizontalsystems.bitcoincore.network.peer.PeerManager
import io.horizontalsystems.bitcoincore.rbf.ReplacementTransactionBuilder
import io.horizontalsystems.bitcoincore.serializers.BlockHeaderParser
import io.horizontalsystems.bitcoincore.serializers.TransactionSerializer
import io.horizontalsystems.bitcoincore.transactions.BlockTransactionProcessor
import io.horizontalsystems.bitcoincore.transactions.PendingTransactionProcessor
import io.horizontalsystems.bitcoincore.transactions.SendTransactionsOnPeersSynced
Expand Down Expand Up @@ -143,6 +144,7 @@ class BitcoinCoreBuilder {
private var peerSize = 10
private val plugins = mutableListOf<IPlugin>()
private var handleAddrMessage = true
private var sendType: BitcoinCore.SendType = BitcoinCore.SendType.P2P

fun setContext(context: Context): BitcoinCoreBuilder {
this.context = context
Expand Down Expand Up @@ -184,6 +186,11 @@ class BitcoinCoreBuilder {
return this
}

fun setSendType(sendType: BitcoinCore.SendType): BitcoinCoreBuilder {
this.sendType = sendType
return this
}

fun setPeerSize(peerSize: Int): BitcoinCoreBuilder {
if (peerSize < TransactionSender.minConnectedPeerSize) {
throw Error("Peer size cannot be less than ${TransactionSender.minConnectedPeerSize}")
Expand Down Expand Up @@ -480,7 +487,9 @@ class BitcoinCoreBuilder {
peerManager,
initialDownload,
storage,
transactionSendTimer
transactionSendTimer,
sendType,
TransactionSerializer
)

dustCalculator = dustCalculatorInstance
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.horizontalsystems.bitcoincore.apisync.blockchair

import com.eclipsesource.json.Json
import com.eclipsesource.json.JsonObject
import io.horizontalsystems.bitcoincore.apisync.model.AddressItem
import io.horizontalsystems.bitcoincore.apisync.model.BlockHeaderItem
import io.horizontalsystems.bitcoincore.apisync.model.TransactionItem
Expand Down Expand Up @@ -76,6 +78,17 @@ class BlockchairApi(
return BlockHeaderItem(hash.hexToByteArray(), height, timestamp!!)
}

fun broadcastTransaction(rawTransactionHex: String) {
val apiManager = ApiManager("https://api.blockchair.com")
val url = "$chainId/push/transaction"

val body = JsonObject().apply {
this["data"] = Json.value(rawTransactionHex)
}.toString()

apiManager.post(url, body)
}

private fun fetchTransactions(
addresses: List<String>,
stopHeight: Int? = null,
Expand Down Expand Up @@ -125,7 +138,7 @@ class BlockchairApi(

private fun dateStringToTimestamp(date: String): Long? {
return try {
dateFormat.parse(date)?.time?.let { it / 1000 }
dateFormat.parse(date)?.time?.let { it / 1000 }
} catch (e: ParseException) {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package io.horizontalsystems.bitcoincore.managers

import com.eclipsesource.json.Json
import com.eclipsesource.json.JsonValue
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.BufferedOutputStream
import java.io.BufferedWriter
import java.io.IOException
Expand All @@ -30,6 +32,7 @@ class ApiManager(private val host: String) {
connectTimeout = 5000
readTimeout = 60000
setRequestProperty("Accept", "application/json")
setRequestProperty("content-type", "application/json")
}.getInputStream()
} catch (exception: IOException) {
throw ApiManagerException.Other("${exception.javaClass.simpleName}: $host")
Expand All @@ -46,6 +49,7 @@ class ApiManager(private val host: String) {
val url = URL(path)
val urlConnection = url.openConnection() as HttpURLConnection
urlConnection.requestMethod = "POST"
urlConnection.setRequestProperty("Content-Type", "application/json")
val out = BufferedOutputStream(urlConnection.outputStream)
val writer = BufferedWriter(OutputStreamWriter(out, "UTF-8"))
writer.write(data)
Expand Down Expand Up @@ -95,6 +99,7 @@ class ApiManager(private val host: String) {
throw ApiManagerException.Other("${e.javaClass.simpleName}: $host, ${e.localizedMessage}")
}
}

}

sealed class ApiManagerException : Exception() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package io.horizontalsystems.bitcoincore.transactions

import io.horizontalsystems.bitcoincore.blocks.InitialBlockDownload
import io.horizontalsystems.bitcoincore.BitcoinCore
import io.horizontalsystems.bitcoincore.apisync.blockchair.BlockchairApi
import io.horizontalsystems.bitcoincore.core.IInitialDownload
import io.horizontalsystems.bitcoincore.core.IStorage
import io.horizontalsystems.bitcoincore.extensions.toHexString
import io.horizontalsystems.bitcoincore.models.SentTransaction
import io.horizontalsystems.bitcoincore.network.peer.IPeerTaskHandler
import io.horizontalsystems.bitcoincore.network.peer.Peer
import io.horizontalsystems.bitcoincore.network.peer.PeerGroup
import io.horizontalsystems.bitcoincore.network.peer.PeerManager
import io.horizontalsystems.bitcoincore.network.peer.task.PeerTask
import io.horizontalsystems.bitcoincore.network.peer.task.SendTransactionTask
import io.horizontalsystems.bitcoincore.serializers.TransactionSerializer
import io.horizontalsystems.bitcoincore.storage.FullTransaction

class TransactionSender(
private val transactionSyncer: TransactionSyncer,
private val peerManager: PeerManager,
private val initialBlockDownload: IInitialDownload,
private val storage: IStorage,
private val timer: TransactionSendTimer,
private val maxRetriesCount: Int = 3,
private val retriesPeriod: Int = 60)
: IPeerTaskHandler, TransactionSendTimer.Listener {
private val transactionSyncer: TransactionSyncer,
private val peerManager: PeerManager,
private val initialBlockDownload: IInitialDownload,
private val storage: IStorage,
private val timer: TransactionSendTimer,
private val sendType: BitcoinCore.SendType,
private val transactionSerializer: TransactionSerializer,
private val maxRetriesCount: Int = 3,
private val retriesPeriod: Int = 60
) : IPeerTaskHandler, TransactionSendTimer.Listener {

fun sendPendingTransactions() {
try {
Expand Down Expand Up @@ -68,13 +73,13 @@ class TransactionSender(
}

val freeSyncedPeer = initialBlockDownload.syncedPeers
.sortedBy { it.ready } // not ready first
.firstOrNull()
?: return emptyList()
.sortedBy { it.ready } // not ready first
.firstOrNull()
?: return emptyList()

val readyPeers = peerManager.readyPears()
.filter { it != freeSyncedPeer }
.sortedBy { it.synced } // not synced first
.filter { it != freeSyncedPeer }
.sortedBy { it.synced } // not synced first

if (readyPeers.size == 1) {
return readyPeers
Expand All @@ -84,6 +89,31 @@ class TransactionSender(
}

private fun send(transactions: List<FullTransaction>) {
when (sendType) {
BitcoinCore.SendType.P2P -> {
sendViaP2P(transactions)
}

is BitcoinCore.SendType.API -> {
sendViaAPI(transactions, sendType.blockchairApi)
}
}
}

private fun sendViaAPI(transactions: List<FullTransaction>, blockchairApi: BlockchairApi) {
transactions.forEach { transaction ->
try {
val hex = transactionSerializer.serialize(transaction).toHexString()
blockchairApi.broadcastTransaction(hex)

transactionSyncer.handleRelayed(listOf(transaction))
} catch (error: Throwable) {
transactionSyncer.handleInvalid(transaction)
}
}
}

private fun sendViaP2P(transactions: List<FullTransaction>) {
val peers = getPeersToSend()
if (peers.isEmpty()) {
return
Expand Down Expand Up @@ -139,6 +169,7 @@ class TransactionSender(
transactionSendSuccess(task.transaction)
true
}

else -> false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import io.horizontalsystems.bitcoincore.storage.Storage
import io.horizontalsystems.bitcoincore.transactions.scripts.ScriptType
import io.horizontalsystems.bitcoincore.utils.AddressConverterChain
import io.horizontalsystems.bitcoincore.utils.Base58AddressConverter
import io.horizontalsystems.bitcoincore.utils.CashAddressConverter
import io.horizontalsystems.bitcoincore.utils.PaymentAddressParser
import io.horizontalsystems.bitcoincore.utils.SegwitAddressConverter
import io.horizontalsystems.hdwalletkit.HDExtendedKey
Expand Down Expand Up @@ -167,7 +166,8 @@ class LitecoinKit : AbstractKit {
val storage = Storage(database)
val checkpoint = Checkpoint.resolveCheckpoint(syncMode, network, storage)
val apiSyncStateManager = ApiSyncStateManager(storage, network.syncableFromApi && syncMode !is SyncMode.Full)
val apiTransactionProvider = apiTransactionProvider(networkType, syncMode, apiSyncStateManager)
val blockchairApi = BlockchairApi(network.blockchairChainId)
val apiTransactionProvider = apiTransactionProvider(networkType, syncMode, apiSyncStateManager, blockchairApi)
val paymentAddressParser = PaymentAddressParser("litecoin", removeScheme = true)
val blockValidatorSet = blockValidatorSet(storage, networkType)

Expand All @@ -183,6 +183,7 @@ class LitecoinKit : AbstractKit {
.setPaymentAddressParser(paymentAddressParser)
.setPeerSize(peerSize)
.setSyncMode(syncMode)
.setSendType(BitcoinCore.SendType.API(blockchairApi))
.setConfirmationThreshold(confirmationsThreshold)
.setStorage(storage)
.setApiTransactionProvider(apiTransactionProvider)
Expand Down Expand Up @@ -260,13 +261,13 @@ class LitecoinKit : AbstractKit {
private fun apiTransactionProvider(
networkType: NetworkType,
syncMode: SyncMode,
apiSyncStateManager: ApiSyncStateManager
apiSyncStateManager: ApiSyncStateManager,
blockchairApi: BlockchairApi
) = when (networkType) {
NetworkType.MainNet -> {
val bCoinApiProvider = BCoinApi("https://ltc.blocksdecoded.com/api")

if (syncMode is SyncMode.Blockchair) {
val blockchairApi = BlockchairApi(network.blockchairChainId)
val blockchairBlockHashFetcher = BlockchairBlockHashFetcher(blockchairApi)
val blockchairProvider = BlockchairTransactionProvider(blockchairApi, blockchairBlockHashFetcher)

Expand Down
Loading