NonceQueueManager.kt
package io.github.psychoplasma.nonceq.queue
import io.github.psychoplasma.nonceq.NonceManager
import io.github.psychoplasma.nonceq.utils.BlockNonceProvider
import java.math.BigInteger
import java.util.concurrent.Semaphore
internal class NonceQueueManager(
private val blockNonceProvider: BlockNonceProvider,
private val nonceQueue: NonceQueue,
) : NonceManager {
private val semaphore = Semaphore(1)
override fun getNextValidNonce(address: String): BigInteger {
semaphore.acquire()
try {
val neutralizedAddress = address.lowercase()
// Initialize with block nonce if empty
if (nonceQueue.isEmpty(neutralizedAddress)) {
val blockNonce = blockNonceProvider.getBlockNonce(
neutralizedAddress,
)
nonceQueue.insert(neutralizedAddress, blockNonce)
return blockNonce
}
return nonceQueue.next(neutralizedAddress)
} finally {
semaphore.release()
}
}
override fun useNonce(address: String, nonce: BigInteger, txId: String?) {
semaphore.acquire()
try {
nonceQueue.markUsed(address.lowercase(), nonce)
} finally {
semaphore.release()
}
}
override fun discardNonce(
address: String,
nonce: BigInteger,
errorMessage: String?,
) {
semaphore.acquire()
try {
val neutralizedAddress = address.lowercase()
// If somehow nonce manager falls behind blockchain nonce,
// then reset the queue and start with the recent nonce value
// in the next iteration.
if (
errorMessage?.contains("nonce too low", true) == true
&& !nonceQueue.isEmpty(neutralizedAddress)
) {
nonceQueue.reset(neutralizedAddress)
}
else {
nonceQueue.remove(neutralizedAddress, nonce)
}
} finally {
semaphore.release()
}
}
override fun reset(address: String) {
semaphore.acquire()
try {
nonceQueue.reset(address.lowercase())
} finally {
semaphore.release()
}
}
}