From 082dfec580af04f763873036dd694afa67ca99b1 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Fri, 19 Nov 2021 14:24:10 +0300 Subject: [PATCH 01/12] init Blockstore in Blockchain + beautify SpvContext --- .../main/java/org/veriblock/spv/SpvContext.kt | 41 ++++++++----------- .../org/veriblock/spv/service/Blockchain.kt | 7 +++- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt b/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt index cfe332a60..bcef0967c 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt @@ -52,35 +52,30 @@ private val logger = createLogger {} class SpvContext( config: SpvConfig ) { - val networkParameters: NetworkParameters = config.networkParameters - - private val p2pConfiguration: P2pConfiguration val directory: File - val filePrefix: String - val transactionPool: TransactionPool - val blockStore: BlockStore - val blockchain: Blockchain - val spvService: SpvService val peerTable: PeerTable - val addressManager: AddressManager - val transactionService: TransactionService + val wallet: AddressManager + val blockchain: Blockchain + val transactionPool: TransactionPool val pendingTransactionContainer: PendingTransactionContainer val pendingTransactionDownloadedListener: PendingTransactionDownloadedListener + val transactionService: TransactionService + val spvService: SpvService + private val p2pConfiguration: P2pConfiguration private val addressState: ConcurrentHashMap = ConcurrentHashMap() - val trustPeerHashes = config.trustPeerHashes val startTime: Instant = Instant.now() init { - if (trustPeerHashes) { + if (config.trustPeerHashes) { logger.info { "Fast sync mode is enabled." } } if (!Context.isCreated()) { - Context.create(networkParameters) - } else if (Context.get().networkParameters.name != networkParameters.name) { - throw IllegalStateException("Attempting to create $networkParameters SPV context while on ${Context.get().networkParameters}") + Context.create(config.networkParameters) + } else if (Context.get().networkParameters.name != config.networkParameters.name) { + throw IllegalStateException("Attempting to create $config.networkParameters SPV context while on ${Context.get().networkParameters}") } val baseDir = File(config.dataDir) @@ -88,14 +83,12 @@ class SpvContext( try { directory = baseDir - filePrefix = networkParameters.name - blockStore = BlockStore(networkParameters, directory) transactionPool = TransactionPool() - blockchain = Blockchain(blockStore) + blockchain = Blockchain(config.networkParameters, directory) pendingTransactionContainer = PendingTransactionContainer(this) - addressManager = AddressManager() - val walletFile = File(directory, filePrefix + FILE_EXTENSION) - addressManager.load(walletFile) + wallet = AddressManager() + val walletFile = File(directory, config.networkParameters.name + FILE_EXTENSION) + wallet.load(walletFile) pendingTransactionDownloadedListener = PendingTransactionDownloadedListener(this) val externalPeerEndpoints = config.connectDirectlyTo.map { @@ -105,7 +98,7 @@ class SpvContext( val uri = URI.create(input) // if port is not provided, use standard port from networkParameters - val port = if (uri.port == -1) networkParameters.p2pPort else uri.port + val port = if (uri.port == -1) config.networkParameters.p2pPort else uri.port NetworkAddress(uri.host, port) } catch (e: Exception) { throw ConfigurationException("Wrong format for peer address ${it}, it should be host:port") @@ -134,9 +127,9 @@ class SpvContext( val bootstrapper = PeerTableBootstrapper(p2pConfiguration, DnsResolver()) peerTable = PeerTable(p2pConfiguration, warden, bootstrapper) - transactionService = TransactionService(addressManager, networkParameters) + transactionService = TransactionService(wallet, config.networkParameters) spvService = SpvService( - this, peerTable, transactionService, addressManager, + this, peerTable, transactionService, wallet, pendingTransactionContainer, blockchain ) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/service/Blockchain.kt b/nodecore-spv/src/main/java/org/veriblock/spv/service/Blockchain.kt index 8009be765..b05d1540b 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/service/Blockchain.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/service/Blockchain.kt @@ -30,17 +30,20 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock import kotlinx.coroutines.CancellationException +import org.veriblock.core.params.NetworkParameters +import java.io.File private val logger = createLogger {} class Blockchain( - val blockStore: BlockStore + val networkParameters: NetworkParameters, + val dataDir: File ) { + val blockStore = BlockStore(networkParameters, dataDir) // in-memory block index val blockIndex = ConcurrentHashMap() lateinit var activeChain: Chain val size get() = blockIndex.size - val networkParameters get() = blockStore.networkParameters val lock = ReentrantLock() private val networkBlockQueue = Channel(UNLIMITED) From 16d517c00463db8de1df4c7f328996a6d0788717 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Fri, 19 Nov 2021 14:56:38 +0300 Subject: [PATCH 02/12] fix build --- .../org/veriblock/spv/standalone/VeriBlockSPV.kt | 6 +++--- .../src/main/java/org/veriblock/spv/SpvContext.kt | 2 +- .../org/veriblock/spv/net/PeerEventListener.kt | 8 ++++---- .../java/org/veriblock/spv/service/SpvService.kt | 6 +++--- .../spv/service/task/AddressStateUpdateTask.kt | 6 +++--- .../org/veriblock/spv/blockchain/BlockchainTest.kt | 14 ++++++-------- .../spv/lite/core/StandardTransactionTest.kt | 8 ++++---- .../spv/net/impl/PeerEventListenerTest.kt | 2 +- .../miners/pop/service/AltchainPopMinerService.kt | 12 ++++++------ 9 files changed, 31 insertions(+), 33 deletions(-) diff --git a/nodecore-spv/nodecore-spv-standalone/src/main/kotlin/org/veriblock/spv/standalone/VeriBlockSPV.kt b/nodecore-spv/nodecore-spv-standalone/src/main/kotlin/org/veriblock/spv/standalone/VeriBlockSPV.kt index daf2f05cb..1edb9b3e5 100644 --- a/nodecore-spv/nodecore-spv-standalone/src/main/kotlin/org/veriblock/spv/standalone/VeriBlockSPV.kt +++ b/nodecore-spv/nodecore-spv-standalone/src/main/kotlin/org/veriblock/spv/standalone/VeriBlockSPV.kt @@ -144,10 +144,10 @@ private fun run(): Int { logger.info { "Type 'importwallet ' to import an existing wallet" } logger.info { "Type 'help' to display a list of available commands" } - if (!spvContext.addressManager.isLocked) { + if (!spvContext.wallet.isLocked) { try { - spvContext.spvService.importWallet(spvContext.addressManager.walletPath()) - logger.info { "Successfully imported the wallet file: ${spvContext.addressManager.walletPath()}" } + spvContext.spvService.importWallet(spvContext.wallet.walletPath()) + logger.info { "Successfully imported the wallet file: ${spvContext.wallet.walletPath()}" } logger.info { "Type 'getbalance' to see the balances of all of your addresses" } } catch (exception: Exception) { logger.info { "Failed to import the wallet file: ${exception.message}" } diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt b/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt index bcef0967c..d5c4dd53f 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt @@ -50,7 +50,7 @@ private val logger = createLogger {} * Initialize and hold beans/classes. */ class SpvContext( - config: SpvConfig + val config: SpvConfig ) { val directory: File val peerTable: PeerTable diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt index 57b2c299f..d0b104d02 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt @@ -75,7 +75,7 @@ class PeerEventListener( private val blockchain: Blockchain, private val pendingTransactionContainer: PendingTransactionContainer ) { - private val networkParameters: NetworkParameters = spvContext.networkParameters + private val networkParameters: NetworkParameters = spvContext.config.networkParameters private val hashDispatcher = Threading.HASH_EXECUTOR.asCoroutineDispatcher() @@ -250,7 +250,7 @@ class PeerEventListener( "Received advertisement of ${advertiseBlocks.headersList.size} blocks," + " height: ${lastBlock.height}" } - val trustHashes = spvContext.trustPeerHashes && advertiseBlocks.headersList.size > 10 + val trustHashes = spvContext.config.trustPeerHashes && advertiseBlocks.headersList.size > 10 val veriBlockBlocks: List = coroutineScope { advertiseBlocks.headersList.map { async(hashDispatcher) { @@ -423,9 +423,9 @@ class PeerEventListener( } private fun createBloomFilter(): BloomFilter { - val addresses = spvContext.addressManager.all + val addresses = spvContext.wallet.all val filter = BloomFilter( - spvContext.addressManager.numAddresses + 10, BLOOM_FILTER_FALSE_POSITIVE_RATE, + spvContext.wallet.numAddresses + 10, BLOOM_FILTER_FALSE_POSITIVE_RATE, BLOOM_FILTER_TWEAK ) for (address in addresses) { diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/service/SpvService.kt b/nodecore-spv/src/main/java/org/veriblock/spv/service/SpvService.kt index a64d327e7..4625aedd2 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/service/SpvService.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/service/SpvService.kt @@ -82,7 +82,7 @@ class SpvService( connectedPeerCount = peerTable.getConnectedPeers().size, networkHeight = SpvState.getNetworkHeight(), localBlockchainHeight = blockchain.activeChain.tip.height, - networkVersion = spvContext.networkParameters.name, + networkVersion = spvContext.config.networkParameters.name, dataDirectory = spvContext.directory.path, programVersion = SpvConstants.PROGRAM_VERSION ?: "UNKNOWN", nodecoreStartTime = spvContext.startTime.epochSecond, @@ -276,7 +276,7 @@ class SpvService( throw WalletException("Unable to load/import wallet file!") } - spvContext.addressManager.all.forEach { + spvContext.wallet.all.forEach { spvContext.getAddressState(Address(it.hash)) } } catch (e: Exception) { @@ -357,7 +357,7 @@ class SpvService( ) } - fun getLastBitcoinBlock(): Sha256Hash = spvContext.networkParameters.bitcoinOriginBlock.hash //Mock todo SPV-111 + fun getLastBitcoinBlock(): Sha256Hash = spvContext.config.networkParameters.bitcoinOriginBlock.hash //Mock todo SPV-111 fun getTransactions(ids: List) = ids.mapNotNull { pendingTransactionContainer.getTransactionInfo(it) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/service/task/AddressStateUpdateTask.kt b/nodecore-spv/src/main/java/org/veriblock/spv/service/task/AddressStateUpdateTask.kt index 7220e7584..68ea81bb1 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/service/task/AddressStateUpdateTask.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/service/task/AddressStateUpdateTask.kt @@ -32,7 +32,7 @@ fun SpvContext.startAddressStateUpdateTask() { suspend fun SpvContext.updateAddressState() { try { - val addresses = addressManager.all.map { it.hash } + val addresses = wallet.all.map { it.hash } if (addresses.isEmpty()) { logger.error { "No addresses in addressManager..." } return @@ -60,12 +60,12 @@ suspend fun SpvContext.updateAddressState() { } } // handle responses with known addresses - .filter { addressManager.contains(it.address.toBase58()) } + .filter { wallet.contains(it.address.toBase58()) } // handle only cryptographically valid responses .filter { LedgerProofReplyValidator.validate(it) } // mapper returns null if it can't deserialize block header, so // handle responses with valid blocks - .mapNotNull { LedgerProofReplyMapper.map(it, trustPeerHashes) } + .mapNotNull { LedgerProofReplyMapper.map(it, config.trustPeerHashes) } // block-of-proof may be new or known. if known or new and it connects, this will return true. .filter { blockchain.acceptBlock(it.block) } // handle responses with block-of-proofs that are on active chain diff --git a/nodecore-spv/src/test/java/org/veriblock/spv/blockchain/BlockchainTest.kt b/nodecore-spv/src/test/java/org/veriblock/spv/blockchain/BlockchainTest.kt index 6ba270cf9..fc06cb80a 100644 --- a/nodecore-spv/src/test/java/org/veriblock/spv/blockchain/BlockchainTest.kt +++ b/nodecore-spv/src/test/java/org/veriblock/spv/blockchain/BlockchainTest.kt @@ -26,8 +26,7 @@ class BlockchainTest { tmpdir.deleteRecursively() } - val blockStore = BlockStore(regtest, tmpdir) - val blockchain = Blockchain(blockStore) + val blockchain = Blockchain(regtest, tmpdir) fun generateBlock(prev: VeriBlockBlock) = vbkBlockGenerator(prev, regtest) { // return previous block header @@ -79,7 +78,7 @@ class BlockchainTest { // starting from height 1000, generate 2001 blocks. Chain B should win, as it has // higher chainwork - val lastBlock2 = generateBlock(blockchain.activeChain[1000]!!.readBlock(blockStore)!!.header) + val lastBlock2 = generateBlock(blockchain.activeChain[1000]!!.readBlock(blockchain.blockStore)!!.header) .take(2001) .onEach { blockchain.acceptBlock(it) shouldBe true } .last() @@ -87,7 +86,7 @@ class BlockchainTest { val tipB = blockchain.getBlock(lastBlock2.hash)!! blockchain.size shouldBe /*genesis=*/1 + 2100 + 2001 - blockchain.activeChain.tip.readBlock(blockStore)!!.header shouldBe tipB.header + blockchain.activeChain.tip.readBlock(blockchain.blockStore)!!.header shouldBe tipB.header } @Test @@ -112,7 +111,7 @@ class BlockchainTest { headerGenerated.nonce ) - val positionAfterLastValidBlock = blockStore.appendBlock( + val positionAfterLastValidBlock = blockchain.blockStore.appendBlock( StoredVeriBlockBlock( header = headerCorrupted, work = BigInteger.valueOf(999999999L), @@ -120,11 +119,10 @@ class BlockchainTest { ) ) - val store = BlockStore(regtest, tmpdir) - val bchain2 = Blockchain(store) + val bchain2 = Blockchain(regtest, tmpdir) bchain2.getBlock(headerCorrupted.hash) shouldBe null - blockStore.size shouldBe positionAfterLastValidBlock + blockchain.blockStore.size shouldBe positionAfterLastValidBlock bchain2.size shouldBe 101 } } diff --git a/nodecore-spv/src/test/java/org/veriblock/spv/lite/core/StandardTransactionTest.kt b/nodecore-spv/src/test/java/org/veriblock/spv/lite/core/StandardTransactionTest.kt index d409cb618..d57169098 100644 --- a/nodecore-spv/src/test/java/org/veriblock/spv/lite/core/StandardTransactionTest.kt +++ b/nodecore-spv/src/test/java/org/veriblock/spv/lite/core/StandardTransactionTest.kt @@ -40,12 +40,12 @@ class StandardTransactionTest { ) ) val tx = StandardTransaction( - "V8dy5tWcP7y36kxiJwxKPKUrWAJbjs", 3500000000L, outputs, 5904L, spvContext.networkParameters + "V8dy5tWcP7y36kxiJwxKPKUrWAJbjs", 3500000000L, outputs, 5904L, spvContext.config.networkParameters ) val pub = byteArrayOf(1, 2, 3) val sign = byteArrayOf(3, 2, 1) tx.addSignature(sign, pub) - val signedTransaction = tx.getSignedMessageBuilder(spvContext.networkParameters).build() + val signedTransaction = tx.getSignedMessageBuilder(spvContext.config.networkParameters).build() signedTransaction.signatureIndex shouldBe 5904L signedTransaction.transaction.sourceAmount shouldBe 3500000000L ByteStringUtility.byteStringToBase58(signedTransaction.transaction.sourceAddress) shouldBe "V8dy5tWcP7y36kxiJwxKPKUrWAJbjs" @@ -64,9 +64,9 @@ class StandardTransactionTest { ) ) val tx = StandardTransaction( - "V8dy5tWcP7y36kxiJwxKPKUrWAJbjs", 3500000000L, outputs, 5904L, spvContext.networkParameters + "V8dy5tWcP7y36kxiJwxKPKUrWAJbjs", 3500000000L, outputs, 5904L, spvContext.config.networkParameters ) - val serialized = tx.toByteArray(spvContext.networkParameters) + val serialized = tx.toByteArray(spvContext.config.networkParameters) Utility.bytesToHex(serialized) shouldBe "01011667A654EE3E0C918D8652B63829D7F3BEF98524BF899604D09DC30001011667901A1E11C650509EFC46E09E81678054D8562AF02B04D09DC2FF0217100100" } } diff --git a/nodecore-spv/src/test/java/org/veriblock/spv/net/impl/PeerEventListenerTest.kt b/nodecore-spv/src/test/java/org/veriblock/spv/net/impl/PeerEventListenerTest.kt index 8a593808e..ac75815ac 100644 --- a/nodecore-spv/src/test/java/org/veriblock/spv/net/impl/PeerEventListenerTest.kt +++ b/nodecore-spv/src/test/java/org/veriblock/spv/net/impl/PeerEventListenerTest.kt @@ -73,7 +73,7 @@ class PeerEventListenerTest { Output("V7GghFKRA6BKqtHD7LTdT2ao93DRNA".asStandardAddress(), 3499999999L.asCoin()) ) val standardTransaction = StandardTransaction( - "V8dy5tWcP7y36kxiJwxKPKUrWAJbjs", 3500000000L, outputs, 5904L, spvContext.networkParameters + "V8dy5tWcP7y36kxiJwxKPKUrWAJbjs", 3500000000L, outputs, 5904L, spvContext.config.networkParameters ) val pub = byteArrayOf(1, 2, 3) val sign = byteArrayOf(3, 2, 1) diff --git a/pop-miners/altchain-pop-miner/src/main/kotlin/org/veriblock/miners/pop/service/AltchainPopMinerService.kt b/pop-miners/altchain-pop-miner/src/main/kotlin/org/veriblock/miners/pop/service/AltchainPopMinerService.kt index ec65f855e..885c828bd 100644 --- a/pop-miners/altchain-pop-miner/src/main/kotlin/org/veriblock/miners/pop/service/AltchainPopMinerService.kt +++ b/pop-miners/altchain-pop-miner/src/main/kotlin/org/veriblock/miners/pop/service/AltchainPopMinerService.kt @@ -91,7 +91,7 @@ class AltchainPopMinerService( context, gateway, transactionMonitor, - spvContext.addressManager + spvContext.wallet ) transactionMonitor.start() @@ -148,7 +148,7 @@ class AltchainPopMinerService( try { logger.info { "VeriBlock Network: ${context.networkParameters.name}" } - logger.info { "Send funds to the ${context.vbkTokenName} address ${spvContext.addressManager.defaultAddress.hash}" } + logger.info { "Send funds to the ${context.vbkTokenName} address ${spvContext.wallet.defaultAddress.hash}" } logger.info { "Connecting to peers..." } // Restore & submit operations (including re-attach listeners) before the network starts @@ -189,9 +189,9 @@ class AltchainPopMinerService( } } - fun getAddress(): String = spvContext.addressManager.defaultAddress.hash + fun getAddress(): String = spvContext.wallet.defaultAddress.hash - fun setDefaultAddress(address: String) = spvContext.addressManager.setDefaultAddress(address) + fun setDefaultAddress(address: String) = spvContext.wallet.setDefaultAddress(address) fun getBalance(): Balance = network.latestBalance @@ -210,7 +210,7 @@ class AltchainPopMinerService( VBK PoP wallet does not contain sufficient funds, Current balance: ${network.latestBalance.confirmedBalance.atomicUnits.formatCoinAmount()} ${context.vbkTokenName}, Minimum required: ${config.maxFee.formatCoinAmount()}, need ${(config.maxFee - network.latestBalance.confirmedBalance.atomicUnits).formatCoinAmount()} more - Send ${context.vbkTokenName} coins to: ${spvContext.addressManager.defaultAddress.hash} + Send ${context.vbkTokenName} coins to: ${spvContext.wallet.defaultAddress.hash} """.trimIndent())) } // Verify the synchronized status @@ -399,7 +399,7 @@ class AltchainPopMinerService( } private fun createTransactionMonitor(): TransactionMonitor { - val address = Address(spvContext.addressManager.defaultAddress.hash) + val address = Address(spvContext.wallet.defaultAddress.hash) return TransactionMonitor(context, gateway, address) } From aad7896b540e5928f263cf9693e82c668a1c05e7 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Fri, 26 Nov 2021 10:34:11 +0200 Subject: [PATCH 03/12] tx flow --- .../veriblock/spv/net/PeerEventListener.kt | 75 ++++++++++++++----- .../service/PendingTransactionContainer.kt | 48 +++++++++++- 2 files changed, 100 insertions(+), 23 deletions(-) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt index d0b104d02..bebdade7a 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt @@ -7,7 +7,6 @@ package org.veriblock.spv.net import com.google.protobuf.ByteString -import java.util.concurrent.locks.ReentrantLock import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -35,6 +34,8 @@ import nodecore.p2p.P2pEventBus import nodecore.p2p.Peer import nodecore.p2p.PeerCapabilities import nodecore.p2p.PeerTable +import nodecore.p2p.TrafficManager +import nodecore.p2p.TransactionRequest import nodecore.p2p.buildMessage import nodecore.p2p.event.P2pEvent import nodecore.p2p.event.PeerMisbehaviorEvent @@ -45,7 +46,9 @@ import org.veriblock.core.crypto.BloomFilter import org.veriblock.core.crypto.asVbkHash import org.veriblock.core.crypto.asVbkTxId import org.veriblock.core.params.NetworkParameters +import org.veriblock.core.params.allDefaultNetworkParameters import org.veriblock.core.utilities.BlockUtility +import org.veriblock.core.utilities.Utility import org.veriblock.core.utilities.createLogger import org.veriblock.sdk.models.VeriBlockBlock import org.veriblock.sdk.services.SerializeDeserializeService @@ -57,10 +60,10 @@ import org.veriblock.spv.serialization.MessageSerializer import org.veriblock.spv.service.Blockchain import org.veriblock.spv.service.NetworkBlock import org.veriblock.spv.service.PendingTransactionContainer -import org.veriblock.spv.util.SpvEventBus import org.veriblock.spv.util.Threading +import java.util.* +import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock -import org.veriblock.core.params.allDefaultNetworkParameters private val logger = createLogger {} @@ -76,6 +79,7 @@ class PeerEventListener( private val pendingTransactionContainer: PendingTransactionContainer ) { private val networkParameters: NetworkParameters = spvContext.config.networkParameters + private val trafficManager: TrafficManager = TrafficManager() private val hashDispatcher = Threading.HASH_EXECUTOR.asCoroutineDispatcher() @@ -98,11 +102,43 @@ class PeerEventListener( private fun onAddTransaction(event: P2pEvent) { event.acknowledge() - // TODO: Different Transaction types - val standardTransaction = MessageSerializer.deserializeNormalTransaction(event.content) - // TODO: Some peers are still sending transactions not relevant to our bloom filter; figure out why - if (bloomFilter.isRelevant(standardTransaction)) { - SpvEventBus.pendingTransactionDownloadedEvent.trigger(standardTransaction) + try { + val txId = extractTxIdFromMessage(event.content) + if (txId.isPresent) { + if (trafficManager.transactionReceived(txId.get(), event.producer.addressKey)) { + val addTransactionResult = pendingTransactionContainer.addNetworkTransaction(event.content) + if (addTransactionResult == PendingTransactionContainer.AddTransactionResult.INVALID) { + P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( + peer = event.producer, + reason = PeerMisbehaviorEvent.Reason.INVALID_TRANSACTION, + message = "Peer sent a transaction that didn't pass the validations" + )) + } + } else { + P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( + peer = event.producer, + reason = PeerMisbehaviorEvent.Reason.UNREQUESTED_TRANSACTION, + message = "Peer sent a transaction this NodeCore instance didn't request" + )) + } + } else { + P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( + event.producer, + PeerMisbehaviorEvent.Reason.INVALID_TRANSACTION, + "Peer sent a transaction which txId couldn't be computed" + )) + } + } catch (e: Exception) { + logger.warn("Could not queue network transaction", e) + } + + } + + private fun extractTxIdFromMessage(message: RpcTransactionUnion): Optional { + return when (message.transactionCase) { + RpcTransactionUnion.TransactionCase.SIGNED -> Optional.of(ByteStringUtility.byteStringToHex(message.signed.transaction.txId)) + RpcTransactionUnion.TransactionCase.SIGNED_MULTISIG -> Optional.of(ByteStringUtility.byteStringToHex(message.signedMultisig.transaction.txId)) + else -> Optional.empty() } } @@ -317,19 +353,18 @@ class PeerEventListener( // return //} - val txRequestBuilder = RpcTransactionRequest.newBuilder() - val transactions = event.content.transactionsList - for (tx in transactions) { - val txId = tx.txId.toByteArray().asVbkTxId() - val broadcastCount = spvContext.transactionPool.record(txId, event.producer.addressKey) - if (broadcastCount == 1) { - txRequestBuilder.addTransactions(tx) - } - } - if (txRequestBuilder.transactionsCount > 0) { - event.producer.sendMessage { - setTxRequest(txRequestBuilder) + try { + val requestQueue = ArrayList() + for (tx in event.content.transactionsList) { + val txId = ByteStringUtility.byteStringToHex(tx.txId) + if (event.producer.state.addSeenTransaction(txId, Utility.getCurrentTimeSeconds())) { + requestQueue.add(TransactionRequest(txId, tx, event.producer)) + } } + + trafficManager.requestTransactions(requestQueue) + } catch (e: Exception) { + logger.warn("Unable to handle advertised transactions", e) } } diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt b/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt index d98e60f63..87c3042ae 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt @@ -1,20 +1,23 @@ package org.veriblock.spv.service -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import nodecore.api.grpc.RpcTransactionUnion +import nodecore.api.grpc.utilities.ByteStringUtility import org.veriblock.core.crypto.VbkTxId import org.veriblock.core.utilities.createLogger import org.veriblock.sdk.models.Address import org.veriblock.sdk.models.VeriBlockBlock import org.veriblock.spv.SpvContext import org.veriblock.spv.model.Transaction +import org.veriblock.spv.serialization.MessageSerializer import org.veriblock.spv.util.SpvEventBus import org.veriblock.spv.util.Threading +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock private val logger = createLogger {} @@ -113,6 +116,39 @@ class PendingTransactionContainer( pendingTransactions[transaction.txId] = transaction } + fun addNetworkTransaction(message: RpcTransactionUnion): AddTransactionResult? { + var txId: String? = null + txId = when (message.transactionCase) { + RpcTransactionUnion.TransactionCase.UNSIGNED -> { + logger.warn("Rejected network transaction because it was unsigned") + return AddTransactionResult.INVALID + } + RpcTransactionUnion.TransactionCase.SIGNED -> ByteStringUtility.byteStringToHex(message.signed.transaction.txId) + RpcTransactionUnion.TransactionCase.SIGNED_MULTISIG -> ByteStringUtility.byteStringToHex(message.signedMultisig.transaction.txId) + RpcTransactionUnion.TransactionCase.TRANSACTION_NOT_SET -> { + logger.warn("Rejected a network transaction because the type was not set") + return AddTransactionResult.INVALID + } + else -> { + logger.warn("Rejected transaction with unknown type") + return AddTransactionResult.INVALID + } + } + return try { + val transaction: Transaction = MessageSerializer.deserializeNormalTransaction(message) + // TODO: DenylistTransactionCache +// if (DenylistTransactionCache.get().contains(transaction.getTxId())) { +// return PendingTransactionContainer.AddTransactionResult.INVALID +// } + addTransaction(transaction) + logger.debug("Add transaction to mempool {}", txId) + AddTransactionResult.SUCCESS + } catch (ex: Exception) { + logger.warn("Could not construct transaction for reason: {}", ex.message) + AddTransactionResult.INVALID + } + } + fun getTransaction(txId: VbkTxId): Transaction? { return pendingTransactions[txId] } @@ -173,4 +209,10 @@ class PendingTransactionContainer( addTransaction(transaction) } } + + enum class AddTransactionResult { + SUCCESS, + INVALID, + DUPLICATE + } } From 88d4588f6e77ec15f708b4fc20c81fcb53ad1d65 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Fri, 26 Nov 2021 14:13:51 +0200 Subject: [PATCH 04/12] early return --- .../veriblock/spv/net/PeerEventListener.kt | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt index bebdade7a..2770818ef 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt @@ -104,29 +104,31 @@ class PeerEventListener( try { val txId = extractTxIdFromMessage(event.content) - if (txId.isPresent) { - if (trafficManager.transactionReceived(txId.get(), event.producer.addressKey)) { - val addTransactionResult = pendingTransactionContainer.addNetworkTransaction(event.content) - if (addTransactionResult == PendingTransactionContainer.AddTransactionResult.INVALID) { - P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( - peer = event.producer, - reason = PeerMisbehaviorEvent.Reason.INVALID_TRANSACTION, - message = "Peer sent a transaction that didn't pass the validations" - )) - } - } else { - P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( - peer = event.producer, - reason = PeerMisbehaviorEvent.Reason.UNREQUESTED_TRANSACTION, - message = "Peer sent a transaction this NodeCore instance didn't request" - )) - } - } else { + if (!txId.isPresent) { P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( event.producer, PeerMisbehaviorEvent.Reason.INVALID_TRANSACTION, "Peer sent a transaction which txId couldn't be computed" )) + return + } + + if (!trafficManager.transactionReceived(txId.get(), event.producer.addressKey)) { + P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( + peer = event.producer, + reason = PeerMisbehaviorEvent.Reason.UNREQUESTED_TRANSACTION, + message = "Peer sent a transaction this NodeCore instance didn't request" + )) + return + } + + val addTransactionResult = pendingTransactionContainer.addNetworkTransaction(event.content) + if (addTransactionResult == PendingTransactionContainer.AddTransactionResult.INVALID) { + P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( + peer = event.producer, + reason = PeerMisbehaviorEvent.Reason.INVALID_TRANSACTION, + message = "Peer sent a transaction that didn't pass the validations" + )) } } catch (e: Exception) { logger.warn("Could not queue network transaction", e) From 75fed0a18713c0430d97d8885eb3ce28166fc8b2 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Fri, 26 Nov 2021 15:13:07 +0200 Subject: [PATCH 05/12] add test syncAllNetworkTransactions --- build.gradle.kts | 1 + nodecore-spv/build.gradle.kts | 18 +++--- .../veriblock/spv/net/PeerEventListener.kt | 2 +- .../spv/net/impl/PeerEventListenerTest.kt | 62 +++++++++++++++++++ 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c6dd6a754..c7e5ccfd5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,6 +41,7 @@ allprojects { mavenLocal() jcenter() maven("https://jitpack.io") + mavenCentral() } tasks.withType { diff --git a/nodecore-spv/build.gradle.kts b/nodecore-spv/build.gradle.kts index ae8b22e7a..d1b313141 100644 --- a/nodecore-spv/build.gradle.kts +++ b/nodecore-spv/build.gradle.kts @@ -28,19 +28,19 @@ dependencies { api(project(":nodecore-grpc")) api(project(":nodecore-p2p")) - implementation("io.ktor:ktor-network-jvm:$ktorVersion") + implementation("io.ktor:ktor-network-jvm:1.6.4") // Logging - implementation("io.github.microutils:kotlin-logging:1.6.26") - implementation("org.apache.logging.log4j:log4j-api:$log4jVersion") - implementation("org.apache.logging.log4j:log4j-core:$log4jVersion") - implementation("org.slf4j:slf4j-api:$slf4jVersion") + implementation("io.github.microutils:kotlin-logging:2.0.11") + implementation("org.apache.logging.log4j:log4j-api:2.14.1") + implementation("org.apache.logging.log4j:log4j-core:2.14.1") + implementation("org.slf4j:slf4j-api:1.7.32") - implementation("org.freemarker:freemarker:2.3.14") + implementation("org.freemarker:freemarker:2.3.31") - testImplementation("junit:junit:4.12") - testImplementation("io.kotest:kotest-assertions-core-jvm:4.3.0") - testImplementation("io.mockk:mockk:1.9.3") + testImplementation("junit:junit:4.13.2") + testImplementation("io.kotest:kotest-assertions-core-jvm:4.6.3") + testImplementation("io.mockk:mockk:1.12.0") } tasks.test { diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt index 2770818ef..4d1054d14 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt @@ -99,7 +99,7 @@ class PeerEventListener( P2pEventBus.peerDisconnected.register(this, ::onPeerDisconnected) } - private fun onAddTransaction(event: P2pEvent) { + fun onAddTransaction(event: P2pEvent) { event.acknowledge() try { diff --git a/nodecore-spv/src/test/java/org/veriblock/spv/net/impl/PeerEventListenerTest.kt b/nodecore-spv/src/test/java/org/veriblock/spv/net/impl/PeerEventListenerTest.kt index ac75815ac..2f50f2797 100644 --- a/nodecore-spv/src/test/java/org/veriblock/spv/net/impl/PeerEventListenerTest.kt +++ b/nodecore-spv/src/test/java/org/veriblock/spv/net/impl/PeerEventListenerTest.kt @@ -1,14 +1,22 @@ package org.veriblock.spv.net.impl +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe import io.mockk.every import io.mockk.mockk +import io.mockk.slot import io.mockk.verify +import nodecore.api.grpc.RpcAdvertiseTransaction +import nodecore.api.grpc.RpcEvent import nodecore.api.grpc.RpcTransactionAnnounce import nodecore.api.grpc.RpcTransactionRequest +import nodecore.api.grpc.RpcTransactionUnion import nodecore.api.grpc.utilities.extensions.toByteString import nodecore.p2p.Peer +import nodecore.p2p.PeerState import nodecore.p2p.PeerTable import nodecore.p2p.event.P2pEvent +import nodecore.p2p.event.toP2pEvent import org.junit.Before import org.junit.Test import org.veriblock.core.Context @@ -20,6 +28,7 @@ import org.veriblock.spv.SpvConfig import org.veriblock.spv.SpvContext import org.veriblock.spv.model.Output import org.veriblock.spv.model.StandardTransaction +import org.veriblock.spv.model.Transaction import org.veriblock.spv.model.asStandardAddress import org.veriblock.spv.net.PeerEventListener import org.veriblock.spv.service.PendingTransactionContainer @@ -87,4 +96,57 @@ class PeerEventListenerTest { verify(exactly = 1 ) { pendingTransactionContainer.getTransaction(txIds[0]) } verify(exactly = 1 ) { peer.send(any()) } } + + private fun createAdvertiseTransaction(txIds: List) = P2pEvent( + peer, "", false, + RpcAdvertiseTransaction.newBuilder() + .addAllTransactions(txIds.map { + RpcTransactionAnnounce.newBuilder().apply { + type = RpcTransactionAnnounce.Type.NORMAL + txId = it.bytes.toByteString() + }.build() + }) + .build() + ) + + private fun createAddTransaction(transaction: Transaction): P2pEvent { + val event = RpcEvent.newBuilder() + .setId("") + .setAcknowledge(false) + .setTransaction( + RpcTransactionUnion.newBuilder().setSigned(transaction.getSignedMessageBuilder(spvContext.config.networkParameters)) + ) + .build() + return event.toP2pEvent(peer, event.transaction) + } + + @Test + fun syncAllNetworkTransactions() { + val outputs = listOf( + Output("V7GghFKRA6BKqtHD7LTdT2ao93DRNA".asStandardAddress(), 3499999999L.asCoin()) + ) + val standardTransaction = StandardTransaction( + "V8dy5tWcP7y36kxiJwxKPKUrWAJbjs", 3500000000L, outputs, 5904L, spvContext.config.networkParameters + ) + val pub = byteArrayOf(1, 2, 3) + val sign = byteArrayOf(3, 2, 1) + standardTransaction.addSignature(sign, pub) + val txIds = listOf(standardTransaction.txId) + + val slotRpcEvent = slot() + every { peer.send(capture(slotRpcEvent)) } returns true + val state = PeerState() + every { peer.state } returns state + + val advertiseTransactionEvent = createAdvertiseTransaction(txIds) + peerEventListener.onAdvertiseTransactions(advertiseTransactionEvent) + verify(exactly = 1 ) { peer.send(any()) } + val response = slotRpcEvent.captured.toP2pEvent(peer, slotRpcEvent.captured.txRequest) + val txRequest = createTxRequest(txIds) + (txRequest.content == response.content) shouldBe true + + val addTransactionEvent = createAddTransaction(standardTransaction) + peerEventListener.onAddTransaction(addTransactionEvent) + verify(exactly = 1) { pendingTransactionContainer.addNetworkTransaction(any()) } + } } From c98601bb14f210c59f3d6d979313c730033b0e32 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Tue, 30 Nov 2021 13:13:06 +0300 Subject: [PATCH 06/12] request full blocks --- .../veriblock/spv/net/PeerEventListener.kt | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt index 4d1054d14..1a1b63ebd 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt @@ -16,6 +16,7 @@ import nodecore.api.grpc.RpcAcknowledgement import nodecore.api.grpc.RpcAdvertiseBlocks import nodecore.api.grpc.RpcAdvertiseTransaction import nodecore.api.grpc.RpcAnnounce +import nodecore.api.grpc.RpcBlock import nodecore.api.grpc.RpcBlockHeader import nodecore.api.grpc.RpcCreateFilter import nodecore.api.grpc.RpcEvent @@ -29,6 +30,7 @@ import nodecore.api.grpc.RpcNotFound import nodecore.api.grpc.RpcTransactionRequest import nodecore.api.grpc.RpcTransactionUnion import nodecore.api.grpc.utilities.ByteStringUtility +import nodecore.p2p.BlockRequest import nodecore.p2p.P2pConstants import nodecore.p2p.P2pEventBus import nodecore.p2p.Peer @@ -86,6 +88,7 @@ class PeerEventListener( private val bloomFilter = createBloomFilter() init { + P2pEventBus.addBlock.register(this, ::onAddBlock) P2pEventBus.addTransaction.register(this, ::onAddTransaction) P2pEventBus.announce.register(this, ::onAnnounce) P2pEventBus.heartbeat.register(this, ::onHeartbeat) @@ -98,6 +101,25 @@ class PeerEventListener( P2pEventBus.peerConnected.register(this, ::onPeerConnected) P2pEventBus.peerDisconnected.register(this, ::onPeerDisconnected) } + + fun onAddBlock(event: P2pEvent) { + event.acknowledge() + + val blockHash = ByteStringUtility.byteStringToHex(event.content.hash) + try { + if (trafficManager.blockReceived(blockHash, event.producer.addressKey)) { + // TODO: add mempool management + } else { + P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( + peer = event.producer, + reason = PeerMisbehaviorEvent.Reason.UNREQUESTED_BLOCK, + message = "Peer sent a block this SPV instance didn't request" + )) + } + } catch (e: Exception) { + logger.warn("Could not queue network block $blockHash", e) + } + } fun onAddTransaction(event: P2pEvent) { event.acknowledge() @@ -117,7 +139,7 @@ class PeerEventListener( P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( peer = event.producer, reason = PeerMisbehaviorEvent.Reason.UNREQUESTED_TRANSACTION, - message = "Peer sent a transaction this NodeCore instance didn't request" + message = "Peer sent a transaction this SPV instance didn't request" )) return } @@ -281,6 +303,15 @@ class PeerEventListener( logger.debug { "Got advertise blocks event from ${event.producer.address}" } event.acknowledge() + if (event.content.headersCount > P2pConstants.PEER_MAX_ADVERTISEMENTS) { + P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( + peer = event.producer, + reason = PeerMisbehaviorEvent.Reason.ADVERTISEMENT_SIZE, + message = "The peer exceeded the maximum allowed blocks in a single advertisement (${event.content.headersCount}, the maximum is ${P2pConstants.PEER_MAX_ADVERTISEMENTS})" + )) + return + } + val advertiseBlocks = event.content logger.debug { @@ -334,6 +365,29 @@ class PeerEventListener( // TODO(warchant): if allBlocksAccepted == false here, block can not be connected or invalid // maybe ban peer? for now, do nothing + + // download full block bodies to manage mempool + try { + val blocksToRequest = ArrayList() + + val list = event.content.headersList + for (h in list) { + val hash = ByteStringUtility.byteStringToHex(h.hash) + + if (event.producer.state.addSeenBlock(hash, Utility.getCurrentTimeSeconds())) { + blocksToRequest.add(BlockRequest(hash, h, event.producer)) + } + } + + logger.debug { + "Requesting blocks ${BlockUtility.extractBlockHeightFromBlockHeader(list[0].header.toByteArray())}" + + "-${BlockUtility.extractBlockHeightFromBlockHeader(list[list.size - 1].header.toByteArray())} from ${event.producer.address}" + } + + trafficManager.requestBlocks(blocksToRequest) + } catch (e: Exception) { + logger.warn("Unable to handle advertised blocks", e) + } } fun onAdvertiseTransactions(event: P2pEvent) { From 1f3260c95298573f361fbfe0e953ebc7e0d2897c Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Tue, 30 Nov 2021 13:59:31 +0300 Subject: [PATCH 07/12] onAddBlock early return --- .../main/java/org/veriblock/spv/net/PeerEventListener.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt index 1a1b63ebd..47c9dd65e 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt @@ -107,15 +107,15 @@ class PeerEventListener( val blockHash = ByteStringUtility.byteStringToHex(event.content.hash) try { - if (trafficManager.blockReceived(blockHash, event.producer.addressKey)) { - // TODO: add mempool management - } else { + if (!trafficManager.blockReceived(blockHash, event.producer.addressKey)) { P2pEventBus.peerMisbehavior.trigger(PeerMisbehaviorEvent( peer = event.producer, reason = PeerMisbehaviorEvent.Reason.UNREQUESTED_BLOCK, message = "Peer sent a block this SPV instance didn't request" )) + return } + // TODO: add mempool management } catch (e: Exception) { logger.warn("Could not queue network block $blockHash", e) } From b1b776016fcbe2636cbd1e597501a32baa55e723 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Mon, 13 Dec 2021 12:45:16 +0300 Subject: [PATCH 08/12] update txs infos --- .../main/java/org/veriblock/spv/SpvContext.kt | 1 - .../veriblock/spv/net/PeerEventListener.kt | 3 +- .../service/PendingTransactionContainer.kt | 107 ++++++++-- .../task/PendingTransactionsUpdateTask.kt | 188 +++++++++--------- 4 files changed, 182 insertions(+), 117 deletions(-) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt b/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt index d5c4dd53f..1387780a2 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/SpvContext.kt @@ -148,7 +148,6 @@ class SpvContext( logger.info { "SPV Disconnected" } } ) - startPendingTransactionsUpdateTask() startAddressStateUpdateTask() Threading.PEER_TABLE_SCOPE.launchWithFixedDelay(40_000, 120_000) { diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt index 47c9dd65e..4ece5afe1 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt @@ -115,7 +115,8 @@ class PeerEventListener( )) return } - // TODO: add mempool management + val fullBlock = MessageSerializer.deserialize(event.content) + pendingTransactionContainer.updateTransactionsByBlock(fullBlock) } catch (e: Exception) { logger.warn("Could not queue network block $blockHash", e) } diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt b/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt index 87c3042ae..159e3926b 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt @@ -4,17 +4,24 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import nodecore.api.grpc.RpcOutput +import nodecore.api.grpc.RpcTransaction import nodecore.api.grpc.RpcTransactionUnion import nodecore.api.grpc.utilities.ByteStringUtility +import nodecore.api.grpc.utilities.extensions.toHex +import nodecore.api.grpc.utilities.extensions.toProperAddressType import org.veriblock.core.crypto.VbkTxId +import org.veriblock.core.crypto.asVbkTxId import org.veriblock.core.utilities.createLogger import org.veriblock.sdk.models.Address import org.veriblock.sdk.models.VeriBlockBlock import org.veriblock.spv.SpvContext +import org.veriblock.spv.model.FullBlock import org.veriblock.spv.model.Transaction import org.veriblock.spv.serialization.MessageSerializer import org.veriblock.spv.util.SpvEventBus import org.veriblock.spv.util.Threading +import java.lang.IllegalArgumentException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -27,10 +34,13 @@ class PendingTransactionContainer( ) { // TODO(warchant): use Address as a key, instead of String private val pendingTransactionsByAddress: MutableMap> = ConcurrentHashMap() - private val confirmedTransactionReplies: MutableMap = ConcurrentHashMap() - private val confirmedTransactions: MutableMap = ConcurrentHashMap() private val pendingTransactions: MutableMap = ConcurrentHashMap() - private val transactionsToMonitor: MutableSet = ConcurrentHashMap.newKeySet() + // FIXME: add converting between TransactionData <-> Transaction and delete this + private val pendingTransactionsInfo: MutableMap = ConcurrentHashMap() + + private val addedToBlockchainTransactions: MutableMap = ConcurrentHashMap() + // FIXME: add converting between TransactionData <-> Transaction and delete this + private val addedToBlockchainTransactionsInfo: MutableMap = ConcurrentHashMap() private val lock = ReentrantLock() @@ -46,15 +56,15 @@ class PendingTransactionContainer( .sortedBy { it.value.getSignatureIndex() } .map { it.key } .toSet() - return pendingTransactions + transactionsToMonitor + return pendingTransactions } fun getTransactionInfo(txId: VbkTxId): TransactionInfo? { - confirmedTransactionReplies[txId]?.let { + pendingTransactionsInfo[txId]?.let { return it } - if (!pendingTransactions.containsKey(txId)) { - transactionsToMonitor.add(txId) + addedToBlockchainTransactionsInfo[txId]?.let { + return it } return null } @@ -69,11 +79,10 @@ class PendingTransactionContainer( fun updateTransactionInfo(transactionInfo: TransactionInfo) = lock.withLock { val transaction = transactionInfo.transaction - if (pendingTransactions.containsKey(transaction.txId) || transactionsToMonitor.contains(transaction.txId)) { - confirmedTransactionReplies[transaction.txId] = transactionInfo + if (pendingTransactions.containsKey(transaction.txId)) { + pendingTransactionsInfo[transaction.txId] = transactionInfo val pendingTx = pendingTransactions[transaction.txId] if (pendingTx != null) { - confirmedTransactions[transaction.txId] = pendingTx if (pendingTx.getSignatureIndex() > lastConfirmedSignatureIndex) { lastConfirmedSignatureIndex = pendingTx.getSignatureIndex() } @@ -86,23 +95,26 @@ class PendingTransactionContainer( maxConfirmedSigIndex = currentSignatureIndex } } + addedToBlockchainTransactions[transaction.txId] = pendingTx!! + addedToBlockchainTransactionsInfo[transaction.txId] = pendingTransactionsInfo[transaction.txId]!! + pendingTransactions.remove(transaction.txId) - transactionsToMonitor.remove(transaction.txId) + pendingTransactionsInfo.remove(transaction.txId) pendingTransactionsByAddress[transaction.sourceAddress]?.removeIf { it.txId == transaction.txId } } } // Prune confirmed transactions - if (confirmedTransactionReplies.size > 10_000) { - val topConfirmedBlockHeight = confirmedTransactionReplies.values.maxOf { it.blockNumber } - val txToRemove = confirmedTransactionReplies.values.asSequence().filter { + if (addedToBlockchainTransactionsInfo.size > 10_000) { + val topConfirmedBlockHeight = addedToBlockchainTransactionsInfo.values.maxOf { it.blockNumber } + val txToRemove = addedToBlockchainTransactionsInfo.values.asSequence().filter { it.blockNumber < topConfirmedBlockHeight - 1_000 }.map { it.transaction.txId } for (txId in txToRemove) { - confirmedTransactionReplies.remove(txId) - confirmedTransactions.remove(txId) + addedToBlockchainTransactions.remove(txId) + addedToBlockchainTransactionsInfo.remove(txId) } } } @@ -186,7 +198,6 @@ class PendingTransactionContainer( logger.info { "All the transactions for that address will be pruned in order to prevent further transactions from being rejected." } for (tx in newTransactions) { pendingTransactions.remove(tx.txId) - transactionsToMonitor.remove(tx.txId) } newTransactions.clear() } @@ -198,21 +209,75 @@ class PendingTransactionContainer( } private fun handleRemovedBestBlock(removedBlock: VeriBlockBlock) = lock.withLock { - val reorganizedTransactions = confirmedTransactionReplies.values.filter { + val reorganizedTransactions = addedToBlockchainTransactionsInfo.values.filter { it.blockNumber == removedBlock.height }.mapNotNull { - confirmedTransactions[it.transaction.txId] + addedToBlockchainTransactions[it.transaction.txId] } for (transaction in reorganizedTransactions) { - confirmedTransactionReplies.remove(transaction.txId) - confirmedTransactions.remove(transaction.txId) + addedToBlockchainTransactions.remove(transaction.txId) addTransaction(transaction) } } + fun updateTransactionsByBlock(block: FullBlock) = lock.withLock { + /* + * We are removing only transactions that match the exact String from the block. If the block validation + * fails, NO transactions are removed from the transaction pool. + */ + val normalTransactions = block.normalTransactions + ?: throw IllegalArgumentException( + "removeTransactionsInBlock cannot be called with a block with a " + + "null transaction set!" + ) + var allSuccessful = true + for (transaction in normalTransactions) { + // FIXME: convert StandardTransaction to TransactionData + val builder = transaction.getSignedMessageBuilder(context.config.networkParameters) + val rpcTx = builder.build() + val txData = rpcTx.transaction.toModel() + + val txInfo = TransactionInfo( + blockNumber = block.height, + timestamp = block.timestamp, + transaction = txData, + confirmations = context.blockchain.getChainHeadIndex().height + 1 - block.height, + blockHash = block.hash.toString(), + merklePath = "", // FIXME: is it possible to calculate merklePath in SPV? + endorsedBlockHash = "", // FIXME: for POP + bitcoinBlockHash = "", // FIXME: for POP + bitcoinTxId = "", // FIXME: for POP + bitcoinConfirmations = 0, // FIXME: for POP + ) + updateTransactionInfo(txInfo) + } + } + enum class AddTransactionResult { SUCCESS, INVALID, DUPLICATE } + + private fun RpcTransaction.toModel() = TransactionData( + type = TransactionType.valueOf(type.name), + sourceAddress = sourceAddress.toProperAddressType(), + sourceAmount = sourceAmount, + outputs = outputsList.map { it.toModel() }, + transactionFee = transactionFee, + data = data.toHex(), + bitcoinTransaction = bitcoinTransaction.toHex(), + endorsedBlockHeader = endorsedBlockHeader.toHex(), + bitcoinBlockHeaderOfProof = "", + merklePath = merklePath, + contextBitcoinBlockHeaders = listOf(), + timestamp = timestamp, + size = size, + txId = txId.toByteArray().asVbkTxId() + ) + + private fun RpcOutput.toModel() = OutputData( + address = address.toHex(), + amount = amount + ) } diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/service/task/PendingTransactionsUpdateTask.kt b/nodecore-spv/src/main/java/org/veriblock/spv/service/task/PendingTransactionsUpdateTask.kt index b0e698ac4..f595a2a27 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/service/task/PendingTransactionsUpdateTask.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/service/task/PendingTransactionsUpdateTask.kt @@ -1,95 +1,95 @@ package org.veriblock.spv.net - - -import nodecore.api.grpc.RpcGetTransactionRequest -import nodecore.api.grpc.RpcOutput -import nodecore.api.grpc.RpcTransaction -import nodecore.api.grpc.RpcTransactionInfo -import nodecore.api.grpc.utilities.extensions.toByteString -import nodecore.api.grpc.utilities.extensions.toHex -import nodecore.p2p.buildMessage -import org.veriblock.core.crypto.asVbkTxId -import org.veriblock.core.utilities.createLogger -import org.veriblock.core.utilities.debugError -import org.veriblock.core.utilities.debugWarn -import org.veriblock.spv.SpvContext -import org.veriblock.spv.service.OutputData -import org.veriblock.spv.service.TransactionData -import org.veriblock.spv.service.TransactionInfo -import org.veriblock.spv.service.TransactionType -import org.veriblock.spv.service.advertise -import org.veriblock.spv.util.Threading.PEER_TABLE_SCOPE -import org.veriblock.spv.util.invokeOnFailure -import org.veriblock.spv.util.launchWithFixedDelay -import kotlin.system.exitProcess -import nodecore.api.grpc.utilities.extensions.toProperAddressType - -private val logger = createLogger {} - -fun SpvContext.startPendingTransactionsUpdateTask() { - PEER_TABLE_SCOPE.launchWithFixedDelay(5_000L, 20_000L) { - requestPendingTransactions() - }.invokeOnFailure { t -> - logger.debugError(t) { "The pending transactions update task has failed" } - exitProcess(1) - } -} - -suspend fun SpvContext.requestPendingTransactions() { - val pendingTransactionIds = pendingTransactionContainer.getPendingTransactionIds() - for (txId in pendingTransactionIds) { - try { - val request = buildMessage { - transactionRequest = RpcGetTransactionRequest.newBuilder() - .setId(txId.bytes.toByteString()) - .build() - } - val response = peerTable.requestMessage(request) - if (response?.transactionReply?.success == true) { - pendingTransactionContainer.updateTransactionInfo(response.transactionReply.transaction.toModel()) - } else { - val transaction = pendingTransactionContainer.getTransaction(txId) - if (transaction != null) { - peerTable.advertise(transaction) - } - } - } catch (e: Exception) { - logger.debugWarn(e) { "Unable to request pending transaction $txId" } - } - } -} - -private fun RpcTransactionInfo.toModel() = TransactionInfo( - confirmations = confirmations, - transaction = transaction.toModel(), - blockNumber = blockNumber, - timestamp = timestamp, - endorsedBlockHash = endorsedBlockHash.toHex(), - bitcoinBlockHash = bitcoinBlockHash.toHex(), - bitcoinTxId = bitcoinTxId.toHex(), - bitcoinConfirmations = bitcoinConfirmations, - blockHash = blockHash.toHex(), - merklePath = merklePath -) - -private fun RpcTransaction.toModel() = TransactionData( - type = TransactionType.valueOf(type.name), - sourceAddress = sourceAddress.toProperAddressType(), - sourceAmount = sourceAmount, - outputs = outputsList.map { it.toModel() }, - transactionFee = transactionFee, - data = data.toHex(), - bitcoinTransaction = bitcoinTransaction.toHex(), - endorsedBlockHeader = endorsedBlockHeader.toHex(), - bitcoinBlockHeaderOfProof = "", - merklePath = merklePath, - contextBitcoinBlockHeaders = listOf(), - timestamp = timestamp, - size = size, - txId = txId.toByteArray().asVbkTxId() -) - -private fun RpcOutput.toModel() = OutputData( - address = address.toHex(), - amount = amount -) +// +// +//import nodecore.api.grpc.RpcGetTransactionRequest +//import nodecore.api.grpc.RpcOutput +//import nodecore.api.grpc.RpcTransaction +//import nodecore.api.grpc.RpcTransactionInfo +//import nodecore.api.grpc.utilities.extensions.toByteString +//import nodecore.api.grpc.utilities.extensions.toHex +//import nodecore.p2p.buildMessage +//import org.veriblock.core.crypto.asVbkTxId +//import org.veriblock.core.utilities.createLogger +//import org.veriblock.core.utilities.debugError +//import org.veriblock.core.utilities.debugWarn +//import org.veriblock.spv.SpvContext +//import org.veriblock.spv.service.OutputData +//import org.veriblock.spv.service.TransactionData +//import org.veriblock.spv.service.TransactionInfo +//import org.veriblock.spv.service.TransactionType +//import org.veriblock.spv.service.advertise +//import org.veriblock.spv.util.Threading.PEER_TABLE_SCOPE +//import org.veriblock.spv.util.invokeOnFailure +//import org.veriblock.spv.util.launchWithFixedDelay +//import kotlin.system.exitProcess +//import nodecore.api.grpc.utilities.extensions.toProperAddressType +// +//private val logger = createLogger {} +// +//fun SpvContext.startPendingTransactionsUpdateTask() { +// PEER_TABLE_SCOPE.launchWithFixedDelay(5_000L, 20_000L) { +// requestPendingTransactions() +// }.invokeOnFailure { t -> +// logger.debugError(t) { "The pending transactions update task has failed" } +// exitProcess(1) +// } +//} +// +//suspend fun SpvContext.requestPendingTransactions() { +// val pendingTransactionIds = pendingTransactionContainer.getPendingTransactionIds() +// for (txId in pendingTransactionIds) { +// try { +// val request = buildMessage { +// transactionRequest = RpcGetTransactionRequest.newBuilder() +// .setId(txId.bytes.toByteString()) +// .build() +// } +// val response = peerTable.requestMessage(request) +// if (response?.transactionReply?.success == true) { +// pendingTransactionContainer.updateTransactionInfo(response.transactionReply.transaction.toModel()) +// } else { +// val transaction = pendingTransactionContainer.getTransaction(txId) +// if (transaction != null) { +// peerTable.advertise(transaction) +// } +// } +// } catch (e: Exception) { +// logger.debugWarn(e) { "Unable to request pending transaction $txId" } +// } +// } +//} +// +//private fun RpcTransactionInfo.toModel() = TransactionInfo( +// confirmations = confirmations, +// transaction = transaction.toModel(), +// blockNumber = blockNumber, +// timestamp = timestamp, +// endorsedBlockHash = endorsedBlockHash.toHex(), +// bitcoinBlockHash = bitcoinBlockHash.toHex(), +// bitcoinTxId = bitcoinTxId.toHex(), +// bitcoinConfirmations = bitcoinConfirmations, +// blockHash = blockHash.toHex(), +// merklePath = merklePath +//) +// +//private fun RpcTransaction.toModel() = TransactionData( +// type = TransactionType.valueOf(type.name), +// sourceAddress = sourceAddress.toProperAddressType(), +// sourceAmount = sourceAmount, +// outputs = outputsList.map { it.toModel() }, +// transactionFee = transactionFee, +// data = data.toHex(), +// bitcoinTransaction = bitcoinTransaction.toHex(), +// endorsedBlockHeader = endorsedBlockHeader.toHex(), +// bitcoinBlockHeaderOfProof = "", +// merklePath = merklePath, +// contextBitcoinBlockHeaders = listOf(), +// timestamp = timestamp, +// size = size, +// txId = txId.toByteArray().asVbkTxId() +//) +// +//private fun RpcOutput.toModel() = OutputData( +// address = address.toHex(), +// amount = amount +//) From e18907e67b40ef157d46a10f571226e8a0ef9c17 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Wed, 15 Dec 2021 08:25:06 +0300 Subject: [PATCH 09/12] hash alignment when deserializing RpcBlock --- .../spv/serialization/MessageSerializer.kt | 32 ++++++++++++++++--- .../service/PendingTransactionContainer.kt | 1 - 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/serialization/MessageSerializer.kt b/nodecore-spv/src/main/java/org/veriblock/spv/serialization/MessageSerializer.kt index 7f525509f..7e517170e 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/serialization/MessageSerializer.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/serialization/MessageSerializer.kt @@ -39,6 +39,11 @@ import org.veriblock.spv.model.asLightAddress private val logger = createLogger {} object MessageSerializer { + private const val VBK_HASH_LENGTH_FULL = 24 + private const val VBK_HASH_LENGTH = 12 + private const val VBK_KEYSTONE_HASH_LENGTH = 9 + private const val TRUNCATED_MERKLE_ROOT_LENGTH = 16 + fun deserialize(blockHeaderMessage: RpcBlockHeader, trustHash: Boolean = false): VeriBlockBlock { return if (trustHash) { SerializeDeserializeService.parseVeriBlockBlock(blockHeaderMessage.header.toByteArray(), blockHeaderMessage.hash.toByteArray().asVbkHash()) @@ -82,13 +87,32 @@ object MessageSerializer { } fun deserialize(blockMessage: RpcBlock): FullBlock { + val merkleRoot = ByteStringUtility.byteStringToHex(blockMessage.merkleRoot) val block = FullBlock( blockMessage.number, blockMessage.version.toShort(), - blockMessage.previousHash.asVbkPreviousBlockHash(), - blockMessage.secondPreviousHash.asVbkPreviousKeystoneHash(), - blockMessage.thirdPreviousHash.asVbkPreviousKeystoneHash(), - ByteStringUtility.byteStringToHex(blockMessage.merkleRoot).asTruncatedMerkleRoot(), + + if (blockMessage.previousHash.size() == VBK_HASH_LENGTH_FULL) + blockMessage.previousHash.substring(VBK_HASH_LENGTH).asVbkPreviousBlockHash() + else + blockMessage.previousHash.asVbkPreviousBlockHash(), + + if (blockMessage.secondPreviousHash.size() == VBK_HASH_LENGTH_FULL) + blockMessage.secondPreviousHash.substring(VBK_HASH_LENGTH_FULL - VBK_KEYSTONE_HASH_LENGTH).asVbkPreviousKeystoneHash() + else + blockMessage.secondPreviousHash.asVbkPreviousKeystoneHash(), + + if (blockMessage.thirdPreviousHash.size() == VBK_HASH_LENGTH_FULL) + blockMessage.thirdPreviousHash.substring(VBK_HASH_LENGTH_FULL - VBK_KEYSTONE_HASH_LENGTH).asVbkPreviousKeystoneHash() + else + blockMessage.thirdPreviousHash.asVbkPreviousKeystoneHash(), + + if (merkleRoot.length == VBK_HASH_LENGTH_FULL*2) + merkleRoot.substring((VBK_HASH_LENGTH_FULL - TRUNCATED_MERKLE_ROOT_LENGTH)*2).asTruncatedMerkleRoot() + else { + logger.error { merkleRoot.length } + merkleRoot.asTruncatedMerkleRoot()}, + blockMessage.timestamp, blockMessage.encodedDifficulty, blockMessage.winningNonce diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt b/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt index 159e3926b..6ebef3245 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt @@ -230,7 +230,6 @@ class PendingTransactionContainer( "removeTransactionsInBlock cannot be called with a block with a " + "null transaction set!" ) - var allSuccessful = true for (transaction in normalTransactions) { // FIXME: convert StandardTransaction to TransactionData val builder = transaction.getSignedMessageBuilder(context.config.networkParameters) From 90b309a31b965120165771c12993ffd26479456b Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Wed, 15 Dec 2021 09:24:52 +0300 Subject: [PATCH 10/12] deserialize tx publicKey and signature --- .../org/veriblock/spv/serialization/MessageSerializer.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/serialization/MessageSerializer.kt b/nodecore-spv/src/main/java/org/veriblock/spv/serialization/MessageSerializer.kt index 7e517170e..b37d1785f 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/serialization/MessageSerializer.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/serialization/MessageSerializer.kt @@ -25,6 +25,7 @@ import org.veriblock.core.crypto.asVbkHash import org.veriblock.core.crypto.asBtcHash import org.veriblock.core.crypto.asTruncatedMerkleRoot import org.veriblock.core.crypto.asVbkTxId +import org.veriblock.core.utilities.AddressUtility import org.veriblock.sdk.models.VeriBlockBlock import org.veriblock.sdk.models.asCoin import org.veriblock.sdk.services.SerializeDeserializeService @@ -141,6 +142,11 @@ object MessageSerializer { } tx.setSignatureIndex(signedTransaction.signatureIndex) tx.data = txMessage.data.toByteArray() + + // TODO: add checks + tx.publicKey = signedTransaction.publicKey.toByteArray() + tx.signature = signedTransaction.signature.toByteArray() + return tx } From bc028416f89cf4b893c52f2ae0dc48bb92380a9e Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Wed, 15 Dec 2021 11:46:53 +0300 Subject: [PATCH 11/12] Skip full block downloading if SPV is not synchronized --- .../src/main/java/org/veriblock/spv/model/DownloadStatus.kt | 3 +++ .../src/main/java/org/veriblock/spv/net/PeerEventListener.kt | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/model/DownloadStatus.kt b/nodecore-spv/src/main/java/org/veriblock/spv/model/DownloadStatus.kt index a69d1a772..57bb3a670 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/model/DownloadStatus.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/model/DownloadStatus.kt @@ -10,4 +10,7 @@ enum class DownloadStatus { fun isDownloading(): Boolean = DOWNLOADING == this + + fun isReady(): Boolean = + READY == this } diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt index 4ece5afe1..06b7d5d63 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/net/PeerEventListener.kt @@ -367,7 +367,8 @@ class PeerEventListener( // TODO(warchant): if allBlocksAccepted == false here, block can not be connected or invalid // maybe ban peer? for now, do nothing - // download full block bodies to manage mempool + // download full block bodies to manage mempool. Skip if SPV is not synchronized + if (!spvContext.spvService.getDownloadStatus().downloadStatus.isReady()) return try { val blocksToRequest = ArrayList() From 246125e8430039096ec51a7b55e50a1422cf2cd8 Mon Sep 17 00:00:00 2001 From: Shchegolev Vladimir Date: Wed, 22 Dec 2021 17:29:14 +0300 Subject: [PATCH 12/12] enable logs in tests --- nodecore-spv/build.gradle.kts | 68 ++++++++++-- .../service/PendingTransactionContainer.kt | 17 ++- nodecore-spv/src/main/resources/log4j2.xml | 26 +++++ .../PendingTransactionContainerTest.kt | 102 ++++++++++++++++++ 4 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 nodecore-spv/src/main/resources/log4j2.xml create mode 100644 nodecore-spv/src/test/java/org/veriblock/spv/service/PendingTransactionContainerTest.kt diff --git a/nodecore-spv/build.gradle.kts b/nodecore-spv/build.gradle.kts index d1b313141..b6e09e0b5 100644 --- a/nodecore-spv/build.gradle.kts +++ b/nodecore-spv/build.gradle.kts @@ -5,7 +5,10 @@ // https://www.veriblock.org // Distributed under the MIT software license, see the accompanying // file LICENSE or http://www.opensource.org/licenses/mit-license.php. -import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import com.adarshr.gradle.testlogger.TestLoggerExtension +import com.adarshr.gradle.testlogger.TestLoggerPlugin +import com.adarshr.gradle.testlogger.theme.ThemeType plugins { java @@ -15,6 +18,7 @@ plugins { `java-library` `maven-publish` id("com.jfrog.artifactory") + id("com.adarshr.test-logger") version "3.1.0" } configurations.all { @@ -22,6 +26,8 @@ configurations.all { resolutionStrategy.cacheChangingModulesFor(0, "seconds") } +val log4jVersion = "2.16.0" + dependencies { api(project(":veriblock-core")) api(project(":veriblock-extensions")) @@ -31,10 +37,10 @@ dependencies { implementation("io.ktor:ktor-network-jvm:1.6.4") // Logging - implementation("io.github.microutils:kotlin-logging:2.0.11") - implementation("org.apache.logging.log4j:log4j-api:2.14.1") - implementation("org.apache.logging.log4j:log4j-core:2.14.1") - implementation("org.slf4j:slf4j-api:1.7.32") + implementation("org.apache.logging.log4j:log4j-api:$log4jVersion") + implementation("org.apache.logging.log4j:log4j-core:$log4jVersion") + implementation("org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion") + implementation("io.github.microutils:kotlin-logging:2.1.16") implementation("org.freemarker:freemarker:2.3.31") @@ -43,12 +49,6 @@ dependencies { testImplementation("io.mockk:mockk:1.12.0") } -tasks.test { - testLogging { - exceptionFormat = TestExceptionFormat.FULL - } -} - setupJar("VeriBlock Lite Toolkit", "veriblock.lite") val sourcesJar = setupSourcesJar() @@ -59,3 +59,49 @@ publish( ) setupJacoco() + +tasks.test { + useJUnitPlatform() + maxParallelForks = 1 +} + +tasks.withType { + kotlinOptions.jvmTarget = "1.8" +} +tasks.withType { + targetCompatibility = "1.8" + sourceCompatibility = "1.8" +} + +tasks.withType { + useJUnitPlatform() + + reports { + html.required.set(true) + junitXml.required.set(true) + junitXml.apply { + isOutputPerTestCase = true // defaults to false + } + } +} + +plugins.withType { + configure { + theme = ThemeType.MOCHA + showExceptions = true + showStackTraces = true + showFullStackTraces = false + showCauses = true + slowThreshold = 20000 + showSummary = true + showSimpleNames = false + showPassed = true + showSkipped = true + showFailed = true + showStandardStreams = true + showPassedStandardStreams = false + showSkippedStandardStreams = false + showFailedStandardStreams = true + logLevel = LogLevel.LIFECYCLE + } +} diff --git a/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt b/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt index 6ebef3245..79a78c17b 100644 --- a/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt +++ b/nodecore-spv/src/main/java/org/veriblock/spv/service/PendingTransactionContainer.kt @@ -59,6 +59,16 @@ class PendingTransactionContainer( return pendingTransactions } + fun getTransaction(txId: VbkTxId): Transaction? { + pendingTransactions[txId]?.let { + return it + } + addedToBlockchainTransactions[txId]?.let { + return it + } + return null + } + fun getTransactionInfo(txId: VbkTxId): TransactionInfo? { pendingTransactionsInfo[txId]?.let { return it @@ -129,6 +139,7 @@ class PendingTransactionContainer( } fun addNetworkTransaction(message: RpcTransactionUnion): AddTransactionResult? { + logger.info { "START!!!" } var txId: String? = null txId = when (message.transactionCase) { RpcTransactionUnion.TransactionCase.UNSIGNED -> { @@ -147,11 +158,13 @@ class PendingTransactionContainer( } } return try { + logger.error { "deserializeNormalTransaction" } val transaction: Transaction = MessageSerializer.deserializeNormalTransaction(message) // TODO: DenylistTransactionCache // if (DenylistTransactionCache.get().contains(transaction.getTxId())) { // return PendingTransactionContainer.AddTransactionResult.INVALID // } + logger.error { "addTransaction" } addTransaction(transaction) logger.debug("Add transaction to mempool {}", txId) AddTransactionResult.SUCCESS @@ -161,10 +174,6 @@ class PendingTransactionContainer( } } - fun getTransaction(txId: VbkTxId): Transaction? { - return pendingTransactions[txId] - } - fun getPendingSignatureIndexForAddress(address: Address, ledgerSignatureIndex: Long?): Long? = lock.withLock { val transactions = pendingTransactionsByAddress[address.address] if (transactions.isNullOrEmpty()) { diff --git a/nodecore-spv/src/main/resources/log4j2.xml b/nodecore-spv/src/main/resources/log4j2.xml new file mode 100644 index 000000000..dd626a481 --- /dev/null +++ b/nodecore-spv/src/main/resources/log4j2.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nodecore-spv/src/test/java/org/veriblock/spv/service/PendingTransactionContainerTest.kt b/nodecore-spv/src/test/java/org/veriblock/spv/service/PendingTransactionContainerTest.kt new file mode 100644 index 000000000..a04c3372f --- /dev/null +++ b/nodecore-spv/src/test/java/org/veriblock/spv/service/PendingTransactionContainerTest.kt @@ -0,0 +1,102 @@ +package org.veriblock.spv.service + +import com.google.protobuf.ByteString +import io.kotest.matchers.shouldNotBe +import io.mockk.every +import io.mockk.mockk +import nodecore.api.grpc.RpcSignedTransaction +import nodecore.api.grpc.RpcTransaction +import nodecore.api.grpc.RpcTransactionUnion +import org.junit.Before +import org.junit.Test +import org.veriblock.core.Context +import org.veriblock.core.params.defaultTestNetParameters +import org.veriblock.core.utilities.AddressUtility +import org.veriblock.core.utilities.extensions.toHex +import org.veriblock.core.wallet.AddressKeyGenerator +import org.veriblock.spv.SpvConfig +import org.veriblock.spv.SpvContext +import kotlin.random.Random + +private const val KB = 1024 + +class PendingTransactionContainerTest { + private lateinit var spvContext: SpvContext + private lateinit var pendingTransactionContainer: PendingTransactionContainer + + private val ledger = mutableMapOf() + private val pendingLedger = mutableMapOf() + + data class AddressData( + val address: String, + var balance: Long = 50_000_000, + var sigIndex: Long = 0L + ) + + private fun String.getAddressData() = ledger.getOrPut(this) { AddressData(this) } + private fun String.getPendingAddressData() = pendingLedger.getOrPut(this) { getAddressData().copy() } + + @Before + fun setUp() { + Context.set(defaultTestNetParameters) + spvContext = SpvContext(SpvConfig(defaultTestNetParameters, connectDirectlyTo = listOf("localhost"))) + spvContext.wallet.newAddress + pendingTransactionContainer = PendingTransactionContainer(spvContext) + } + + private fun createRpcTransactionUnionMock( + txCase: RpcTransactionUnion.TransactionCase = RpcTransactionUnion.TransactionCase.SIGNED, + tx: RpcSignedTransaction = createRpcSignedTransactionMock(), + ): RpcTransactionUnion = mockk { + every { transactionCase } returns txCase + every { signed } returns tx + } + + private fun createRpcSignedTransactionMock( + pubKey: ByteString = ByteString.copyFromUtf8(spvContext.wallet.defaultAddress.hash), + sigIndex: Long = ++pubKey.toString().getPendingAddressData().sigIndex, + tx: RpcTransaction = createRpcTransactionMock( + srcAddress = pubKey, + ), + ): RpcSignedTransaction = mockk { + every { transaction } returns tx + every { publicKey } returns pubKey + every { signature } returns ByteString.copyFrom(spvContext.wallet.signMessage(tx.txId.toByteArray(), pubKey.toStringUtf8())) + every { signatureIndex } returns sigIndex + } + + private fun createRpcTransactionMock( + typeTx: RpcTransaction.Type = RpcTransaction.Type.STANDARD, + id: ByteString = ByteString.copyFromUtf8(Random.nextBytes(32).toHex()), + srcAddress: ByteString = ByteString.copyFromUtf8(generateRandomAddress()), + amount: Long = 1000L, + valid: Boolean = true, + signed: Boolean = true, + fee: Long = 1_000_000L, + dataTx: ByteString = ByteString.copyFromUtf8(""), + ): RpcTransaction = mockk { + every { type } returns typeTx + every { txId } returns id + every { sourceAddress } returns srcAddress + every { sourceAmount } returns amount +// every { isSigned } returns signed +// every { isValid } returns valid + every { outputsList } returns emptyList() +// every { timeStamp } returns getCurrentTimestamp() + every { transactionFee } returns fee + every { data } returns dataTx + every { size } returns KB // 1KB per transaction + } + + private fun generateRandomAddress(): String { + val pair = AddressKeyGenerator.generate() + return AddressUtility.addressFromPublicKey(pair.public) + } + + @Test + fun testAddNetworkTransaction() { + val txUnion = createRpcTransactionUnionMock() + val res = pendingTransactionContainer.addNetworkTransaction(txUnion) + res shouldNotBe PendingTransactionContainer.AddTransactionResult.INVALID + } +}