Skip to content

Commit

Permalink
Check transaction is not replaced already
Browse files Browse the repository at this point in the history
  • Loading branch information
omurovch committed Mar 26, 2024
1 parent 29b60ca commit df2efd2
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ class BitcoinCoreBuilder {
val signer = TransactionSigner(ecdsaInputSigner, schnorrInputSigner)
transactionCreator = TransactionCreator(transactionBuilder, pendingTransactionProcessor, transactionSenderInstance, signer, bloomFilterManager)
replacementTransactionBuilder = ReplacementTransactionBuilder(
storage, transactionSizeCalculator, dustCalculator, metadataExtractor, pluginManager, unspentOutputProvider, publicKeyManager
storage, transactionSizeCalculator, dustCalculator, metadataExtractor, pluginManager, unspentOutputProvider, publicKeyManager, conflictsResolver
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ open class TransactionInfo {
var conflictingTxHash: String? = null
var rbfEnabled: Boolean = false

val replaceable: Boolean
get() = rbfEnabled && blockHeight == null && conflictingTxHash == null

constructor(
uid: String,
transactionHash: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.horizontalsystems.bitcoincore.storage.FullTransactionInfo
import io.horizontalsystems.bitcoincore.storage.InputToSign
import io.horizontalsystems.bitcoincore.storage.InputWithPreviousOutput
import io.horizontalsystems.bitcoincore.storage.UnspentOutput
import io.horizontalsystems.bitcoincore.transactions.TransactionConflictsResolver
import io.horizontalsystems.bitcoincore.transactions.TransactionSizeCalculator
import io.horizontalsystems.bitcoincore.transactions.builder.MutableTransaction
import io.horizontalsystems.bitcoincore.transactions.extractors.TransactionMetadataExtractor
Expand All @@ -31,6 +32,7 @@ class ReplacementTransactionBuilder(
private val pluginManager: PluginManager,
private val unspentOutputProvider: UnspentOutputProvider,
private val publicKeyManager: IPublicKeyManager,
private val conflictsResolver: TransactionConflictsResolver
) {

private fun replacementTransaction(
Expand Down Expand Up @@ -279,7 +281,13 @@ class ReplacementTransactionBuilder(
val descendantTransactions = storage.getDescendantTransactionsFullInfo(transactionHash.toReversedByteArray())
val absoluteFee = descendantTransactions.sumOf { it.metadata.fee ?: 0 }

check(descendantTransactions.all { it.header.conflictingTxHash == null }) { throw BuildError.InvalidTransaction("Already replaced") }
check(
descendantTransactions.all { it.header.conflictingTxHash == null } &&
!conflictsResolver.isTransactionReplaced(originalFullInfo.fullTransaction)
) {
throw BuildError.InvalidTransaction("Already replaced")
}

check(absoluteFee <= minFee) { throw BuildError.FeeTooLow }

val mutableTransaction = when (type) {
Expand Down Expand Up @@ -329,6 +337,13 @@ class ReplacementTransactionBuilder(
val descendantTransactions = storage.getDescendantTransactionsFullInfo(transactionHash.toReversedByteArray())
val absoluteFee = descendantTransactions.sumOf { it.metadata.fee ?: 0 }

check(
descendantTransactions.all { it.header.conflictingTxHash == null } &&
!conflictsResolver.isTransactionReplaced(originalFullInfo.fullTransaction)
) {
return null
}

val replacementTxMinSize: Long
val removableOutputsValue: Long

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ class FullTransactionInfo(

val rawTransaction: String
get() {
val fullTransaction = FullTransaction(header, inputs.map { it.input }, outputs)
return TransactionSerializer.serialize(fullTransaction).toHexString()
}

val fullTransaction: FullTransaction
get() = FullTransaction(header, inputs.map { it.input }, outputs)

}

Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ class TransactionConflictsResolver(private val storage: IStorage) {
}
}

// Checks if the transactions has a conflicting input with higher sequence
fun isTransactionReplaced(transaction: FullTransaction): Boolean {
val conflictingTransactions = getConflictingTransactionsForTransaction(transaction)

if (conflictingTransactions.isEmpty() || conflictingTransactions.any { it.blockHash == null }) {
return false
}

val conflictingFullTransactions = storage.getFullTransactions(conflictingTransactions)

return conflictingFullTransactions
.any { existingHasHigherSequence(mempoolTransaction = transaction, existingTransaction = it) }
}

private fun getConflictingTransactionsForTransaction(transaction: FullTransaction): List<Transaction> {
return transaction.inputs.mapNotNull { input ->
val conflictingTxHash = storage.getTransactionInput(input.previousOutputTxHash, input.previousOutputIndex)?.transactionHash
Expand Down

0 comments on commit df2efd2

Please sign in to comment.