diff --git a/build.gradle b/build.gradle index 5904868..16493a1 100644 --- a/build.gradle +++ b/build.gradle @@ -63,11 +63,9 @@ subprojects { implementation 'org.web3j:core:4.10.3' implementation 'wf.bitcoin:bitcoin-rpc-client:1.2.4' - implementation "org.springframework.boot:spring-boot-starter-amqp:$springBootVersion" implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' implementation 'com.github.sealedtx:bitcoin-cash-converter:1.0' - implementation 'com.xuxueli:xxl-job-core:2.4.0' - implementation 'com.github.GalaxySciTech:tokencore:1.3.0' + implementation 'com.github.galaxyscitech:tokencore:2.0.0' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' diff --git a/settings.gradle b/settings.gradle index ddbdfce..21644bb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,5 @@ include( "wallet-common", "wallet-entity", "wallet-webapi", - "wallet-hsm", - "wallet-task" + "wallet-hsm" ) diff --git a/wallet-common/build.gradle b/wallet-common/build.gradle index 8320e10..7d668ea 100644 --- a/wallet-common/build.gradle +++ b/wallet-common/build.gradle @@ -5,6 +5,7 @@ jar.enabled = true dependencies { api project(":wallet-entity") + implementation project(":wallet-hsm") implementation 'commons-codec:commons-codec:1.16.0' implementation 'org.bitcoinj:bitcoinj-core:0.14.7' } diff --git a/wallet-common/src/main/kotlin/com/wallet/biz/dict/SysConfigKey.kt b/wallet-common/src/main/kotlin/com/wallet/biz/dict/SysConfigKey.kt index 7a1dbac..b38c4b9 100644 --- a/wallet-common/src/main/kotlin/com/wallet/biz/dict/SysConfigKey.kt +++ b/wallet-common/src/main/kotlin/com/wallet/biz/dict/SysConfigKey.kt @@ -1,7 +1,15 @@ package com.wallet.biz.dict -/**
 - * Created by pie on 2020/7/24 18: 50.
 + DEPOSIT_NOTIFY_MODE("post","充值同步模式,支持 post","充值通知"), + SCHEDULER_DEPOSIT_SCAN_ENABLED("true", "充值扫描任务开关", "任务调度"), + SCHEDULER_DEPOSIT_SCAN_MS("15000", "充值扫描任务间隔毫秒", "任务调度"), + SCHEDULER_CHAIN_SYNC_ENABLED("true", "区块同步任务开关", "任务调度"), + SCHEDULER_CHAIN_SYNC_MS("20000", "区块同步任务间隔毫秒", "任务调度"), + SCHEDULER_SWEEP_ENABLED("false", "归集任务开关", "任务调度"), + SCHEDULER_SWEEP_MS("30000", "归集任务间隔毫秒", "任务调度"), + SCHEDULER_FEE_SUPPLY_ENABLED("false", "手续费补充任务开关", "任务调度"), + SCHEDULER_FEE_SUPPLY_MS("30000", "手续费补充任务间隔毫秒", "任务调度"); + */ enum class SysConfigKey(var defaultValue:String,var description:String,var group:String) { diff --git a/wallet-common/src/main/kotlin/com/wallet/biz/mq/PushComponent.kt b/wallet-common/src/main/kotlin/com/wallet/biz/mq/PushComponent.kt index 69bf0eb..90f925e 100644 --- a/wallet-common/src/main/kotlin/com/wallet/biz/mq/PushComponent.kt +++ b/wallet-common/src/main/kotlin/com/wallet/biz/mq/PushComponent.kt @@ -1,13 +1,11 @@ package com.wallet.biz.mq import com.fasterxml.jackson.databind.JsonNode -import com.wallet.biz.dict.MqKey import com.wallet.biz.dict.SysConfigKey import com.wallet.biz.domain.dict.ErrorCode import com.wallet.biz.domain.exception.BizException import com.wallet.biz.log.impl.LogService import com.wallet.biz.utils.Crypto -import org.springframework.amqp.core.AmqpTemplate import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component @@ -25,7 +23,7 @@ class PushComponent : LogService() { } fun sendMsgToMq(msg: String) { - amqpTemplate.convertAndSend(MqKey.TOPIC_EXCHANGE, MqKey.DEPOSIT_KEY, msg) + sendMsgToService(msg) } fun sendMsgToService(msg: String) { @@ -39,9 +37,6 @@ class PushComponent : LogService() { } } - @Autowired - lateinit var amqpTemplate: AmqpTemplate - @Autowired lateinit var restTemplate: RestTemplate diff --git a/wallet-common/src/main/kotlin/com/wallet/biz/request/HsmRequest.kt b/wallet-common/src/main/kotlin/com/wallet/biz/request/HsmRequest.kt index c46c582..c024254 100644 --- a/wallet-common/src/main/kotlin/com/wallet/biz/request/HsmRequest.kt +++ b/wallet-common/src/main/kotlin/com/wallet/biz/request/HsmRequest.kt @@ -1,163 +1,29 @@ package com.wallet.biz.request -import com.wallet.biz.dict.HsmReuqestType -import com.wallet.biz.dict.SysConfigKey -import com.wallet.biz.domain.dict.ErrorCode -import com.wallet.biz.domain.dict.TokenResponse -import com.wallet.biz.domain.exception.BizException import com.wallet.biz.domain.po.* import com.wallet.biz.domain.vo.AddressVo -import com.fasterxml.jackson.databind.ObjectMapper import org.consenlabs.tokencore.wallet.transaction.BitcoinTransaction import org.consenlabs.tokencore.wallet.transaction.TxSignResult -import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired -import org.springframework.core.ParameterizedTypeReference -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod import org.springframework.stereotype.Component -import org.springframework.web.client.RestTemplate import java.math.BigDecimal -import java.util.* -/**
 - * Created by pie on 2019-04-13 16: 01.
 - */ -@Component -class HsmRequest { - - private val logger = LoggerFactory.getLogger(HsmRequest::class.java) - - private fun getHsmUrl(): String { - return cacheService.getSysConfig(SysConfigKey.HSM_URL) - } - - fun getAllWallets():List{ - return exchange( - "${getHsmUrl()}${HsmReuqestType.GET_ALL_WALLETS}", - HttpMethod.GET, - null, - List::class.java - ) - .map { obj.readValue(obj.writeValueAsString(it), AddressVo::class.java) } - } - - fun deriveWallets(chainTypes: List): List { - return exchange( - "${getHsmUrl()}${HsmReuqestType.DERIVE_WALLETS}", - HttpMethod.POST, - chainTypes, - List::class.java - ) - .map { obj.readValue(obj.writeValueAsString(it), AddressVo::class.java) } + return hsmXService.getAllWallets() } - fun signUsdtTransaction( - utxos: ArrayList, - amount: BigDecimal, - fee: BigDecimal, - toAddress: String, - walletId: String - ): TxSignResult { - val signUsdtPo = SignUsdtPo() - signUsdtPo.utxos = utxos - signUsdtPo.amount = amount - signUsdtPo.fee = fee - signUsdtPo.toAddress = toAddress - signUsdtPo.walletId = walletId - return exchange( - "${getHsmUrl()}${HsmReuqestType.SIGN_USDT_TRANSACTION}", - HttpMethod.POST, - signUsdtPo, - TxSignResult::class.java - ) - } - - fun signBtcTransaction( - amount: BigDecimal, - fee: BigDecimal, - toAddress: String, - utxos: ArrayList, - walletId: String - ): TxSignResult { - val signBitcoinPo = SignBitcoinPo() - signBitcoinPo.utxos = utxos - signBitcoinPo.amount = amount - signBitcoinPo.fee = fee - signBitcoinPo.toAddress = toAddress - signBitcoinPo.walletId = walletId - return exchange( - "${getHsmUrl()}${HsmReuqestType.SIGN_BTC_TRANSACTION}", - HttpMethod.POST, - signBitcoinPo, - TxSignResult::class.java - ) - } - - fun signEthtransaction( - nonce: Int, - amount: BigDecimal, - gasPrice: BigDecimal, - gasLimit: Long, - toAddress: String, - walletId: String, - data: String? - ): TxSignResult { - val signEthereumPo = SignEthereumPo() - signEthereumPo.walletId = walletId - signEthereumPo.amount = amount - signEthereumPo.toAddress = toAddress - signEthereumPo.nonce = nonce - signEthereumPo.gasPrice = gasPrice - signEthereumPo.data = data - signEthereumPo.gasLimit = gasLimit - return exchange( - "${getHsmUrl()}${HsmReuqestType.SIGN_ETH_TRANSACTION}", - HttpMethod.POST, - signEthereumPo, - TxSignResult::class.java - ) - } - - fun signUsdtCollectTransaction( - toAddress: String, - amount: BigDecimal, - fee: BigDecimal, - utxos: ArrayList, - feeProviderUtxos: ArrayList, - walletId: String, - feeProviderWalletId: String - ): TxSignResult { - val signUsdtCollectPo = SignUsdtCollectPo() - signUsdtCollectPo.feeProviderUtxos = feeProviderUtxos - signUsdtCollectPo.fee = fee - signUsdtCollectPo.feeProviderWalletId = feeProviderWalletId - signUsdtCollectPo.walletId = walletId - signUsdtCollectPo.toAddress = toAddress - signUsdtCollectPo.amount = amount - signUsdtCollectPo.utxos = utxos - return exchange( - "${getHsmUrl()}${HsmReuqestType.SIGN_USDT_COLLECT_TRANSACTION}", - HttpMethod.POST, - signUsdtCollectPo, - TxSignResult::class.java - ) - } - - - fun checkWallet(walletCode: String): String { - - return exchange( - "${getHsmUrl()}${HsmReuqestType.CHECK_WALLET}/$walletCode", - HttpMethod.GET, - null, - String::class.java - ) - } - - fun exportWallet(walletCode: String, type: Int): String { - return exchange( + return hsmXService.deriveWallets(chainTypes) + return hsmXService.signUsdtTransaction(signUsdtPo) + fun signBtcTransaction(amount: BigDecimal, fee: BigDecimal, toAddress: String, utxos: ArrayList, walletId: String): TxSignResult { + return hsmXService.signBitcoinTransaction(signBitcoinPo) + fun signEthtransaction(nonce: Int, amount: BigDecimal, gasPrice: BigDecimal, gasLimit: Long, toAddress: String, walletId: String, data: String?): TxSignResult { + return hsmXService.signEthereumTransaction(signEthereumPo) + fun signUsdtCollectTransaction(toAddress: String, amount: BigDecimal, fee: BigDecimal, utxos: ArrayList, feeProviderUtxos: ArrayList, walletId: String, feeProviderWalletId: String): TxSignResult { + return hsmXService.signUsdtCollectTransaction(signUsdtCollectPo) + fun checkWallet(walletCode: String): String = hsmXService.getAllWallets().firstOrNull { it.walletCode == walletCode }?.address ?: "" + fun exportWallet(walletCode: String, type: Int): String = hsmXService.exportWallet(walletCode, type) + fun removeUselessWallet(map: Map) { hsmXService.removeUselessWallet(map) } + fun importWallet(importWalletPo: ImportWalletPo): AddressVo = hsmXService.importWallet(importWalletPo) + lateinit var hsmXService: com.wallet.hsm.xservice.HsmXService "${getHsmUrl()}${HsmReuqestType.EXPORT_WALLET}/$walletCode/$type", HttpMethod.GET, null, diff --git a/wallet-webapi/build.gradle b/wallet-webapi/build.gradle index f2a7f86..8e3e088 100644 --- a/wallet-webapi/build.gradle +++ b/wallet-webapi/build.gradle @@ -8,6 +8,7 @@ mainClassName = "com.wallet.WebApiApplicationKt" dependencies { implementation project(":wallet-common") + implementation project(":wallet-hsm") } jib { @@ -19,7 +20,7 @@ jib { } container { mainClass = 'com.wallet.WebApiApplicationKt' - ports = ['10001'] + ports = ['8080'] jvmFlags = ['-Xms256m', '-Xmx512m', '-Djava.security.egd=file:/dev/./urandom'] } } diff --git a/wallet-webapi/src/main/kotlin/com/wallet/webapi/controller/AdminController.kt b/wallet-webapi/src/main/kotlin/com/wallet/webapi/controller/AdminController.kt index 2ec59bc..fc06a57 100644 --- a/wallet-webapi/src/main/kotlin/com/wallet/webapi/controller/AdminController.kt +++ b/wallet-webapi/src/main/kotlin/com/wallet/webapi/controller/AdminController.kt @@ -1,6 +1,8 @@ package com.wallet.webapi.controller import com.wallet.biz.domain.dict.TokenResponse +import com.wallet.biz.service.ConfigService +import com.wallet.biz.dict.SysConfigKey import com.wallet.biz.xservice.AdminXService import com.wallet.entity.domain.* import io.swagger.v3.oas.annotations.Operation @@ -10,6 +12,7 @@ import jakarta.servlet.http.HttpServletRequest import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -17,8 +20,74 @@ import org.springframework.web.bind.annotation.RestController @Tag(name = "Admin API", description = "管理后台接口") @RequestMapping("admin") class AdminController { + private val auditLogs = mutableListOf>() - @GetMapping("get_user") + @GetMapping("config/rpc") + @Operation(summary = "RPC配置") + fun getRpcConfig(): TokenResponse> { + val keys = listOf(SysConfigKey.ETH_RPC_URL, SysConfigKey.OMNI_RPC_URL, SysConfigKey.BCH_RPC_URL, SysConfigKey.TRX_API_URL) + return TokenResponse(keys.associate { it.name to maskSensitive(it.name, findConfig(it.name)) }) + } + + @PutMapping("config/rpc") + @Operation(summary = "更新RPC配置") + fun putRpcConfig(key: String, value: String): TokenResponse { + saveConfigByKey(key, value) + audit("UPDATE_RPC_CONFIG", key, value) + return TokenResponse() + } + + @GetMapping("config/scheduler") + @Operation(summary = "调度配置") + fun getSchedulerConfig(): TokenResponse> { + val keys = SysConfigKey.values().filter { it.name.startsWith("SCHEDULER_") } + return TokenResponse(keys.associate { it.name to findConfig(it.name) }) + } + + @PutMapping("config/scheduler") + @Operation(summary = "更新调度配置") + fun putSchedulerConfig(key: String, value: String): TokenResponse { + saveConfigByKey(key, value) + audit("UPDATE_SCHEDULER_CONFIG", key, value) + return TokenResponse() + } + + + + @GetMapping("config/chains") + @Operation(summary = "链配置") + fun getChainConfig(): TokenResponse> { + val keys = listOf(SysConfigKey.ETH_SCAN_BACK, SysConfigKey.BTC_SCAN_BACK, SysConfigKey.TRX_SCAN_BACK) + return TokenResponse(keys.associate { it.name to findConfig(it.name) }) + } + + @PutMapping("config/chains") + @Operation(summary = "更新链配置") + fun putChainConfig(key: String, value: String): TokenResponse { + saveConfigByKey(key, value) + audit("UPDATE_CHAIN_CONFIG", key, value) + return TokenResponse() + } + + @GetMapping("config/security") + @Operation(summary = "安全配置") + fun getSecurityConfig(): TokenResponse> { + val keys = listOf("SECURITY_ALLOW_EXPORT_PRIVATE_KEY", "SECURITY_REQUIRE_2FA_ON_EXPORT", "SECURITY_MASK_SENSITIVE_FIELDS") + return TokenResponse(keys.associateWith { maskSensitive(it, findConfig(it)) }) + } + + @PutMapping("config/security") + @Operation(summary = "更新安全配置") + fun putSecurityConfig(key: String, value: String): TokenResponse { + saveConfigByKey(key, value) + audit("UPDATE_SECURITY_CONFIG", key, value) + return TokenResponse() + } + + @GetMapping("config/audit-logs") + @Operation(summary = "审计日志") + fun getAuditLogs(): TokenResponse>> = TokenResponse(auditLogs.takeLast(200).reversed()) +@GetMapping("get_user") @Operation(summary = "获取用户") fun getUser(): TokenResponse { val user = request.getAttribute("user") as User @@ -177,4 +246,26 @@ class AdminController { @Resource lateinit var request: HttpServletRequest + + @Autowired + lateinit var configService: ConfigService + + private fun findConfig(key: String): String = configService.findAll().firstOrNull { it.configKey == key }?.configValue ?: "" + + private fun saveConfigByKey(key: String, value: String) { + val item = configService.findAll().firstOrNull { it.configKey == key } ?: Config().also { it.configKey = key } + if (value.isBlank()) return + item.configValue = value + configService.save(item) + } + + private fun audit(action: String, key: String, value: String) { + auditLogs.add(mapOf("action" to action, "key" to key, "value" to maskSensitive(key, value), "operator" to ((request.getHeader("X-Admin-User") ?: "system")), "time" to java.time.Instant.now().toString())) + } + + private fun maskSensitive(key: String, value: String): String { + if (!key.contains("PASSWORD") && !key.contains("KEY")) return value + if (value.length <= 8) return "****" + return value.take(4) + "****" + value.takeLast(4) + } } diff --git a/wallet-webapi/src/main/kotlin/com/wallet/webapi/scheduler/DynamicTaskScheduler.kt b/wallet-webapi/src/main/kotlin/com/wallet/webapi/scheduler/DynamicTaskScheduler.kt new file mode 100644 index 0000000..c9c07eb --- /dev/null +++ b/wallet-webapi/src/main/kotlin/com/wallet/webapi/scheduler/DynamicTaskScheduler.kt @@ -0,0 +1,48 @@ +package com.wallet.webapi.scheduler + +import com.wallet.biz.cache.CacheService +import com.wallet.biz.dict.SysConfigKey +import com.wallet.biz.handler.service.CollectService +import com.wallet.biz.handler.service.SendFeeService +import com.wallet.biz.handler.service.SynService +import jakarta.annotation.PostConstruct +import org.slf4j.LoggerFactory +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler +import org.springframework.stereotype.Component +import java.time.Duration + +@Component +class DynamicTaskScheduler( + private val cacheService: CacheService, + private val synService: SynService, + private val collectService: CollectService, + private val sendFeeService: SendFeeService +) { + private val log = LoggerFactory.getLogger(javaClass) + private val scheduler = ThreadPoolTaskScheduler() + + @PostConstruct + fun start() { + scheduler.poolSize = 4 + scheduler.setThreadNamePrefix("wallet-scheduler-") + scheduler.initialize() + register("deposit", SysConfigKey.SCHEDULER_DEPOSIT_SCAN_MS, SysConfigKey.SCHEDULER_DEPOSIT_SCAN_ENABLED) { synService.synDeposit() } + register("sync", SysConfigKey.SCHEDULER_CHAIN_SYNC_MS, SysConfigKey.SCHEDULER_CHAIN_SYNC_ENABLED) { + synService.synETH(); synService.synOMNI(); synService.synTRX(); synService.synImportAddress() + } + register("sweep", SysConfigKey.SCHEDULER_SWEEP_MS, SysConfigKey.SCHEDULER_SWEEP_ENABLED) { + collectService.collectETH(); collectService.collectOMNI(); collectService.collectTRX(); collectService.collectTRC(); collectService.collectERC() + } + register("fee", SysConfigKey.SCHEDULER_FEE_SUPPLY_MS, SysConfigKey.SCHEDULER_FEE_SUPPLY_ENABLED) { + sendFeeService.sendFeeETH(); sendFeeService.sendFeeTRX() + } + } + + private fun register(name: String, intervalKey: SysConfigKey, enabledKey: SysConfigKey, task: () -> Unit) { + scheduler.scheduleWithFixedDelay({ + val enabled = cacheService.getSysConfig(enabledKey).lowercase() == "true" + if (!enabled) return@scheduleWithFixedDelay + runCatching(task).onFailure { log.error("dynamic task {} failed: {}", name, it.message) } + }, Duration.ofMillis(cacheService.getSysConfig(intervalKey).toLongOrNull() ?: intervalKey.defaultValue.toLong())) + } +}