diff --git a/database/src/main/resources/db/migration/V1_91__Create_tx_processing_failures_table.sql b/database/src/main/resources/db/migration/V1_91__Create_tx_processing_failures_table.sql new file mode 100644 index 00000000..e9511adc --- /dev/null +++ b/database/src/main/resources/db/migration/V1_91__Create_tx_processing_failures_table.sql @@ -0,0 +1,27 @@ +SELECT 'Create tx_processing_failures table' AS comment; +CREATE TABLE IF NOT EXISTS tx_processing_failures +( + block_height + INT + NOT + NULL, + tx_hash + VARCHAR +( + 128 +) NOT NULL, + process_type VARCHAR +( + 64 +) NOT NULL, + failure_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + error_message TEXT DEFAULT NULL, + retried BOOLEAN NOT NULL DEFAULT FALSE, + success BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY +( + block_height, + tx_hash, + process_type +) + ); \ No newline at end of file diff --git a/service/src/main/kotlin/io/provenance/explorer/domain/entities/Blocks.kt b/service/src/main/kotlin/io/provenance/explorer/domain/entities/Blocks.kt index e51ecf02..1f0238e1 100644 --- a/service/src/main/kotlin/io/provenance/explorer/domain/entities/Blocks.kt +++ b/service/src/main/kotlin/io/provenance/explorer/domain/entities/Blocks.kt @@ -582,3 +582,70 @@ class BlockTxRetryRecord(id: EntityID) : IntEntity(id) { var success by BlockTxRetryTable.success var errorBlock by BlockTxRetryTable.errorBlock } + +object TxProcessingFailuresTable : IdTable(name = "tx_processing_failures") { + val blockHeight = integer("block_height") + val txHash = varchar("tx_hash", 128) + val processType = varchar("process_type", 64) + val failureTime = datetime("failure_time") + val errorMessage = text("error_message").nullable() + val retried = bool("retried").default(false) + val success = bool("success").default(false) + + override val id = integer("id").entityId() + + init { + index(true, blockHeight, txHash, processType) + } +} + +class TxProcessingFailureRecord(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(TxProcessingFailuresTable) { + + fun insertOrUpdate( + blockHeight: Int, + txHash: String, + processType: String, + errorMessage: String?, + success: Boolean + ) = transaction { + val existingRecord = TxProcessingFailureRecord.find { + (TxProcessingFailuresTable.blockHeight eq blockHeight) and + (TxProcessingFailuresTable.txHash eq txHash) and + (TxProcessingFailuresTable.processType eq processType) + }.firstOrNull() + + if (existingRecord == null) { + TxProcessingFailuresTable.insertIgnore { + it[this.blockHeight] = blockHeight + it[this.txHash] = txHash + it[this.processType] = processType + it[this.errorMessage] = errorMessage + it[this.success] = success + } + } else { + existingRecord.apply { + this.errorMessage = errorMessage + this.success = success + this.retried = true + this.failureTime = DateTime.now() + } + } + } + + fun deleteProcessedRecords() = transaction { + TxProcessingFailuresTable.deleteWhere { + (TxProcessingFailuresTable.retried eq true) and + (TxProcessingFailuresTable.success eq true) + } + } + } + + var blockHeight by TxProcessingFailuresTable.blockHeight + var txHash by TxProcessingFailuresTable.txHash + var processType by TxProcessingFailuresTable.processType + var failureTime by TxProcessingFailuresTable.failureTime + var errorMessage by TxProcessingFailuresTable.errorMessage + var retried by TxProcessingFailuresTable.retried + var success by TxProcessingFailuresTable.success +} \ No newline at end of file diff --git a/service/src/main/kotlin/io/provenance/explorer/service/async/BlockAndTxProcessor.kt b/service/src/main/kotlin/io/provenance/explorer/service/async/BlockAndTxProcessor.kt index 2f98f509..580c718a 100644 --- a/service/src/main/kotlin/io/provenance/explorer/service/async/BlockAndTxProcessor.kt +++ b/service/src/main/kotlin/io/provenance/explorer/service/async/BlockAndTxProcessor.kt @@ -8,40 +8,7 @@ import io.provenance.explorer.config.ExplorerProperties import io.provenance.explorer.domain.core.logger import io.provenance.explorer.domain.core.sql.toArray import io.provenance.explorer.domain.core.sql.toObject -import io.provenance.explorer.domain.entities.AccountRecord -import io.provenance.explorer.domain.entities.BlockCacheRecord -import io.provenance.explorer.domain.entities.BlockTxRetryRecord -import io.provenance.explorer.domain.entities.FeePayer -import io.provenance.explorer.domain.entities.IbcAckType -import io.provenance.explorer.domain.entities.IbcLedgerRecord -import io.provenance.explorer.domain.entities.IbcRelayerRecord -import io.provenance.explorer.domain.entities.NameRecord -import io.provenance.explorer.domain.entities.ProcessQueueRecord -import io.provenance.explorer.domain.entities.ProcessQueueType -import io.provenance.explorer.domain.entities.SignatureRecord -import io.provenance.explorer.domain.entities.SignatureTxRecord -import io.provenance.explorer.domain.entities.TxAddressJoinRecord -import io.provenance.explorer.domain.entities.TxAddressJoinType -import io.provenance.explorer.domain.entities.TxCacheRecord -import io.provenance.explorer.domain.entities.TxEventAttrRecord -import io.provenance.explorer.domain.entities.TxEventAttrTable -import io.provenance.explorer.domain.entities.TxEventRecord -import io.provenance.explorer.domain.entities.TxFeeRecord -import io.provenance.explorer.domain.entities.TxFeepayerRecord -import io.provenance.explorer.domain.entities.TxGasCacheRecord -import io.provenance.explorer.domain.entities.TxIbcRecord -import io.provenance.explorer.domain.entities.TxMarkerJoinRecord -import io.provenance.explorer.domain.entities.TxMessageRecord -import io.provenance.explorer.domain.entities.TxMsgTypeSubtypeRecord -import io.provenance.explorer.domain.entities.TxMsgTypeSubtypeTable -import io.provenance.explorer.domain.entities.TxNftJoinRecord -import io.provenance.explorer.domain.entities.TxSingleMessageCacheRecord -import io.provenance.explorer.domain.entities.TxSmCodeRecord -import io.provenance.explorer.domain.entities.TxSmContractRecord -import io.provenance.explorer.domain.entities.ValidatorMarketRateRecord -import io.provenance.explorer.domain.entities.ValidatorStateRecord -import io.provenance.explorer.domain.entities.buildInsert -import io.provenance.explorer.domain.entities.updateHitCount +import io.provenance.explorer.domain.entities.* import io.provenance.explorer.domain.exceptions.InvalidArgumentException import io.provenance.explorer.domain.extensions.TX_ACC_SEQ import io.provenance.explorer.domain.extensions.TX_EVENT @@ -266,7 +233,6 @@ class AsyncCachingV2( listOf() } - // Function that saves all the things under a transaction fun processAndSaveTransactionData( res: ServiceOuterClass.GetTxResponse, blockTime: DateTime, @@ -276,13 +242,25 @@ class AsyncCachingV2( val txUpdate = TxUpdate(tx) val txInfo = TxData(proposerRec.blockHeight, null, res.txResponse.txhash, blockTime) + // TODO: See: https://github.com/provenance-io/explorer-service/issues/538 saveMessages(txInfo, res, txUpdate) saveTxFees(res, txInfo, txUpdate, proposerRec) val addrs = saveAddresses(txInfo, res, txUpdate) val markers = saveMarkers(txInfo, res, txUpdate) saveNftData(txInfo, res, txUpdate) saveGovData(res, txInfo, txUpdate) - saveIbcChannelData(res, txInfo, txUpdate) + try { + saveIbcChannelData(res, txInfo, txUpdate) + } catch (e: Exception) { + logger.error("Failed to process IBC channel data for tx ${txInfo.txHash} at height ${txInfo.blockHeight}. Error: ${e.message}") + TxProcessingFailureRecord.insertOrUpdate( + txInfo.blockHeight, + txInfo.txHash, + "ibc_channel_data", + e.stackTraceToString(), + false + ) + } saveSmartContractData(res, txInfo, txUpdate) saveNameData(res, txInfo) groupService.saveGroups(res, txInfo, txUpdate)