From 10362c9085ebf7eafee38631cc6b3b38d729c5ff Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 22 Aug 2023 19:28:15 +0300 Subject: [PATCH 001/109] WeakBlockAlgos --- .../history/header/WeakBlockAlgos.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala new file mode 100644 index 0000000000..7b2ee3cd2d --- /dev/null +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala @@ -0,0 +1,17 @@ +package org.ergoplatform.modifiers.history.header + +import org.ergoplatform.nodeView.history.ErgoHistory.Difficulty + + +object WeakBlockAlgos { + + val weaksPerBlock = 128 // weak blocks per block + + def isWeak(header: Header, requiredDifficulty: Difficulty): Boolean = { + val diff = requiredDifficulty / weaksPerBlock + header.requiredDifficulty >= diff + } + + + +} From c5d30d3fd4217ec655987c3cc5c708bb14bb5dff Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 18 Sep 2023 13:34:03 +0300 Subject: [PATCH 002/109] WeakBlockInfo, WeakBlockMessageSpec --- .../history/header/WeakBlockAlgos.scala | 55 ++++++++++++++++++- .../modifiers/mempool/ErgoTransaction.scala | 2 + 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala index 7b2ee3cd2d..06d9e3cc76 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala @@ -1,7 +1,12 @@ package org.ergoplatform.modifiers.history.header import org.ergoplatform.nodeView.history.ErgoHistory.Difficulty - +import scorex.core.consensus.SyncInfo +import scorex.core.network.message.Message.MessageCode +import scorex.core.network.message.MessageSpecV1 +import scorex.core.serialization.{BytesSerializable, ErgoSerializer} +import scorex.util.serialization.{Reader, Writer} +import scorex.util.Extensions._ object WeakBlockAlgos { @@ -14,4 +19,52 @@ object WeakBlockAlgos { + // messages: + // + // weak block signal: + // version + // weak block (~200 bytes) - contains a link to parent block + // previous weak block + // transactions since last weak blocks (8 byte ids?) + + class WeakBlockInfo(version: Byte, weakBlock: Header, prevWeakBlockId: Array[Byte], txsSinceLastWeak: Array[Array[Byte]]) extends BytesSerializable { + override type M = WeakBlockInfo + + val weakTransactionIdLength = 8 + /** + * Serializer which can convert self to bytes + */ + override def serializer: ErgoSerializer[WeakBlockInfo] = new ErgoSerializer[WeakBlockInfo] { + override def serialize(wbi: WeakBlockInfo, w: Writer): Unit = { + w.put(version) + HeaderSerializer.serialize(weakBlock, w) + w.putBytes(prevWeakBlockId) + w.putUShort(txsSinceLastWeak.length) // consider case when more txs than can be in short + txsSinceLastWeak.foreach(txId => w.putBytes(txId)) + } + + override def parse(r: Reader): WeakBlockInfo = { + val version = r.getByte() + val weakBlock = HeaderSerializer.parse(r) + val prevWeakBlockId = r.getBytes(32) + val txsCount = r.getUShort().toShortExact + val txsSinceLastWeak = (1 to txsCount).map{_ => // todo: more efficient array construction + r.getBytes(weakTransactionIdLength) + }.toArray + new WeakBlockInfo(version, weakBlock, prevWeakBlockId, txsSinceLastWeak) + } + } + } + + class WeakBlockMessageSpec[SI <: SyncInfo](serializer: ErgoSerializer[SI]) extends MessageSpecV1[SI] { + + override val messageCode: MessageCode = 90: Byte + override val messageName: String = "Sync" + + override def serialize(data: SI, w: Writer): Unit = serializer.serialize(data, w) + + override def parse(r: Reader): SI = serializer.parse(r) + } + + } diff --git a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala index 55fa39b005..088bd1256f 100644 --- a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala +++ b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala @@ -68,6 +68,8 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input], override lazy val id: ModifierId = bytesToId(serializedId) + lazy val weakId = id.take(8) + /** * Id of transaction "witness" (taken from Bitcoin jargon, means commitment to signatures of a transaction). * Id is 248-bit long, to distinguish transaction ids from witness ids in Merkle tree of transactions, From 5c2eb297ad4fec0d73d1963b5166ca50db712cb0 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 29 Sep 2023 00:44:07 +0300 Subject: [PATCH 003/109] impl steps --- .../modifiers/history/header/WeakBlockAlgos.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala index 06d9e3cc76..f32358f019 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala @@ -8,6 +8,16 @@ import scorex.core.serialization.{BytesSerializable, ErgoSerializer} import scorex.util.serialization.{Reader, Writer} import scorex.util.Extensions._ +/** + * Implementation steps: + * * implement basic weak block algorithms (isweak etc) + * * implement weak block network message + * * implement weak block info support in sync tracker + * * implement downloading weak blocks chain + * * implement avoiding downloading full-blocks + * * weak blocks support in /mining API + * * weak confirmations API + */ object WeakBlockAlgos { val weaksPerBlock = 128 // weak blocks per block From 884311418c4c0493ed4dc7551a12918d25925a9b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 31 Oct 2023 01:22:36 +0300 Subject: [PATCH 004/109] initial weak blocks structures and algos --- papers/propagation.md | 8 ++++++ .../http/api/MiningApiRoute.scala | 11 +++++++- .../mining/AutolykosSolution.scala | 25 ++++++++++++++++++ .../history/header/WeakBlockAlgos.scala | 26 ++++++++++++------- .../modifiers/mempool/ErgoTransaction.scala | 4 +-- 5 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 papers/propagation.md diff --git a/papers/propagation.md b/papers/propagation.md new file mode 100644 index 0000000000..1320aafd60 --- /dev/null +++ b/papers/propagation.md @@ -0,0 +1,8 @@ +Improved Block Propagation +========================== + +* Author: kushti +* Status: Proposed +* Created: 31-Oct-2023 +* License: CC0 +* Forking: Soft Fork \ No newline at end of file diff --git a/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala index c9ca7c1b8c..1402e9e3d5 100644 --- a/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala @@ -6,7 +6,7 @@ import akka.pattern.ask import io.circe.syntax._ import io.circe.{Encoder, Json} import org.ergoplatform.mining.CandidateGenerator.Candidate -import org.ergoplatform.mining.{AutolykosSolution, CandidateGenerator, ErgoMiner} +import org.ergoplatform.mining.{AutolykosSolution, CandidateGenerator, ErgoMiner, WeakAutolykosSolution} import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.wallet.ErgoAddressJsonEncoder import org.ergoplatform.settings.ErgoSettings @@ -63,6 +63,15 @@ case class MiningApiRoute(miner: ActorRef, ApiResponse(result) } + def weakSolutionR: Route = (path("weakSolution") & post & entity(as[WeakAutolykosSolution])) { solution => + val result = if (ergoSettings.nodeSettings.useExternalMiner) { + miner.askWithStatus(solution).mapTo[Unit] + } else { + Future.failed(new Exception("External miner support is inactive")) + } + ApiResponse(result) + } + def rewardAddressR: Route = (path("rewardAddress") & get) { val addressF: Future[ErgoAddress] = miner.askWithStatus(ErgoMiner.ReadMinerPk) diff --git a/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala b/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala index adeff25edc..d00e755091 100644 --- a/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala +++ b/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala @@ -4,6 +4,7 @@ import io.circe.syntax._ import io.circe.{Decoder, Encoder, HCursor} import org.bouncycastle.util.BigIntegers import org.ergoplatform.http.api.ApiCodecs +import org.ergoplatform.mining.AutolykosSolution.pkForV2 import org.ergoplatform.modifiers.history.header.Header.Version import org.ergoplatform.settings.Algos import scorex.core.serialization.ErgoSerializer @@ -57,6 +58,30 @@ object AutolykosSolution extends ApiCodecs { } +case class WeakAutolykosSolution(pk: EcPointType, n: Array[Byte]) { + val encodedPk: Array[Byte] = groupElemToBytes(pk) +} + +object WeakAutolykosSolution extends ApiCodecs { + implicit val jsonEncoder: Encoder[WeakAutolykosSolution] = { s: WeakAutolykosSolution => + Map( + "pk" -> s.pk.asJson, + "n" -> Algos.encode(s.n).asJson + ).asJson + } + + implicit val jsonDecoder: Decoder[WeakAutolykosSolution] = { c: HCursor => + for { + pkOpt <- c.downField("pk").as[Option[EcPointType]] + n <- c.downField("n").as[Array[Byte]] + } yield { + WeakAutolykosSolution(pkOpt.getOrElse(pkForV2), n) + } + } + +} + + /** * Binary serializer for Autolykos v1 solution, diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala index f32358f019..bc753b0f77 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala @@ -22,6 +22,8 @@ object WeakBlockAlgos { val weaksPerBlock = 128 // weak blocks per block + val weakTransactionIdLength = 6 + def isWeak(header: Header, requiredDifficulty: Difficulty): Boolean = { val diff = requiredDifficulty / weaksPerBlock header.requiredDifficulty >= diff @@ -34,13 +36,15 @@ object WeakBlockAlgos { // weak block signal: // version // weak block (~200 bytes) - contains a link to parent block - // previous weak block + // previous weak block id // transactions since last weak blocks (8 byte ids?) + // todo: move `txsSinceLastWeak` to a dedicated message class WeakBlockInfo(version: Byte, weakBlock: Header, prevWeakBlockId: Array[Byte], txsSinceLastWeak: Array[Array[Byte]]) extends BytesSerializable { override type M = WeakBlockInfo - val weakTransactionIdLength = 8 + val initialMessageVersion = 1 + /** * Serializer which can convert self to bytes */ @@ -55,13 +59,17 @@ object WeakBlockAlgos { override def parse(r: Reader): WeakBlockInfo = { val version = r.getByte() - val weakBlock = HeaderSerializer.parse(r) - val prevWeakBlockId = r.getBytes(32) - val txsCount = r.getUShort().toShortExact - val txsSinceLastWeak = (1 to txsCount).map{_ => // todo: more efficient array construction - r.getBytes(weakTransactionIdLength) - }.toArray - new WeakBlockInfo(version, weakBlock, prevWeakBlockId, txsSinceLastWeak) + if (version == initialMessageVersion) { + val weakBlock = HeaderSerializer.parse(r) + val prevWeakBlockId = r.getBytes(32) + val txsCount = r.getUShort().toShortExact + val txsSinceLastWeak = (1 to txsCount).map { _ => // todo: more efficient array construction + r.getBytes(weakTransactionIdLength) + }.toArray + new WeakBlockInfo(version, weakBlock, prevWeakBlockId, txsSinceLastWeak) + } else { + throw new Exception("Unsupported weakblock message version") + } } } } diff --git a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala index 088bd1256f..9900d5e4f0 100644 --- a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala +++ b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala @@ -6,7 +6,7 @@ import org.ergoplatform.SigmaConstants.{MaxBoxSize, MaxPropositionBytes} import org.ergoplatform._ import org.ergoplatform.http.api.ApiCodecs import org.ergoplatform.mining.emission.EmissionRules -import org.ergoplatform.modifiers.history.header.Header +import org.ergoplatform.modifiers.history.header.{Header, WeakBlockAlgos} import org.ergoplatform.modifiers.mempool.ErgoTransaction.unresolvedIndices import org.ergoplatform.modifiers.{ErgoNodeViewModifier, NetworkObjectTypeId, TransactionTypeId} import org.ergoplatform.nodeView.ErgoContext @@ -68,7 +68,7 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input], override lazy val id: ModifierId = bytesToId(serializedId) - lazy val weakId = id.take(8) + lazy val weakId = id.take(WeakBlockAlgos.weakTransactionIdLength) /** * Id of transaction "witness" (taken from Bitcoin jargon, means commitment to signatures of a transaction). From 5c4f9c290759b1c37c33a9b179323a61422db04e Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 1 Nov 2023 02:02:59 +0300 Subject: [PATCH 005/109] compact block like messaging --- .../history/header/WeakBlockAlgos.scala | 90 +++++++++++++++---- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala index bc753b0f77..cae95b6ea1 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala @@ -1,12 +1,13 @@ package org.ergoplatform.modifiers.history.header import org.ergoplatform.nodeView.history.ErgoHistory.Difficulty -import scorex.core.consensus.SyncInfo +import scorex.core.{NodeViewModifier, bytesToId, idToBytes} import scorex.core.network.message.Message.MessageCode import scorex.core.network.message.MessageSpecV1 -import scorex.core.serialization.{BytesSerializable, ErgoSerializer} +import scorex.core.serialization.ErgoSerializer import scorex.util.serialization.{Reader, Writer} import scorex.util.Extensions._ +import scorex.util.ModifierId /** * Implementation steps: @@ -40,21 +41,20 @@ object WeakBlockAlgos { // transactions since last weak blocks (8 byte ids?) // todo: move `txsSinceLastWeak` to a dedicated message - class WeakBlockInfo(version: Byte, weakBlock: Header, prevWeakBlockId: Array[Byte], txsSinceLastWeak: Array[Array[Byte]]) extends BytesSerializable { - override type M = WeakBlockInfo + case class WeakBlockInfo(version: Byte, weakBlock: Header, prevWeakBlockId: Array[Byte]) + + object WeakBlockInfo { val initialMessageVersion = 1 /** * Serializer which can convert self to bytes */ - override def serializer: ErgoSerializer[WeakBlockInfo] = new ErgoSerializer[WeakBlockInfo] { + def serializer: ErgoSerializer[WeakBlockInfo] = new ErgoSerializer[WeakBlockInfo] { override def serialize(wbi: WeakBlockInfo, w: Writer): Unit = { - w.put(version) - HeaderSerializer.serialize(weakBlock, w) - w.putBytes(prevWeakBlockId) - w.putUShort(txsSinceLastWeak.length) // consider case when more txs than can be in short - txsSinceLastWeak.foreach(txId => w.putBytes(txId)) + w.put(wbi.version) + HeaderSerializer.serialize(wbi.weakBlock, w) + w.putBytes(wbi.prevWeakBlockId) } override def parse(r: Reader): WeakBlockInfo = { @@ -62,11 +62,7 @@ object WeakBlockAlgos { if (version == initialMessageVersion) { val weakBlock = HeaderSerializer.parse(r) val prevWeakBlockId = r.getBytes(32) - val txsCount = r.getUShort().toShortExact - val txsSinceLastWeak = (1 to txsCount).map { _ => // todo: more efficient array construction - r.getBytes(weakTransactionIdLength) - }.toArray - new WeakBlockInfo(version, weakBlock, prevWeakBlockId, txsSinceLastWeak) + new WeakBlockInfo(version, weakBlock, prevWeakBlockId) } else { throw new Exception("Unsupported weakblock message version") } @@ -74,15 +70,71 @@ object WeakBlockAlgos { } } - class WeakBlockMessageSpec[SI <: SyncInfo](serializer: ErgoSerializer[SI]) extends MessageSpecV1[SI] { + object WeakBlockMessageSpec extends MessageSpecV1[WeakBlockInfo] { + + val MaxMessageSize = 10000 override val messageCode: MessageCode = 90: Byte - override val messageName: String = "Sync" + override val messageName: String = "WeakBlock" + + override def serialize(data: WeakBlockInfo, w: Writer): Unit = { + WeakBlockInfo.serializer.serialize(data, w) + } + + override def parse(r: Reader): WeakBlockInfo = { + WeakBlockInfo.serializer.parse(r) + } + } + + /** + * On receiving weak block or block, the node is sending last weak block id it has to get short transaction + * ids since then + */ + object GetDataSpec extends MessageSpecV1[ModifierId] { + import scorex.util.{idToBytes, bytesToId} - override def serialize(data: SI, w: Writer): Unit = serializer.serialize(data, w) + override val messageCode: MessageCode = 91: Byte + override val messageName: String = "GetData" + + override def serialize(data: ModifierId, w: Writer): Unit = { + w.putBytes(idToBytes(data)) + } - override def parse(r: Reader): SI = serializer.parse(r) + override def parse(r: Reader): ModifierId = { + bytesToId(r.getBytes(NodeViewModifier.ModifierIdSize)) + } } + case class TransactionsSince(transactionsWithBlockIds: Array[(ModifierId, Array[Array[Byte]])]) + + class DataSpec extends MessageSpecV1[TransactionsSince] { + + override val messageCode: MessageCode = 92: Byte + override val messageName: String = "GetData" + + override def serialize(data: TransactionsSince, w: Writer): Unit = { + w.putUInt(data.transactionsWithBlockIds.length) + data.transactionsWithBlockIds.foreach { case (id, txIds) => + w.putBytes(idToBytes(id)) + w.putUInt(txIds.length) + txIds.foreach { txId => + w.putBytes(txId) + } + } + } + + override def parse(r: Reader): TransactionsSince = { + val blocksCount = r.getUInt().toIntExact + val records = (1 to blocksCount).map{_ => + val blockId = r.getBytes(32) + val txsCount = r.getUInt().toIntExact + val txIds = (1 to txsCount).map{_ => + r.getBytes(6) + }.toArray + bytesToId(blockId) -> txIds + }.toArray + TransactionsSince(records) + } + } } From 0dbfeb0fd40c62220bc7f98ce115c8425524852a Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 1 Nov 2023 20:03:59 +0300 Subject: [PATCH 006/109] before structures --- .../modifiers/history/header/WeakBlockAlgos.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala index cae95b6ea1..72e12ee4d7 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala @@ -70,6 +70,10 @@ object WeakBlockAlgos { } } + /** + * Message that is informing about weak block produced. + * Contains header and link to previous weak block (). + */ object WeakBlockMessageSpec extends MessageSpecV1[WeakBlockInfo] { val MaxMessageSize = 10000 @@ -87,10 +91,11 @@ object WeakBlockAlgos { } /** - * On receiving weak block or block, the node is sending last weak block id it has to get short transaction + * On receiving weak block or block, the node is sending last weak block or block id it has to get short transaction * ids since then */ object GetDataSpec extends MessageSpecV1[ModifierId] { + import scorex.util.{idToBytes, bytesToId} override val messageCode: MessageCode = 91: Byte @@ -125,10 +130,10 @@ object WeakBlockAlgos { override def parse(r: Reader): TransactionsSince = { val blocksCount = r.getUInt().toIntExact - val records = (1 to blocksCount).map{_ => + val records = (1 to blocksCount).map { _ => val blockId = r.getBytes(32) val txsCount = r.getUInt().toIntExact - val txIds = (1 to txsCount).map{_ => + val txIds = (1 to txsCount).map { _ => r.getBytes(6) }.toArray bytesToId(blockId) -> txIds @@ -138,3 +143,4 @@ object WeakBlockAlgos { } } + From 1a68d0e9901adc065323a189790f200d0269867b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 4 Nov 2023 23:58:34 +0300 Subject: [PATCH 007/109] skeleton of processing algo --- .../history/header/WeakBlockAlgos.scala | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala index 72e12ee4d7..25ad8b065e 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala @@ -1,5 +1,6 @@ package org.ergoplatform.modifiers.history.header +import org.ergoplatform.modifiers.history.header.WeakBlockAlgos.WeakBlockInfo import org.ergoplatform.nodeView.history.ErgoHistory.Difficulty import scorex.core.{NodeViewModifier, bytesToId, idToBytes} import scorex.core.network.message.Message.MessageCode @@ -9,6 +10,8 @@ import scorex.util.serialization.{Reader, Writer} import scorex.util.Extensions._ import scorex.util.ModifierId +import scala.collection.mutable + /** * Implementation steps: * * implement basic weak block algorithms (isweak etc) @@ -144,3 +147,58 @@ object WeakBlockAlgos { } +object structures { + var lastBlock: Header = null + + val weakBlocks: mutable.Set[ModifierId] = mutable.Set.empty + + val weakBlockLinks: mutable.Map[ModifierId, ModifierId] = mutable.Map.empty + + var weakBlockTxs: Map[ModifierId, Array[Array[Byte]]] = Map.empty + + def processWeakBlock(wbi: WeakBlockInfo) = { + val wbHeader = wbi.weakBlock + val prevWbId = bytesToId(wbi.prevWeakBlockId) + val wbHeight = wbHeader.height + + if(wbHeader.id == prevWbId){ + ??? // todo: throw error + } + + if (wbHeight < lastBlock.height + 1) { + // just ignore as we have better block already + } else if (wbHeight == lastBlock.height + 1) { + if (wbHeader.parentId == lastBlock.id) { + val weakBlockId = wbHeader.id + if(weakBlocks.contains(weakBlockId)){ + // todo: what to do? + } else { + weakBlocks += weakBlockId + if (weakBlocks.contains(prevWbId)){ + weakBlockLinks.put(weakBlockId, prevWbId) + } else { + //todo: download prev weak block id + } + // todo: download weak block related txs + } + } else { + // todo: we got orphaned block's weak block, process this + } + } else { + // just ignoring weak block coming from future for now + } + } + + def processBlock(header: Header) = { + if (header.height > lastBlock.height) { + lastBlock = header + weakBlocks.clear() + weakBlockLinks.clear() + weakBlockTxs = Map.empty + } else { + ??? // todo: process + } + } + +} + From 104092632c62872ff44bf0f6e9b7d791d5dc0ba1 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 7 Nov 2023 18:30:42 +0300 Subject: [PATCH 008/109] weak blocks => sub blocks --- .../history/header/WeakBlockAlgos.scala | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala index 25ad8b065e..5c95c37540 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala @@ -1,6 +1,6 @@ package org.ergoplatform.modifiers.history.header -import org.ergoplatform.modifiers.history.header.WeakBlockAlgos.WeakBlockInfo +import org.ergoplatform.modifiers.history.header.WeakBlockAlgos.SubBlockInfo import org.ergoplatform.nodeView.history.ErgoHistory.Difficulty import scorex.core.{NodeViewModifier, bytesToId, idToBytes} import scorex.core.network.message.Message.MessageCode @@ -44,28 +44,28 @@ object WeakBlockAlgos { // transactions since last weak blocks (8 byte ids?) // todo: move `txsSinceLastWeak` to a dedicated message - case class WeakBlockInfo(version: Byte, weakBlock: Header, prevWeakBlockId: Array[Byte]) + case class SubBlockInfo(version: Byte, subBlock: Header, prevWeakBlockId: Array[Byte]) - object WeakBlockInfo { + object SubBlockInfo { val initialMessageVersion = 1 /** * Serializer which can convert self to bytes */ - def serializer: ErgoSerializer[WeakBlockInfo] = new ErgoSerializer[WeakBlockInfo] { - override def serialize(wbi: WeakBlockInfo, w: Writer): Unit = { - w.put(wbi.version) - HeaderSerializer.serialize(wbi.weakBlock, w) - w.putBytes(wbi.prevWeakBlockId) + def serializer: ErgoSerializer[SubBlockInfo] = new ErgoSerializer[SubBlockInfo] { + override def serialize(sbi: SubBlockInfo, w: Writer): Unit = { + w.put(sbi.version) + HeaderSerializer.serialize(sbi.subBlock, w) + w.putBytes(sbi.prevWeakBlockId) } - override def parse(r: Reader): WeakBlockInfo = { + override def parse(r: Reader): SubBlockInfo = { val version = r.getByte() if (version == initialMessageVersion) { val weakBlock = HeaderSerializer.parse(r) val prevWeakBlockId = r.getBytes(32) - new WeakBlockInfo(version, weakBlock, prevWeakBlockId) + new SubBlockInfo(version, weakBlock, prevWeakBlockId) } else { throw new Exception("Unsupported weakblock message version") } @@ -77,19 +77,19 @@ object WeakBlockAlgos { * Message that is informing about weak block produced. * Contains header and link to previous weak block (). */ - object WeakBlockMessageSpec extends MessageSpecV1[WeakBlockInfo] { + object SubBlockMessageSpec extends MessageSpecV1[SubBlockInfo] { val MaxMessageSize = 10000 override val messageCode: MessageCode = 90: Byte override val messageName: String = "WeakBlock" - override def serialize(data: WeakBlockInfo, w: Writer): Unit = { - WeakBlockInfo.serializer.serialize(data, w) + override def serialize(data: SubBlockInfo, w: Writer): Unit = { + SubBlockInfo.serializer.serialize(data, w) } - override def parse(r: Reader): WeakBlockInfo = { - WeakBlockInfo.serializer.parse(r) + override def parse(r: Reader): SubBlockInfo = { + SubBlockInfo.serializer.parse(r) } } @@ -150,32 +150,33 @@ object WeakBlockAlgos { object structures { var lastBlock: Header = null - val weakBlocks: mutable.Set[ModifierId] = mutable.Set.empty + val subBlocks: mutable.Set[ModifierId] = mutable.Set.empty - val weakBlockLinks: mutable.Map[ModifierId, ModifierId] = mutable.Map.empty + val subBlockLinks: mutable.Map[ModifierId, ModifierId] = mutable.Map.empty - var weakBlockTxs: Map[ModifierId, Array[Array[Byte]]] = Map.empty + var subBlockTxs: Map[ModifierId, Array[Array[Byte]]] = Map.empty - def processWeakBlock(wbi: WeakBlockInfo) = { - val wbHeader = wbi.weakBlock - val prevWbId = bytesToId(wbi.prevWeakBlockId) - val wbHeight = wbHeader.height + // A primer algo on processing + def processWeakBlock(sbi: SubBlockInfo) = { + val sbHeader = sbi.subBlock + val prevWbId = bytesToId(sbi.prevWeakBlockId) + val sbHeight = sbHeader.height - if(wbHeader.id == prevWbId){ + if(sbHeader.id == prevWbId){ ??? // todo: throw error } - if (wbHeight < lastBlock.height + 1) { + if (sbHeight < lastBlock.height + 1) { // just ignore as we have better block already - } else if (wbHeight == lastBlock.height + 1) { - if (wbHeader.parentId == lastBlock.id) { - val weakBlockId = wbHeader.id - if(weakBlocks.contains(weakBlockId)){ + } else if (sbHeight == lastBlock.height + 1) { + if (sbHeader.parentId == lastBlock.id) { + val weakBlockId = sbHeader.id + if(subBlocks.contains(weakBlockId)){ // todo: what to do? } else { - weakBlocks += weakBlockId - if (weakBlocks.contains(prevWbId)){ - weakBlockLinks.put(weakBlockId, prevWbId) + subBlocks += weakBlockId + if (subBlocks.contains(prevWbId)){ + subBlockLinks.put(weakBlockId, prevWbId) } else { //todo: download prev weak block id } @@ -192,9 +193,9 @@ object structures { def processBlock(header: Header) = { if (header.height > lastBlock.height) { lastBlock = header - weakBlocks.clear() - weakBlockLinks.clear() - weakBlockTxs = Map.empty + subBlocks.clear() + subBlockLinks.clear() + subBlockTxs = Map.empty } else { ??? // todo: process } From 636f3d87966b29177a3ab8815f79a9a21c94f2c1 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 8 Nov 2023 00:01:47 +0300 Subject: [PATCH 009/109] more weak=>sub renaming --- ...akBlockAlgos.scala => SubBlockAlgos.scala} | 83 ++++++++++--------- .../modifiers/mempool/ErgoTransaction.scala | 4 +- 2 files changed, 45 insertions(+), 42 deletions(-) rename src/main/scala/org/ergoplatform/modifiers/history/header/{WeakBlockAlgos.scala => SubBlockAlgos.scala} (66%) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala similarity index 66% rename from src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala rename to src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala index 5c95c37540..c653b059be 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/WeakBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala @@ -1,6 +1,6 @@ package org.ergoplatform.modifiers.history.header -import org.ergoplatform.modifiers.history.header.WeakBlockAlgos.SubBlockInfo +import org.ergoplatform.modifiers.history.header.SubBlockAlgos.SubBlockInfo import org.ergoplatform.nodeView.history.ErgoHistory.Difficulty import scorex.core.{NodeViewModifier, bytesToId, idToBytes} import scorex.core.network.message.Message.MessageCode @@ -14,22 +14,22 @@ import scala.collection.mutable /** * Implementation steps: - * * implement basic weak block algorithms (isweak etc) - * * implement weak block network message - * * implement weak block info support in sync tracker - * * implement downloading weak blocks chain + * * implement basic sub block algorithms (isSub etc) + * * implement sub block network message + * * implement sub block info support in sync tracker + * * implement downloading sub blocks chain * * implement avoiding downloading full-blocks - * * weak blocks support in /mining API - * * weak confirmations API + * * sub blocks support in /mining API + * * sub confirmations API */ -object WeakBlockAlgos { +object SubBlockAlgos { - val weaksPerBlock = 128 // weak blocks per block + val subsPerBlock = 128 // sub blocks per block val weakTransactionIdLength = 6 - def isWeak(header: Header, requiredDifficulty: Difficulty): Boolean = { - val diff = requiredDifficulty / weaksPerBlock + def isSub(header: Header, requiredDifficulty: Difficulty): Boolean = { + val diff = requiredDifficulty / subsPerBlock header.requiredDifficulty >= diff } @@ -37,14 +37,14 @@ object WeakBlockAlgos { // messages: // - // weak block signal: + // sub block signal: // version - // weak block (~200 bytes) - contains a link to parent block - // previous weak block id - // transactions since last weak blocks (8 byte ids?) + // sub block (~200 bytes) - contains a link to parent block + // previous sub block id + // transactions since last sub blocks (8 byte ids?) - // todo: move `txsSinceLastWeak` to a dedicated message - case class SubBlockInfo(version: Byte, subBlock: Header, prevWeakBlockId: Array[Byte]) + // todo: move `txsSinceLastSub` to a dedicated message + case class SubBlockInfo(version: Byte, subBlock: Header, prevSubBlockId: Array[Byte]) object SubBlockInfo { @@ -57,32 +57,32 @@ object WeakBlockAlgos { override def serialize(sbi: SubBlockInfo, w: Writer): Unit = { w.put(sbi.version) HeaderSerializer.serialize(sbi.subBlock, w) - w.putBytes(sbi.prevWeakBlockId) + w.putBytes(sbi.prevSubBlockId) } override def parse(r: Reader): SubBlockInfo = { val version = r.getByte() if (version == initialMessageVersion) { - val weakBlock = HeaderSerializer.parse(r) - val prevWeakBlockId = r.getBytes(32) - new SubBlockInfo(version, weakBlock, prevWeakBlockId) + val subBlock = HeaderSerializer.parse(r) + val prevSubBlockId = r.getBytes(32) + new SubBlockInfo(version, subBlock, prevSubBlockId) } else { - throw new Exception("Unsupported weakblock message version") + throw new Exception("Unsupported sub-block message version") } } } } /** - * Message that is informing about weak block produced. - * Contains header and link to previous weak block (). + * Message that is informing about sub block produced. + * Contains header and link to previous sub block (). */ object SubBlockMessageSpec extends MessageSpecV1[SubBlockInfo] { val MaxMessageSize = 10000 override val messageCode: MessageCode = 90: Byte - override val messageName: String = "WeakBlock" + override val messageName: String = "SubBlock" override def serialize(data: SubBlockInfo, w: Writer): Unit = { SubBlockInfo.serializer.serialize(data, w) @@ -94,7 +94,7 @@ object WeakBlockAlgos { } /** - * On receiving weak block or block, the node is sending last weak block or block id it has to get short transaction + * On receiving sub block or block, the node is sending last sub block or block id it has to get short transaction * ids since then */ object GetDataSpec extends MessageSpecV1[ModifierId] { @@ -156,37 +156,40 @@ object structures { var subBlockTxs: Map[ModifierId, Array[Array[Byte]]] = Map.empty - // A primer algo on processing - def processWeakBlock(sbi: SubBlockInfo) = { + case class DownloadPlan() + + // A primer algo on processing sub-blocks + + def processSubBlock(sbi: SubBlockInfo) = { val sbHeader = sbi.subBlock - val prevWbId = bytesToId(sbi.prevWeakBlockId) + val prevSbId = bytesToId(sbi.prevSubBlockId) val sbHeight = sbHeader.height - if(sbHeader.id == prevWbId){ - ??? // todo: throw error + if(sbHeader.id == prevSbId){ + ??? // todo: malicious prev throw error } if (sbHeight < lastBlock.height + 1) { // just ignore as we have better block already } else if (sbHeight == lastBlock.height + 1) { if (sbHeader.parentId == lastBlock.id) { - val weakBlockId = sbHeader.id - if(subBlocks.contains(weakBlockId)){ + val subBlockId = sbHeader.id + if(subBlocks.contains(subBlockId)){ // todo: what to do? } else { - subBlocks += weakBlockId - if (subBlocks.contains(prevWbId)){ - subBlockLinks.put(weakBlockId, prevWbId) + subBlocks += subBlockId + if (subBlocks.contains(prevSbId)){ + subBlockLinks.put(subBlockId, prevSbId) } else { - //todo: download prev weak block id + //todo: download prev sub block id } - // todo: download weak block related txs + // todo: download sub block related txs } } else { - // todo: we got orphaned block's weak block, process this + // todo: we got orphaned block's sub block, process this } } else { - // just ignoring weak block coming from future for now + // just ignoring sub block coming from future for now } } diff --git a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala index 9900d5e4f0..91ffa55183 100644 --- a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala +++ b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala @@ -6,7 +6,7 @@ import org.ergoplatform.SigmaConstants.{MaxBoxSize, MaxPropositionBytes} import org.ergoplatform._ import org.ergoplatform.http.api.ApiCodecs import org.ergoplatform.mining.emission.EmissionRules -import org.ergoplatform.modifiers.history.header.{Header, WeakBlockAlgos} +import org.ergoplatform.modifiers.history.header.{Header, SubBlockAlgos} import org.ergoplatform.modifiers.mempool.ErgoTransaction.unresolvedIndices import org.ergoplatform.modifiers.{ErgoNodeViewModifier, NetworkObjectTypeId, TransactionTypeId} import org.ergoplatform.nodeView.ErgoContext @@ -68,7 +68,7 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input], override lazy val id: ModifierId = bytesToId(serializedId) - lazy val weakId = id.take(WeakBlockAlgos.weakTransactionIdLength) + lazy val weakId = id.take(SubBlockAlgos.weakTransactionIdLength) /** * Id of transaction "witness" (taken from Bitcoin jargon, means commitment to signatures of a transaction). From 62934214a14d37476febae37835b431bc1dd2482 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 8 Nov 2023 01:23:18 +0300 Subject: [PATCH 010/109] returning download plan --- .../modifiers/history/header/SubBlockAlgos.scala | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala index c653b059be..9945f4510a 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala @@ -156,40 +156,51 @@ object structures { var subBlockTxs: Map[ModifierId, Array[Array[Byte]]] = Map.empty - case class DownloadPlan() // A primer algo on processing sub-blocks - def processSubBlock(sbi: SubBlockInfo) = { + /** + * @param sbi + * @return - sub-block ids to download, sub-block transactions to download + */ + def processSubBlock(sbi: SubBlockInfo): (Seq[ModifierId], Seq[ModifierId]) = { val sbHeader = sbi.subBlock val prevSbId = bytesToId(sbi.prevSubBlockId) val sbHeight = sbHeader.height + def emptyResult: (Seq[ModifierId], Seq[ModifierId]) = Seq.empty -> Seq.empty + if(sbHeader.id == prevSbId){ ??? // todo: malicious prev throw error } if (sbHeight < lastBlock.height + 1) { // just ignore as we have better block already + emptyResult } else if (sbHeight == lastBlock.height + 1) { if (sbHeader.parentId == lastBlock.id) { val subBlockId = sbHeader.id if(subBlocks.contains(subBlockId)){ // todo: what to do? + emptyResult } else { subBlocks += subBlockId if (subBlocks.contains(prevSbId)){ subBlockLinks.put(subBlockId, prevSbId) + (Seq.empty, Seq(sbHeader.id)) } else { //todo: download prev sub block id + (Seq(prevSbId), Seq(sbHeader.id)) } // todo: download sub block related txs } } else { // todo: we got orphaned block's sub block, process this + emptyResult } } else { // just ignoring sub block coming from future for now + emptyResult } } From 23a5db0c2535b70cf143fc29eac82d7bbfc0d521 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 10 Nov 2023 00:43:15 +0300 Subject: [PATCH 011/109] better comments and todos, formatting --- .../history/header/SubBlockAlgos.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala index 9945f4510a..d262fb7336 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala @@ -148,7 +148,7 @@ object SubBlockAlgos { } object structures { - var lastBlock: Header = null + var lastBlock: Header = null // we ignore forks for now val subBlocks: mutable.Set[ModifierId] = mutable.Set.empty @@ -157,9 +157,10 @@ object structures { var subBlockTxs: Map[ModifierId, Array[Array[Byte]]] = Map.empty - // A primer algo on processing sub-blocks - /** + * A primer algo on processing sub-blocks in p2p layer. It is updating internal sub-block related + * caches and decides what to download next + * * @param sbi * @return - sub-block ids to download, sub-block transactions to download */ @@ -170,7 +171,7 @@ object structures { def emptyResult: (Seq[ModifierId], Seq[ModifierId]) = Seq.empty -> Seq.empty - if(sbHeader.id == prevSbId){ + if (sbHeader.id == prevSbId) { ??? // todo: malicious prev throw error } @@ -180,12 +181,13 @@ object structures { } else if (sbHeight == lastBlock.height + 1) { if (sbHeader.parentId == lastBlock.id) { val subBlockId = sbHeader.id - if(subBlocks.contains(subBlockId)){ - // todo: what to do? + if (subBlocks.contains(subBlockId)) { + // we got sub-block we already have + // todo: check if previous sub-block and transactions are downloaded emptyResult } else { subBlocks += subBlockId - if (subBlocks.contains(prevSbId)){ + if (subBlocks.contains(prevSbId)) { subBlockLinks.put(subBlockId, prevSbId) (Seq.empty, Seq(sbHeader.id)) } else { @@ -195,7 +197,7 @@ object structures { // todo: download sub block related txs } } else { - // todo: we got orphaned block's sub block, process this + // todo: we got orphaned block's sub block, do nothing for now, but we need to check the block is downloaded emptyResult } } else { From 2c5cf3c04cbb900e01e23859dd3e214d15382294 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 10 Nov 2023 18:50:14 +0300 Subject: [PATCH 012/109] motivation started in EIP, data structures refined --- papers/propagation.md | 14 ++++++++++++-- .../modifiers/history/header/SubBlockAlgos.scala | 14 +++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/papers/propagation.md b/papers/propagation.md index 1320aafd60..10c115679e 100644 --- a/papers/propagation.md +++ b/papers/propagation.md @@ -1,8 +1,18 @@ -Improved Block Propagation +Sub-Blocks and Improved Confirmed Transactions Propagation ========================== * Author: kushti * Status: Proposed * Created: 31-Oct-2023 * License: CC0 -* Forking: Soft Fork \ No newline at end of file +* Forking: Soft Fork + +Motivation +---------- + +Currently, a block is generated every two minutes on average, and confirmed transactions are propagated along with +other block sections. + +This is not efficient at all. Most of new block's transactions are already available in a node's mempool, and +bottlenecking network bandwidth after two minutes of delay is also downgrading network performance. + diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala index d262fb7336..2de149ecc7 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala @@ -150,10 +150,13 @@ object SubBlockAlgos { object structures { var lastBlock: Header = null // we ignore forks for now - val subBlocks: mutable.Set[ModifierId] = mutable.Set.empty + // all the sub-blocks known since the last block + val subBlocks: mutable.Map[ModifierId, Header] = mutable.Map.empty + // links from sub-blocks to their parent sub-blocks val subBlockLinks: mutable.Map[ModifierId, ModifierId] = mutable.Map.empty + // only new transactions appeared in a sub-block var subBlockTxs: Map[ModifierId, Array[Array[Byte]]] = Map.empty @@ -186,10 +189,15 @@ object structures { // todo: check if previous sub-block and transactions are downloaded emptyResult } else { - subBlocks += subBlockId + subBlocks += subBlockId -> sbHeader if (subBlocks.contains(prevSbId)) { + val prevSb = subBlocks(prevSbId) subBlockLinks.put(subBlockId, prevSbId) - (Seq.empty, Seq(sbHeader.id)) + if(prevSb.transactionsRoot != sbHeader.transactionsRoot) { + (Seq.empty, Seq(sbHeader.id)) + } else { + emptyResult // no new transactions + } } else { //todo: download prev sub block id (Seq(prevSbId), Seq(sbHeader.id)) From 49622180d753222824bdb59f80bb8d9aeec03d91 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 1 Dec 2023 23:55:55 +0300 Subject: [PATCH 013/109] pow, superblocks --- papers/propagation.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/papers/propagation.md b/papers/propagation.md index 10c115679e..ec388419d8 100644 --- a/papers/propagation.md +++ b/papers/propagation.md @@ -16,3 +16,22 @@ other block sections. This is not efficient at all. Most of new block's transactions are already available in a node's mempool, and bottlenecking network bandwidth after two minutes of delay is also downgrading network performance. +Also, while average block delay in Ergo is 2 minutes, variance is high, and often a user may wait 10 minutes for +first confirmation. + +Sub-Blocks +---------- + +A valid block is sequence of (semantically valid) header fields (and corresponding valid block sections, such as block +transactions), including special field to iterate over called nonce, such as *H(b) < T*, where *H()* is Autolykos Proof-of-Work +function, *b* are block bytes (including nonce), and *T* is a Proof-of-Work *target* value. A value which is reverse +to target is called difficulty *D*: *D = 2^256 / T* (in fact, slightly less value than 2^256 is taken, namely, order of +secp256k1 curve group, heritage of initial Autolykos 1 Proof-of-Work algorithm). *D* (and so *T*) is being readjusted +regularly via a deterministic procedure (called difficulty readjustment algorithm) to have blocks coming every two minutes on average. + +Aside of blocks, *superblocks" are also used in the Ergo protocol, for building NiPoPoWs on top of them. A superblock is +a block which is more difficult to find than an ordinary, for example, for a (level-1) superblock *S* we may require +*H(b) < T/2*, and in general, we can call n-level superblock a block *S* for which *H(b) < T/2^n*. Please note that a +superblock is also a valid block (every superblock is passing block PoW test). + +Similarly, we can go in opposite direction and use *subblocks* From 514e6fb9df84bdc9ad1012487d730c8db38f100b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 2 Dec 2023 23:06:47 +0300 Subject: [PATCH 014/109] subblocks section finished --- papers/propagation.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/papers/propagation.md b/papers/propagation.md index ec388419d8..545a96f0ad 100644 --- a/papers/propagation.md +++ b/papers/propagation.md @@ -17,7 +17,9 @@ This is not efficient at all. Most of new block's transactions are already avail bottlenecking network bandwidth after two minutes of delay is also downgrading network performance. Also, while average block delay in Ergo is 2 minutes, variance is high, and often a user may wait 10 minutes for -first confirmation. +first confirmation. Proposals to lower variance are introducing experimental and controversial changes in consensus protocol. +Changing block delay via hardfork would have a lot of harsh consequences (e.g. many contracts relying on current block +delay would be broken). Thus it makes sense to consider weaker notions of confirmation. Sub-Blocks ---------- @@ -31,7 +33,13 @@ regularly via a deterministic procedure (called difficulty readjustment algorith Aside of blocks, *superblocks" are also used in the Ergo protocol, for building NiPoPoWs on top of them. A superblock is a block which is more difficult to find than an ordinary, for example, for a (level-1) superblock *S* we may require -*H(b) < T/2*, and in general, we can call n-level superblock a block *S* for which *H(b) < T/2^n*. Please note that a +*H(S) < T/2*, and in general, we can call n-level superblock a block *S* for which *H(S) < T/2^n*. Please note that a superblock is also a valid block (every superblock is passing block PoW test). -Similarly, we can go in opposite direction and use *subblocks* +Similarly, we can go in opposite direction and use *subblocks*, so blocks with lower difficulty. We can set *t = T/64* +and define superblock *s* as *H(s) < t*, then miner can generate on average 64 subblocks (including normal block itself) +per block generation period. Please note that, unlike superblocks, subblocks are not blocks, but a block is passing +subblock check. + +Subblocks are similar to block shares already used in pooled mining. Rather, this proposal is considering to use +sub-blocks for improving transactions propagation and providing a framework for weaker confirmations. \ No newline at end of file From 564ad05790fd6fb4934aa5d2bba7a59c3cbcfc6f Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 6 Dec 2023 15:28:48 +0300 Subject: [PATCH 015/109] propagation text started, prevSubBlockId made optional (comp fails) --- papers/{propagation.md => subblocks.md} | 14 +++++++++++++- .../modifiers/history/header/SubBlockAlgos.scala | 10 ++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) rename papers/{propagation.md => subblocks.md} (79%) diff --git a/papers/propagation.md b/papers/subblocks.md similarity index 79% rename from papers/propagation.md rename to papers/subblocks.md index 545a96f0ad..9b6a29e41d 100644 --- a/papers/propagation.md +++ b/papers/subblocks.md @@ -42,4 +42,16 @@ per block generation period. Please note that, unlike superblocks, subblocks are subblock check. Subblocks are similar to block shares already used in pooled mining. Rather, this proposal is considering to use -sub-blocks for improving transactions propagation and providing a framework for weaker confirmations. \ No newline at end of file +sub-blocks for improving transactions propagation and providing a framework for weaker confirmations. + +Sub-Blocks And Transactions Propagation +--------------------------------------- + +Let's consider that new block is just generated. Miners A and B (among others) are working on a new block. Users are +submitting new unconfirmed transactions at the same time to the p2p network, and eventually they are reaching miners +(including A and B, but at a given time a transaction could be in one of the mempools just, not necessarily both, it +could also be somewhere else and not known to both A and B). + +Then, for example, miner A is generating a sub-block committing to new transactions after last block. It sends sub-block +header as well as weak transaction ids (6 bytes hashes) to peers. + diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala index 2de149ecc7..8beb9e74f5 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala @@ -43,8 +43,14 @@ object SubBlockAlgos { // previous sub block id // transactions since last sub blocks (8 byte ids?) - // todo: move `txsSinceLastSub` to a dedicated message - case class SubBlockInfo(version: Byte, subBlock: Header, prevSubBlockId: Array[Byte]) + /** + * Sub-block message, sent by the node to peers when a sub-block is generated + * @param version - message version (to allow injecting new fields) + * @param subBlock - subblock + * @param prevSubBlockId - previous sub block id `subBlock` is following, if missed, sub-block is linked + * to a previous block + */ + case class SubBlockInfo(version: Byte, subBlock: Header, prevSubBlockId: Option[Array[Byte]]) object SubBlockInfo { From 346e1a782e6ddef26cd881d22a9f27bb747f2bd5 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 21 Dec 2023 09:29:17 +0300 Subject: [PATCH 016/109] commitment --- papers/subblocks.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/papers/subblocks.md b/papers/subblocks.md index 9b6a29e41d..9d1e39c3a1 100644 --- a/papers/subblocks.md +++ b/papers/subblocks.md @@ -55,3 +55,13 @@ could also be somewhere else and not known to both A and B). Then, for example, miner A is generating a sub-block committing to new transactions after last block. It sends sub-block header as well as weak transaction ids (6 bytes hashes) to peers. +Commitment to Sub-Blocks +------------------------ + +Here we consider what kind of footprint sub-blocks would have in consensus-enforced data structures (i.e. on-chain). +Proper balance here is critical and hard to achieve. Strict consensus-enforced commitments (when all the +sub-blocks committed on-chain) require from all the miners to have all the sub-blocks in order to check them. But, +at the same time, consensus-enforced commitments to properly ordered sub-blocks would allow for protocols and +applications using sub-blocks data. + +We have chosen weak commitments. That is, a miner \ No newline at end of file From a0685458d28843cd160823a9a3101a134285deab Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 28 Dec 2023 23:32:47 +0300 Subject: [PATCH 017/109] styling fixes --- papers/subblocks.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/papers/subblocks.md b/papers/subblocks.md index 9d1e39c3a1..a2463f9f75 100644 --- a/papers/subblocks.md +++ b/papers/subblocks.md @@ -14,12 +14,13 @@ Currently, a block is generated every two minutes on average, and confirmed tran other block sections. This is not efficient at all. Most of new block's transactions are already available in a node's mempool, and -bottlenecking network bandwidth after two minutes of delay is also downgrading network performance. +bottlenecking network bandwidth after two minutes of (more or less) idle state is also downgrading network performance. Also, while average block delay in Ergo is 2 minutes, variance is high, and often a user may wait 10 minutes for first confirmation. Proposals to lower variance are introducing experimental and controversial changes in consensus protocol. Changing block delay via hardfork would have a lot of harsh consequences (e.g. many contracts relying on current block -delay would be broken). Thus it makes sense to consider weaker notions of confirmation. +delay would be broken). Thus it makes sense to consider weaker notions of confirmation which still could be useful for +a variety of applications. Sub-Blocks ---------- @@ -64,4 +65,4 @@ sub-blocks committed on-chain) require from all the miners to have all the sub-b at the same time, consensus-enforced commitments to properly ordered sub-blocks would allow for protocols and applications using sub-blocks data. -We have chosen weak commitments. That is, a miner \ No newline at end of file +We have chosen weak commitments. That is, a miner may (and incentivized to) \ No newline at end of file From c0709f2820c50ef627c84d8ec070f9bbb033667b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 29 Dec 2023 16:19:45 +0300 Subject: [PATCH 018/109] key spaces for sub-blocks and sidechains --- papers/subblocks.md | 32 +++++++++++++++++-- .../history/extension/Extension.scala | 10 ++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/papers/subblocks.md b/papers/subblocks.md index a2463f9f75..e5f8783c51 100644 --- a/papers/subblocks.md +++ b/papers/subblocks.md @@ -56,8 +56,8 @@ could also be somewhere else and not known to both A and B). Then, for example, miner A is generating a sub-block committing to new transactions after last block. It sends sub-block header as well as weak transaction ids (6 bytes hashes) to peers. -Commitment to Sub-Blocks ------------------------- +Sub-blocks Structure and Commitment to Sub-Blocks +------------------------------------------------- Here we consider what kind of footprint sub-blocks would have in consensus-enforced data structures (i.e. on-chain). Proper balance here is critical and hard to achieve. Strict consensus-enforced commitments (when all the @@ -65,4 +65,30 @@ sub-blocks committed on-chain) require from all the miners to have all the sub-b at the same time, consensus-enforced commitments to properly ordered sub-blocks would allow for protocols and applications using sub-blocks data. -We have chosen weak commitments. That is, a miner may (and incentivized to) \ No newline at end of file +We have chosen weak commitments. That is, a miner may (and incentivized to) to commit to longest sub-blocks chain +since previous full-block, but that there are no any requirements about that in Ergo consensus rules. + +New extension key space starting with 0x03 will be used for sub-blocks related data, with one key used per this EIP: + +0x03 0x00 - digest of a Merkle tree of longest sub-blocks chain starting with previous block (but not including it). + +So first sub-block having full-block as a parent will have empty tree, next one will have only first, and next +full-block will commit to all the sub-blocks since previous full-block. + + + +Weak confirmations +------------------ + + + +Sub-Block Based Sidechains +-------------------------- + +As L1 incentivization for propagating and committing on-chain to sub-blocks are missed, we consider sidechains as +possible option to incentivize miners to participate in the sub-blocks sub-protocol. + + + +Incentivization +--------------- diff --git a/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala b/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala index c402d88342..fe64e31c9f 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala +++ b/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala @@ -69,6 +69,16 @@ object Extension extends ApiCodecs { */ val ValidationRulesPrefix: Byte = 0x02 + /** + * Prefix for keys related to sub-blocks related data. + */ + val SubBlocksDataPrefix: Byte = 0x03 + + /** + * Prefix for keys related to sidechains data. + */ + val SidechainsDataPrefix: Byte = 0x04 + /** * Id a type of network object encoding extension */ From 85de7a4c8cefd53b2da268853344a874da8b0e10 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 3 Jan 2024 16:06:33 +0300 Subject: [PATCH 019/109] eip update --- papers/subblocks.md | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/papers/subblocks.md b/papers/subblocks.md index e5f8783c51..d0fd3d8e92 100644 --- a/papers/subblocks.md +++ b/papers/subblocks.md @@ -26,10 +26,10 @@ Sub-Blocks ---------- A valid block is sequence of (semantically valid) header fields (and corresponding valid block sections, such as block -transactions), including special field to iterate over called nonce, such as *H(b) < T*, where *H()* is Autolykos Proof-of-Work +transactions), including special field to iterate over, called nonce, such as *H(b) < T*, where *H()* is Autolykos Proof-of-Work function, *b* are block bytes (including nonce), and *T* is a Proof-of-Work *target* value. A value which is reverse to target is called difficulty *D*: *D = 2^256 / T* (in fact, slightly less value than 2^256 is taken, namely, order of -secp256k1 curve group, heritage of initial Autolykos 1 Proof-of-Work algorithm). *D* (and so *T*) is being readjusted +secp256k1 curve group, this is inherited from initial Autolykos 1 Proof-of-Work algorithm). *D* (and so *T*) is being readjusted regularly via a deterministic procedure (called difficulty readjustment algorithm) to have blocks coming every two minutes on average. Aside of blocks, *superblocks" are also used in the Ergo protocol, for building NiPoPoWs on top of them. A superblock is @@ -54,7 +54,14 @@ submitting new unconfirmed transactions at the same time to the p2p network, and could also be somewhere else and not known to both A and B). Then, for example, miner A is generating a sub-block committing to new transactions after last block. It sends sub-block -header as well as weak transaction ids (6 bytes hashes) to peers. +header as well as weak transaction ids (6 bytes hashes) of transactions included into this sub-block but not previous +sub-blocks to peers. Peers then are asking for transactions they do not know only, and if previous sub-block is not +known, they are downloading it along with its transactions delta, and go further recursively if needed. + +Thus pulse of sub-blocks will allow to exchange transactions quickly. And when a new sub-block is also a block (passing +normal difficulty check), not many transactions to download, normally. Thus instead of exchanging all the full-block +transactions when a new block comes, peers will exchange relatively small transaction deltas all the time. Full-block +transactions sections exchange still will be supported, to support downloading historical blocks, and also old clients. Sub-blocks Structure and Commitment to Sub-Blocks ------------------------------------------------- @@ -75,20 +82,27 @@ New extension key space starting with 0x03 will be used for sub-blocks related d So first sub-block having full-block as a parent will have empty tree, next one will have only first, and next full-block will commit to all the sub-blocks since previous full-block. - - -Weak confirmations ------------------- - +At the same time, no any new checks are planned for the Ergo protocol. Checks are possible for sidechains. Sub-Block Based Sidechains -------------------------- -As L1 incentivization for propagating and committing on-chain to sub-blocks are missed, we consider sidechains as -possible option to incentivize miners to participate in the sub-blocks sub-protocol. +As L1 incentivization for propagating and committing on-chain to sub-blocks are missed, we consider sub-block based +merge-mined sidechains as possible option to incentivize miners to participate in the sub-blocks sub-protocol. They +also can be used to enforce linearity (so that transactions added in a previous sub-block can't be reversed). +A merged-mined sidechain is using sub-blocks as well as blocks to update its state which can be committed via main-chain +transactions even. That is, in every sub-blocks side-chain state (sidechain UTXO set digest etc) can be written in a box +with sidechain NFT, and then every sub-block the box may be updated. +For rewarding miners submitting sub-blocks to Ergo network (sidechain block generators are listening to), a sidechain block +may be consist of main-chain sub-block and sidechain state along with membership proof. For enforcing linearity of transactions +, sidechain consensus may enforce rollback to a sub-block before transaction reversal on proof of reversal being published. Incentivization --------------- + +No incentives to generate and propagate sub-blocks are planned for the Ergo core protocols at the moment. At the same +time, incentives can be provided on the sub-block based merge-mined sidechains, or via application-specific agreements +(where applications may pay to miners for faster confirmations). \ No newline at end of file From a248ccfe49f45e2e88dbd23312a7e811eb4ef58a Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 5 Jan 2024 01:22:04 +0300 Subject: [PATCH 020/109] dag note, new subsections added --- papers/subblocks.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/papers/subblocks.md b/papers/subblocks.md index d0fd3d8e92..7f12ce65cf 100644 --- a/papers/subblocks.md +++ b/papers/subblocks.md @@ -82,6 +82,9 @@ New extension key space starting with 0x03 will be used for sub-blocks related d So first sub-block having full-block as a parent will have empty tree, next one will have only first, and next full-block will commit to all the sub-blocks since previous full-block. +Note that sub-blocks (like blocks) are forming direct acyclic graph (DAG), but only longest sub-blocks chain is +committed. + At the same time, no any new checks are planned for the Ergo protocol. Checks are possible for sidechains. @@ -105,4 +108,16 @@ Incentivization No incentives to generate and propagate sub-blocks are planned for the Ergo core protocols at the moment. At the same time, incentives can be provided on the sub-block based merge-mined sidechains, or via application-specific agreements -(where applications may pay to miners for faster confirmations). \ No newline at end of file +(where applications may pay to miners for faster confirmations). + + +Weak Confirmations +------------------ + +With linearity of transactions history in sub-blocks chain, sub-blocks may be used for getting faster confirmations +with weaker security guarantees. + + +Security Considerations and Assumptions +--------------------------------------- + From 93bc6bcfbf681c873467ead2cd6cb0613b239e1e Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 12 Feb 2024 15:27:06 +0300 Subject: [PATCH 021/109] merging w. master, accounting for optional prevSubBlockId --- .../org/ergoplatform}/SubBlockAlgos.scala | 98 ++++++++++--------- .../modifiers/mempool/ErgoTransaction.scala | 4 +- 2 files changed, 54 insertions(+), 48 deletions(-) rename {src/main/scala/org/ergoplatform/modifiers/history/header => ergo-core/src/main/scala/org/ergoplatform}/SubBlockAlgos.scala (69%) diff --git a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala similarity index 69% rename from src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala rename to ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index 8beb9e74f5..b1ad71e127 100644 --- a/src/main/scala/org/ergoplatform/modifiers/history/header/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -1,14 +1,15 @@ -package org.ergoplatform.modifiers.history.header - -import org.ergoplatform.modifiers.history.header.SubBlockAlgos.SubBlockInfo -import org.ergoplatform.nodeView.history.ErgoHistory.Difficulty -import scorex.core.{NodeViewModifier, bytesToId, idToBytes} -import scorex.core.network.message.Message.MessageCode -import scorex.core.network.message.MessageSpecV1 -import scorex.core.serialization.ErgoSerializer -import scorex.util.serialization.{Reader, Writer} +package org.ergoplatform + +import org.ergoplatform.SubBlockAlgos.SubBlockInfo +import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} +import org.ergoplatform.network.message.MessageConstants.MessageCode +import org.ergoplatform.network.message.MessageSpecV1 +import org.ergoplatform.nodeView.history.ErgoHistoryUtils.Difficulty +import org.ergoplatform.serialization.ErgoSerializer +import org.ergoplatform.settings.Constants import scorex.util.Extensions._ -import scorex.util.ModifierId +import scorex.util.serialization.{Reader, Writer} +import scorex.util.{ModifierId, bytesToId, idToBytes} import scala.collection.mutable @@ -63,14 +64,14 @@ object SubBlockAlgos { override def serialize(sbi: SubBlockInfo, w: Writer): Unit = { w.put(sbi.version) HeaderSerializer.serialize(sbi.subBlock, w) - w.putBytes(sbi.prevSubBlockId) + w.putOption(sbi.prevSubBlockId){case (w, id) => w.putBytes(id)} } override def parse(r: Reader): SubBlockInfo = { val version = r.getByte() if (version == initialMessageVersion) { val subBlock = HeaderSerializer.parse(r) - val prevSubBlockId = r.getBytes(32) + val prevSubBlockId = r.getOption(r.getBytes(Constants.ModifierIdSize)) new SubBlockInfo(version, subBlock, prevSubBlockId) } else { throw new Exception("Unsupported sub-block message version") @@ -105,7 +106,7 @@ object SubBlockAlgos { */ object GetDataSpec extends MessageSpecV1[ModifierId] { - import scorex.util.{idToBytes, bytesToId} + import scorex.util.{bytesToId, idToBytes} override val messageCode: MessageCode = 91: Byte override val messageName: String = "GetData" @@ -115,7 +116,7 @@ object SubBlockAlgos { } override def parse(r: Reader): ModifierId = { - bytesToId(r.getBytes(NodeViewModifier.ModifierIdSize)) + bytesToId(r.getBytes(Constants.ModifierIdSize)) } } @@ -175,52 +176,57 @@ object structures { */ def processSubBlock(sbi: SubBlockInfo): (Seq[ModifierId], Seq[ModifierId]) = { val sbHeader = sbi.subBlock - val prevSbId = bytesToId(sbi.prevSubBlockId) + val prevSbIdOpt = sbi.prevSubBlockId.map(bytesToId) val sbHeight = sbHeader.height def emptyResult: (Seq[ModifierId], Seq[ModifierId]) = Seq.empty -> Seq.empty - if (sbHeader.id == prevSbId) { - ??? // todo: malicious prev throw error - } + prevSbIdOpt match { + case None => ??? // todo: link to prev block + + case Some(prevSbId) => + if (sbHeader.id == prevSbId) { + ??? // todo: malicious prev throw error + } - if (sbHeight < lastBlock.height + 1) { - // just ignore as we have better block already - emptyResult - } else if (sbHeight == lastBlock.height + 1) { - if (sbHeader.parentId == lastBlock.id) { - val subBlockId = sbHeader.id - if (subBlocks.contains(subBlockId)) { - // we got sub-block we already have - // todo: check if previous sub-block and transactions are downloaded + if (sbHeight < lastBlock.height + 1) { + // just ignore as we have better block already emptyResult - } else { - subBlocks += subBlockId -> sbHeader - if (subBlocks.contains(prevSbId)) { - val prevSb = subBlocks(prevSbId) - subBlockLinks.put(subBlockId, prevSbId) - if(prevSb.transactionsRoot != sbHeader.transactionsRoot) { - (Seq.empty, Seq(sbHeader.id)) + } else if (sbHeight == lastBlock.height + 1) { + if (sbHeader.parentId == lastBlock.id) { + val subBlockId = sbHeader.id + if (subBlocks.contains(subBlockId)) { + // we got sub-block we already have + // todo: check if previous sub-block and transactions are downloaded + emptyResult } else { - emptyResult // no new transactions + subBlocks += subBlockId -> sbHeader + if (subBlocks.contains(prevSbId)) { + val prevSb = subBlocks(prevSbId) + subBlockLinks.put(subBlockId, prevSbId) + if (prevSb.transactionsRoot != sbHeader.transactionsRoot) { + (Seq.empty, Seq(sbHeader.id)) + } else { + emptyResult // no new transactions + } + } else { + //todo: download prev sub block id + (Seq(prevSbId), Seq(sbHeader.id)) + } + // todo: download sub block related txs } } else { - //todo: download prev sub block id - (Seq(prevSbId), Seq(sbHeader.id)) + // todo: we got orphaned block's sub block, do nothing for now, but we need to check the block is downloaded + emptyResult } - // todo: download sub block related txs + } else { + // just ignoring sub block coming from future for now + emptyResult } - } else { - // todo: we got orphaned block's sub block, do nothing for now, but we need to check the block is downloaded - emptyResult - } - } else { - // just ignoring sub block coming from future for now - emptyResult } } - def processBlock(header: Header) = { + def processBlock(header: Header): Unit = { if (header.height > lastBlock.height) { lastBlock = header subBlocks.clear() diff --git a/ergo-core/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala b/ergo-core/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala index fcdbd54267..f245e02f9a 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala @@ -1,12 +1,12 @@ package org.ergoplatform.modifiers.mempool import io.circe.syntax._ -import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, ErgoLikeTransaction, ErgoLikeTransactionSerializer, Input} +import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, ErgoLikeTransaction, ErgoLikeTransactionSerializer, Input, SubBlockAlgos} import org.ergoplatform.ErgoBox.BoxId import org.ergoplatform.SigmaConstants.{MaxBoxSize, MaxPropositionBytes} import org.ergoplatform.http.api.ApiCodecs import org.ergoplatform.mining.emission.EmissionRules -import org.ergoplatform.modifiers.history.header.{Header, SubBlockAlgos} +import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.mempool.ErgoTransaction.unresolvedIndices import org.ergoplatform.modifiers.transaction.Signable import org.ergoplatform.modifiers.{ErgoNodeViewModifier, NetworkObjectTypeId, TransactionTypeId} From 92baed0f982b1cb5393a32b386e1be0752bded68 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 19 Jul 2024 23:16:34 +0300 Subject: [PATCH 022/109] input/ordering blocks intro --- .../org/ergoplatform/SubBlockAlgos.scala | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index b1ad71e127..3026e54652 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -25,9 +25,24 @@ import scala.collection.mutable */ object SubBlockAlgos { - val subsPerBlock = 128 // sub blocks per block + // Only sub-blocks may have transactions, full-blocks may only bring block reward transaction ( designated + // by using emission or re-emission NFTs). + // As a full-block is also a sub-block, and miner does not know output in advance, the following requirements + // for the block are introduced. And to be on par with other proposals in consensus performance, we call them + // input block (sub-block) and ordering block(full-block): + // * ordering block's Merkle tree is corresponding to latest input block's Merkle tree , or latest ordering block's + // Merkle tree if there are no input blocks after previous ordering block, with only reward transaction added + // * every block (input and ordering) also contains digest of new transactions since last input block. For ordering + // block, they are ignored. - val weakTransactionIdLength = 6 + // todo: storage rent collecting? + + // Another option is to use 2-PoW-for 1 technique, so sub-block (input block) is defined not by + // hash(b) < T/subsPerBlock , but by reverse(hash(b)) < T/subsPerBlock , while ordering block is defined + // by hash(b) < T + val subsPerBlock = 128 // sub blocks per block, adjustable via miners voting + + val weakTransactionIdLength = 6 // value taken from Bitcoin's compact blocks BIP def isSub(header: Header, requiredDifficulty: Difficulty): Boolean = { val diff = requiredDifficulty / subsPerBlock @@ -42,7 +57,7 @@ object SubBlockAlgos { // version // sub block (~200 bytes) - contains a link to parent block // previous sub block id - // transactions since last sub blocks (8 byte ids?) + // transactions since last sub blocks /** * Sub-block message, sent by the node to peers when a sub-block is generated From 54c99719eb6397b78fc12bc1e862a774e79f2fcd Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 20 Jul 2024 22:29:30 +0300 Subject: [PATCH 023/109] script execution ctx started --- ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index 3026e54652..f07bc8c7db 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -34,8 +34,7 @@ object SubBlockAlgos { // Merkle tree if there are no input blocks after previous ordering block, with only reward transaction added // * every block (input and ordering) also contains digest of new transactions since last input block. For ordering // block, they are ignored. - - // todo: storage rent collecting? + // * script execution context is the same for input and ordering blocks, aside // todo: mining pubkey? // Another option is to use 2-PoW-for 1 technique, so sub-block (input block) is defined not by // hash(b) < T/subsPerBlock , but by reverse(hash(b)) < T/subsPerBlock , while ordering block is defined From 73d76f27cdb85cd336403ca375c2f058eafaf612 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 25 Jul 2024 02:04:26 +0300 Subject: [PATCH 024/109] extending subblocks info --- .../scala/org/ergoplatform/SubBlockAlgos.scala | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index f07bc8c7db..c05dce94c1 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -1,15 +1,18 @@ package org.ergoplatform import org.ergoplatform.SubBlockAlgos.SubBlockInfo +import org.ergoplatform.modifiers.history.header.Header.{Timestamp, Version} import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} import org.ergoplatform.network.message.MessageConstants.MessageCode import org.ergoplatform.network.message.MessageSpecV1 import org.ergoplatform.nodeView.history.ErgoHistoryUtils.Difficulty import org.ergoplatform.serialization.ErgoSerializer import org.ergoplatform.settings.Constants +import scorex.crypto.hash.Digest32 import scorex.util.Extensions._ import scorex.util.serialization.{Reader, Writer} import scorex.util.{ModifierId, bytesToId, idToBytes} +import sigmastate.crypto.CryptoConstants.EcPointType import scala.collection.mutable @@ -34,7 +37,11 @@ object SubBlockAlgos { // Merkle tree if there are no input blocks after previous ordering block, with only reward transaction added // * every block (input and ordering) also contains digest of new transactions since last input block. For ordering // block, they are ignored. - // * script execution context is the same for input and ordering blocks, aside // todo: mining pubkey? + // * script execution context different for input and ordering blocks for the following fields : + // * timestamp - next input or ordering + // * height - the same for input blocks and next ordering block + // * votes - + // * minerPk - // Another option is to use 2-PoW-for 1 technique, so sub-block (input block) is defined not by // hash(b) < T/subsPerBlock , but by reverse(hash(b)) < T/subsPerBlock , while ordering block is defined @@ -60,12 +67,15 @@ object SubBlockAlgos { /** * Sub-block message, sent by the node to peers when a sub-block is generated - * @param version - message version (to allow injecting new fields) + * @param version - message version E(to allow injecting new fields) * @param subBlock - subblock * @param prevSubBlockId - previous sub block id `subBlock` is following, if missed, sub-block is linked * to a previous block */ - case class SubBlockInfo(version: Byte, subBlock: Header, prevSubBlockId: Option[Array[Byte]]) + case class SubBlockInfo(version: Byte, subBlock: Header, prevSubBlockId: Option[Array[Byte]]) { + def transactionsConfirmedDigest: Digest32 = subBlock.transactionsRoot + def subblockTransactionsDigest: Digest32 = ??? // read from extension + } object SubBlockInfo { From bed12e60b3199ead3870b41dd63b63313a0dfab4 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 30 Jul 2024 23:05:45 +0300 Subject: [PATCH 025/109] SubsPerBlock* improved comments in SubBlockAlgos --- .../org/ergoplatform/SubBlockAlgos.scala | 34 +++++++++++-------- .../mining/AutolykosPowScheme.scala | 2 +- .../ergoplatform/settings/Parameters.scala | 11 ++++-- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index c05dce94c1..81cff64a0b 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -7,7 +7,7 @@ import org.ergoplatform.network.message.MessageConstants.MessageCode import org.ergoplatform.network.message.MessageSpecV1 import org.ergoplatform.nodeView.history.ErgoHistoryUtils.Difficulty import org.ergoplatform.serialization.ErgoSerializer -import org.ergoplatform.settings.Constants +import org.ergoplatform.settings.{Constants, Parameters} import scorex.crypto.hash.Digest32 import scorex.util.Extensions._ import scorex.util.serialization.{Reader, Writer} @@ -18,12 +18,12 @@ import scala.collection.mutable /** * Implementation steps: - * * implement basic sub block algorithms (isSub etc) - * * implement sub block network message - * * implement sub block info support in sync tracker - * * implement downloading sub blocks chain + * * implement basic input block algorithms (isInput etc) + * * implement input block network message + * * implement input block info support in sync tracker + * * implement downloading input blocks chain * * implement avoiding downloading full-blocks - * * sub blocks support in /mining API + * * input blocks support in /mining API * * sub confirmations API */ object SubBlockAlgos { @@ -38,24 +38,28 @@ object SubBlockAlgos { // * every block (input and ordering) also contains digest of new transactions since last input block. For ordering // block, they are ignored. // * script execution context different for input and ordering blocks for the following fields : - // * timestamp - next input or ordering + // * timestamp - next input or ordering block has non-decreasing timestamp to ours // * height - the same for input blocks and next ordering block - // * votes - - // * minerPk - + // * votes - could be different in different (input and ordering) blocks + // * minerPk - could be different in different (input and ordering) blocks // Another option is to use 2-PoW-for 1 technique, so sub-block (input block) is defined not by // hash(b) < T/subsPerBlock , but by reverse(hash(b)) < T/subsPerBlock , while ordering block is defined // by hash(b) < T - val subsPerBlock = 128 // sub blocks per block, adjustable via miners voting - val weakTransactionIdLength = 6 // value taken from Bitcoin's compact blocks BIP + // sub blocks per block, adjustable via miners voting + // todo: likely we need to update rule exMatchParameters (#409) + val subsPerBlock = Parameters.SubsPerBlockDefault - def isSub(header: Header, requiredDifficulty: Difficulty): Boolean = { - val diff = requiredDifficulty / subsPerBlock - header.requiredDifficulty >= diff - } + val weakTransactionIdLength = 6 // value taken from Bitcoin's compact blocks BIP + def isInput(header: Header): Boolean = { + // val fullTarget = AutolykosPowScheme.getB(header.nBits) + // val subTarget = fullTarget * subsPerBlock + // todo: calc hit and check block kind + false + } // messages: // diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala index 5dd710da26..02dfa120fa 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala @@ -206,7 +206,7 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { /** * Get target `b` from encoded difficulty `nBits` */ - private[mining] def getB(nBits: Long): BigInt = { + def getB(nBits: Long): BigInt = { q / DifficultySerializer.decodeCompactBits(nBits) } diff --git a/ergo-core/src/main/scala/org/ergoplatform/settings/Parameters.scala b/ergo-core/src/main/scala/org/ergoplatform/settings/Parameters.scala index ba590ddcad..869e4e094e 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/settings/Parameters.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/settings/Parameters.scala @@ -266,6 +266,8 @@ object Parameters { val OutputCostIncrease: Byte = 8 val OutputCostDecrease: Byte = (-OutputCostIncrease).toByte + val SubsPerBlockIncrease: Byte = 9 + val SubsPerBlockDecrease: Byte = (-SubsPerBlockIncrease).toByte val StorageFeeFactorDefault: Int = 1250000 val StorageFeeFactorMax: Int = 2500000 @@ -291,6 +293,8 @@ object Parameters { val MaxBlockCostDefault: Int = 1000000 + val SubsPerBlockDefault: Int = 64 + val DefaultParameters: Map[Byte, Int] = Map( StorageFeeFactorIncrease -> StorageFeeFactorDefault, MinValuePerByteIncrease -> MinValuePerByteDefault, @@ -300,6 +304,7 @@ object Parameters { OutputCostIncrease -> OutputCostDefault, MaxBlockSizeIncrease -> MaxBlockSizeDefault, MaxBlockCostIncrease -> MaxBlockCostDefault, + SubsPerBlockIncrease -> SubsPerBlockDefault, BlockVersion -> 1 ) @@ -312,7 +317,8 @@ object Parameters { TokenAccessCostIncrease -> "Token access cost", InputCostIncrease -> "Cost per one transaction input", DataInputCostIncrease -> "Cost per one data input", - OutputCostIncrease -> "Cost per one transaction output" + OutputCostIncrease -> "Cost per one transaction output", + SubsPerBlockIncrease -> "Input blocks per finalizing block (on average)" ) val stepsTable: Map[Byte, Int] = Map( @@ -324,7 +330,8 @@ object Parameters { StorageFeeFactorIncrease -> StorageFeeFactorMin, MinValuePerByteIncrease -> MinValueMin, MaxBlockSizeIncrease -> MaxBlockSizeMin, - MaxBlockCostIncrease -> 16 * 1024 + MaxBlockCostIncrease -> 16 * 1024, + SubsPerBlockIncrease -> 2 ) val maxValues: Map[Byte, Int] = Map( From 01da1a0cabf4893836a06db88d1c4d278a28f24a Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 3 Aug 2024 20:40:22 +0300 Subject: [PATCH 026/109] BlockKind, distnguishing fn --- .../scala/org/ergoplatform/SubBlockAlgos.scala | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index 81cff64a0b..1f98630e22 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -1,6 +1,7 @@ package org.ergoplatform import org.ergoplatform.SubBlockAlgos.SubBlockInfo +import org.ergoplatform.mining.AutolykosPowScheme import org.ergoplatform.modifiers.history.header.Header.{Timestamp, Version} import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} import org.ergoplatform.network.message.MessageConstants.MessageCode @@ -48,15 +49,23 @@ object SubBlockAlgos { // by hash(b) < T // sub blocks per block, adjustable via miners voting - // todo: likely we need to update rule exMatchParameters (#409) + // todo: likely we need to update rule exMatchParameters (#409) to add new parameter to vote val subsPerBlock = Parameters.SubsPerBlockDefault val weakTransactionIdLength = 6 // value taken from Bitcoin's compact blocks BIP + lazy val powScheme = new AutolykosPowScheme(32, 26) + + sealed trait BlockKind + + def isInput(header: Header): Boolean = { - // val fullTarget = AutolykosPowScheme.getB(header.nBits) - // val subTarget = fullTarget * subsPerBlock + val fullTarget = powScheme.getB(header.nBits) + val subTarget = fullTarget * subsPerBlock + val hit = powScheme.hitForVersion2(header) // todo: cache hit + + hit < subsPerBlock // todo: calc hit and check block kind false } From 3e9f4d14f3b6742e53374b938b77a46e5fc9ed50 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 6 Aug 2024 10:02:42 +0300 Subject: [PATCH 027/109] blockKind() --- .../org/ergoplatform/SubBlockAlgos.scala | 27 +-- .../nodeView/state/UtxoState.scala | 181 +++++++++--------- 2 files changed, 106 insertions(+), 102 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index 1f98630e22..d6ff35cddb 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -2,18 +2,15 @@ package org.ergoplatform import org.ergoplatform.SubBlockAlgos.SubBlockInfo import org.ergoplatform.mining.AutolykosPowScheme -import org.ergoplatform.modifiers.history.header.Header.{Timestamp, Version} import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} import org.ergoplatform.network.message.MessageConstants.MessageCode import org.ergoplatform.network.message.MessageSpecV1 -import org.ergoplatform.nodeView.history.ErgoHistoryUtils.Difficulty import org.ergoplatform.serialization.ErgoSerializer import org.ergoplatform.settings.{Constants, Parameters} import scorex.crypto.hash.Digest32 import scorex.util.Extensions._ import scorex.util.serialization.{Reader, Writer} import scorex.util.{ModifierId, bytesToId, idToBytes} -import sigmastate.crypto.CryptoConstants.EcPointType import scala.collection.mutable @@ -57,17 +54,24 @@ object SubBlockAlgos { lazy val powScheme = new AutolykosPowScheme(32, 26) sealed trait BlockKind - - def isInput(header: Header): Boolean = { + case object InputBlock extends BlockKind + case object FinalizingBlock extends BlockKind + case object InvalidPoWBlock extends BlockKind + + def blockKind(header: Header): BlockKind = { val fullTarget = powScheme.getB(header.nBits) val subTarget = fullTarget * subsPerBlock - val hit = powScheme.hitForVersion2(header) // todo: cache hit - + val hit = powScheme.hitForVersion2(header) // todo: cache hit in header - hit < subsPerBlock - // todo: calc hit and check block kind - false + // todo: consider 2-for-1 pow technique + if (hit < subTarget) { + InputBlock + } else if (hit >= subTarget && hit < fullTarget) { + FinalizingBlock + } else { + InvalidPoWBlock + } } // messages: @@ -94,9 +98,6 @@ object SubBlockAlgos { val initialMessageVersion = 1 - /** - * Serializer which can convert self to bytes - */ def serializer: ErgoSerializer[SubBlockInfo] = new ErgoSerializer[SubBlockInfo] { override def serialize(sbi: SubBlockInfo, w: Writer): Unit = { w.put(sbi.version) diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala index 01b98a59ef..707390e621 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala @@ -109,111 +109,114 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 } } - override def applyModifier(mod: BlockSection, estimatedTip: Option[Height]) - (generate: LocallyGeneratedModifier => Unit): Try[UtxoState] = mod match { - case fb: ErgoFullBlock => - - val keepVersions = ergoSettings.nodeSettings.keepVersions - - // avoid storing versioned information in the database when block being processed is behind - // blockchain tip by `keepVersions` blocks at least - // we store `keepVersions` diffs in the database if chain tip is not known yet - if (fb.height >= estimatedTip.getOrElse(0) - keepVersions) { - if (store.getKeepVersions < keepVersions) { - store.setKeepVersions(keepVersions) - } - } else { - if (store.getKeepVersions > 0) { - store.setKeepVersions(0) - } + private def applyFullBlock(fb: ErgoFullBlock, estimatedTip: Option[Height]) + (generate: LocallyGeneratedModifier => Unit): Try[UtxoState] = { + val keepVersions = ergoSettings.nodeSettings.keepVersions + + // avoid storing versioned information in the database when block being processed is behind + // blockchain tip by `keepVersions` blocks at least + // we store `keepVersions` diffs in the database if chain tip is not known yet + if (fb.height >= estimatedTip.getOrElse(0) - keepVersions) { + if (store.getKeepVersions < keepVersions) { + store.setKeepVersions(keepVersions) } + } else { + if (store.getKeepVersions > 0) { + store.setKeepVersions(0) + } + } - persistentProver.synchronized { - val height = fb.header.height + persistentProver.synchronized { + val height = fb.header.height - log.debug(s"Trying to apply full block with header ${fb.header.encodedId} at height $height") + log.debug(s"Trying to apply full block with header ${fb.header.encodedId} at height $height") - val inRoot = rootDigest + val inRoot = rootDigest - val stateTry = stateContext.appendFullBlock(fb).flatMap { newStateContext => - val txsTry = applyTransactions(fb.blockTransactions.txs, fb.header.id, fb.header.stateRoot, newStateContext) + val stateTry = stateContext.appendFullBlock(fb).flatMap { newStateContext => + val txsTry = applyTransactions(fb.blockTransactions.txs, fb.header.id, fb.header.stateRoot, newStateContext) - txsTry.map { _: Unit => - val emissionBox = extractEmissionBox(fb) - val meta = metadata(idToVersion(fb.id), fb.header.stateRoot, emissionBox, newStateContext) + txsTry.map { _: Unit => + val emissionBox = extractEmissionBox(fb) + val meta = metadata(idToVersion(fb.id), fb.header.stateRoot, emissionBox, newStateContext) - var proofBytes = persistentProver.generateProofAndUpdateStorage(meta) + var proofBytes = persistentProver.generateProofAndUpdateStorage(meta) - if (!store.get(org.ergoplatform.core.idToBytes(fb.id)) - .exists(w => java.util.Arrays.equals(w, fb.header.stateRoot))) { - throw new Exception("Storage kept roothash is not equal to the declared one") - } + if (!store.get(org.ergoplatform.core.idToBytes(fb.id)) + .exists(w => java.util.Arrays.equals(w, fb.header.stateRoot))) { + throw new Exception("Storage kept roothash is not equal to the declared one") + } - if (!java.util.Arrays.equals(fb.header.stateRoot, persistentProver.digest)) { - throw new Exception("Calculated stateRoot is not equal to the declared one") - } + if (!java.util.Arrays.equals(fb.header.stateRoot, persistentProver.digest)) { + throw new Exception("Calculated stateRoot is not equal to the declared one") + } - var proofHash = ADProofs.proofDigest(proofBytes) - - if (!java.util.Arrays.equals(fb.header.ADProofsRoot, proofHash)) { - - log.error("Calculated proofHash is not equal to the declared one, doing another attempt") - - /** - * Proof generated was different from one announced. - * - * In most cases, announced proof is okay, and as proof is already checked, problem in some - * extra bytes added to the proof. - * - * Could be related to https://github.com/ergoplatform/ergo/issues/1614 - * - * So the problem could appear on mining nodes only, and caused by - * proofsForTransactions() wasting the tree unexpectedly. - * - * We are trying to generate proof again now. - */ - - persistentProver.rollback(inRoot) - .ensuring(java.util.Arrays.equals(persistentProver.digest, inRoot)) - - ErgoState.stateChanges(fb.blockTransactions.txs) match { - case Success(stateChanges) => - val mods = stateChanges.operations - mods.foreach( modOp => persistentProver.performOneOperation(modOp)) - - // meta is the same as it is block-specific - proofBytes = persistentProver.generateProofAndUpdateStorage(meta) - proofHash = ADProofs.proofDigest(proofBytes) - - if(!java.util.Arrays.equals(fb.header.ADProofsRoot, proofHash)) { - throw new Exception("Regenerated proofHash is not equal to the declared one") - } - case Failure(e) => - throw new Exception("Can't generate state changes on proof regeneration ", e) - } + var proofHash = ADProofs.proofDigest(proofBytes) + + if (!java.util.Arrays.equals(fb.header.ADProofsRoot, proofHash)) { + + log.error("Calculated proofHash is not equal to the declared one, doing another attempt") + + /** + * Proof generated was different from one announced. + * + * In most cases, announced proof is okay, and as proof is already checked, problem in some + * extra bytes added to the proof. + * + * Could be related to https://github.com/ergoplatform/ergo/issues/1614 + * + * So the problem could appear on mining nodes only, and caused by + * proofsForTransactions() wasting the tree unexpectedly. + * + * We are trying to generate proof again now. + */ + + persistentProver.rollback(inRoot) + .ensuring(java.util.Arrays.equals(persistentProver.digest, inRoot)) + + ErgoState.stateChanges(fb.blockTransactions.txs) match { + case Success(stateChanges) => + val mods = stateChanges.operations + mods.foreach( modOp => persistentProver.performOneOperation(modOp)) + + // meta is the same as it is block-specific + proofBytes = persistentProver.generateProofAndUpdateStorage(meta) + proofHash = ADProofs.proofDigest(proofBytes) + + if(!java.util.Arrays.equals(fb.header.ADProofsRoot, proofHash)) { + throw new Exception("Regenerated proofHash is not equal to the declared one") + } + case Failure(e) => + throw new Exception("Can't generate state changes on proof regeneration ", e) } + } - if (fb.adProofs.isEmpty) { - if (fb.height >= estimatedTip.getOrElse(Int.MaxValue) - ergoSettings.nodeSettings.adProofsSuffixLength) { - val adProofs = ADProofs(fb.header.id, proofBytes) - generate(LocallyGeneratedModifier(adProofs)) - } + if (fb.adProofs.isEmpty) { + if (fb.height >= estimatedTip.getOrElse(Int.MaxValue) - ergoSettings.nodeSettings.adProofsSuffixLength) { + val adProofs = ADProofs(fb.header.id, proofBytes) + generate(LocallyGeneratedModifier(adProofs)) } - - log.info(s"Valid modifier with header ${fb.header.encodedId} and emission box " + - s"${emissionBox.map(e => Algos.encode(e.id))} applied to UtxoState at height ${fb.header.height}") - saveSnapshotIfNeeded(fb.height, estimatedTip) - new UtxoState(persistentProver, idToVersion(fb.id), store, ergoSettings) } + + log.info(s"Valid modifier with header ${fb.header.encodedId} and emission box " + + s"${emissionBox.map(e => Algos.encode(e.id))} applied to UtxoState at height ${fb.header.height}") + saveSnapshotIfNeeded(fb.height, estimatedTip) + new UtxoState(persistentProver, idToVersion(fb.id), store, ergoSettings) } - stateTry.recoverWith[UtxoState] { case e => - log.warn(s"Error while applying full block with header ${fb.header.encodedId} to UTXOState with root" + - s" ${Algos.encode(inRoot)}, reason: ${LoggingUtil.getReasonMsg(e)} ", e) - persistentProver.rollback(inRoot) - .ensuring(java.util.Arrays.equals(persistentProver.digest, inRoot)) - Failure(e) - } } + stateTry.recoverWith[UtxoState] { case e => + log.warn(s"Error while applying full block with header ${fb.header.encodedId} to UTXOState with root" + + s" ${Algos.encode(inRoot)}, reason: ${LoggingUtil.getReasonMsg(e)} ", e) + persistentProver.rollback(inRoot) + .ensuring(java.util.Arrays.equals(persistentProver.digest, inRoot)) + Failure(e) + } + } + } + + override def applyModifier(mod: BlockSection, estimatedTip: Option[Height]) + (generate: LocallyGeneratedModifier => Unit): Try[UtxoState] = mod match { + case fb: ErgoFullBlock => applyFullBlock(fb, estimatedTip)(generate) case bs: BlockSection => log.warn(s"Only full-blocks are expected, found $bs") From 1a8aaeb042133fbde01b1036731be67292206f17 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 7 Aug 2024 20:48:54 +0300 Subject: [PATCH 028/109] unused reportModifierIsInvalid removed --- .../org/ergoplatform/http/api/ApiCodecs.scala | 4 ++-- .../nodeView/ErgoNodeViewHolder.scala | 2 +- .../nodeView/history/ErgoHistory.scala | 4 +--- .../history/VerifyADHistorySpecification.scala | 17 +++++------------ .../VerifyNonADHistorySpecification.scala | 4 +--- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala b/ergo-core/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala index 1b6c9b2214..cdd5552635 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala @@ -1,7 +1,7 @@ package org.ergoplatform.http.api -import cats.syntax.either._ -import io.circe._ +import cats.syntax.either._ // needed for Scala 2.11 +import io.circe._ // needed for Scala 2.11 import io.circe.syntax._ import org.bouncycastle.util.BigIntegers import org.ergoplatform.ErgoBox.RegisterId diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index e68b9d4693..7160c169fb 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -240,7 +240,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti } case Failure(e) => log.warn(s"Invalid modifier! Typeid: ${modToApply.modifierTypeId} id: ${modToApply.id} ", e) - history.reportModifierIsInvalid(modToApply, progressInfo).map { case (newHis, newProgressInfo) => + history.reportModifierIsInvalid(modToApply).map { case (newHis, newProgressInfo) => context.system.eventStream.publish(SemanticallyFailedModification(modToApply.modifierTypeId, modToApply.id, e)) UpdateInformation(newHis, updateInfo.state, Some(modToApply), Some(newProgressInfo), updateInfo.suffix) } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala index c001dd8e64..b3e033d13b 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala @@ -119,9 +119,7 @@ trait ErgoHistory * @return ProgressInfo with next modifier to try to apply */ @SuppressWarnings(Array("OptionGet", "TraversableHead")) - def reportModifierIsInvalid(modifier: BlockSection, - progressInfo: ProgressInfo[BlockSection] - ): Try[(ErgoHistory, ProgressInfo[BlockSection])] = synchronized { + def reportModifierIsInvalid(modifier: BlockSection): Try[(ErgoHistory, ProgressInfo[BlockSection])] = synchronized { log.warn(s"Modifier ${modifier.encodedId} of type ${modifier.modifierTypeId} is marked as invalid") correspondingHeader(modifier) match { case Some(invalidatedHeader) => diff --git a/src/test/scala/org/ergoplatform/nodeView/history/VerifyADHistorySpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/VerifyADHistorySpecification.scala index e1551f01d1..da551cf939 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/VerifyADHistorySpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/VerifyADHistorySpecification.scala @@ -1,6 +1,5 @@ package org.ergoplatform.nodeView.history -import org.ergoplatform.consensus.ProgressInfo import org.ergoplatform.modifiers.history.extension.Extension import org.ergoplatform.modifiers.history.HeaderChain import org.ergoplatform.modifiers.history.header.Header @@ -268,9 +267,7 @@ class VerifyADHistorySpecification extends ErgoCorePropertyTest with NoShrink { history.isSemanticallyValid(fullBlock.blockTransactions.id) shouldBe Unknown - val progressInfo = ProgressInfo[PM](Option(fullBlock.header.parentId), Seq(fullBlock), Seq.empty, Seq.empty) - history.reportModifierIsInvalid(fullBlock.header, progressInfo) - + history.reportModifierIsInvalid(fullBlock.header) history.isSemanticallyValid(fullBlock.header.id) shouldBe Invalid history.isSemanticallyValid(fullBlock.adProofs.value.id) shouldBe Invalid history.isSemanticallyValid(fullBlock.blockTransactions.id) shouldBe Invalid @@ -287,8 +284,7 @@ class VerifyADHistorySpecification extends ErgoCorePropertyTest with NoShrink { history = applyChain(history, fork1) history = applyChain(history, fork2) - val progressInfo = ProgressInfo[PM](Some(inChain.last.parentId), fork2, Seq.empty, Seq.empty) - history.reportModifierIsInvalid(inChain.last.header, progressInfo) + history.reportModifierIsInvalid(inChain.last.header) fork1.foreach { fullBlock => history.isSemanticallyValid(fullBlock.header.id) shouldBe Invalid @@ -315,8 +311,7 @@ class VerifyADHistorySpecification extends ErgoCorePropertyTest with NoShrink { history.bestHeaderOpt.value shouldBe fork1.last.header - val progressInfo = ProgressInfo[PM](Some(common.parentId), fork1, Seq.empty, Seq.empty) - history.reportModifierIsInvalid(fork1.head.header, progressInfo) + history.reportModifierIsInvalid(fork1.head.header) history.bestHeaderOpt.value shouldBe fork2.last.header history.bestFullBlockOpt.value shouldBe fork2.last @@ -330,8 +325,7 @@ class VerifyADHistorySpecification extends ErgoCorePropertyTest with NoShrink { val invalidChain = chain.takeRight(2) - val progressInfo = ProgressInfo[PM](Some(invalidChain.head.parentId), invalidChain, Seq.empty, Seq.empty) - val report = history.reportModifierIsInvalid(invalidChain.head.header, progressInfo).get + val report = history.reportModifierIsInvalid(invalidChain.head.header).get history = report._1 val processInfo = report._2 processInfo.toApply.isEmpty shouldBe true @@ -353,8 +347,7 @@ class VerifyADHistorySpecification extends ErgoCorePropertyTest with NoShrink { history.contains(parentHeader.transactionsId) shouldBe true history.contains(parentHeader.ADProofsId) shouldBe true - val progressInfo = ProgressInfo[PM](Some(parentHeader.id), Seq(fullBlock), Seq.empty, Seq.empty) - val (repHistory, _) = history.reportModifierIsInvalid(fullBlock.blockTransactions, progressInfo).get + val (repHistory, _) = history.reportModifierIsInvalid(fullBlock.blockTransactions).get repHistory.bestFullBlockOpt.value.header shouldBe history.bestHeaderOpt.value repHistory.bestHeaderOpt.value shouldBe parentHeader } diff --git a/src/test/scala/org/ergoplatform/nodeView/history/VerifyNonADHistorySpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/VerifyNonADHistorySpecification.scala index 9cff6acd5e..b6e116b285 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/VerifyNonADHistorySpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/VerifyNonADHistorySpecification.scala @@ -1,6 +1,5 @@ package org.ergoplatform.nodeView.history -import org.ergoplatform.consensus.ProgressInfo import org.ergoplatform.modifiers.{ErgoFullBlock, NetworkObjectTypeId} import org.ergoplatform.modifiers.history._ import org.ergoplatform.modifiers.history.extension.Extension @@ -78,8 +77,7 @@ class VerifyNonADHistorySpecification extends ErgoCorePropertyTest { val invalidChainHead = altChain.head // invalidate modifier from fork - history.reportModifierIsInvalid(invalidChainHead.blockTransactions, - ProgressInfo(None, Seq.empty, Seq.empty, Seq.empty)) + history.reportModifierIsInvalid(invalidChainHead.blockTransactions) history.bestFullBlockIdOpt.get shouldEqual initChain.last.id From 184996e84a4744414f69394bb895c07eeb672dc9 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 7 Aug 2024 21:36:52 +0300 Subject: [PATCH 029/109] ProgressInfo.empty, removing implicit ScorexEncoder --- .../ergoplatform/consensus/ProgressInfo.scala | 9 ++++++--- .../main/scala/org/ergoplatform/core/core.scala | 8 ++++---- .../scala/org/ergoplatform/settings/Algos.scala | 16 ++++++++-------- .../org/ergoplatform/utils/ScorexEncoder.scala | 14 +------------- .../org/ergoplatform/utils/ScorexEncoding.scala | 4 +++- .../validation/ModifierValidator.scala | 7 ++++--- .../http/api/ErgoUtilsApiRoute.scala | 11 ++++------- .../network/ErgoNodeViewSynchronizer.scala | 10 +++++----- .../nodeView/ErgoNodeViewHolder.scala | 5 ++--- .../nodeView/history/ErgoHistory.scala | 6 +++--- .../nodeView/history/ErgoHistoryReader.scala | 4 +--- .../history/storage/HistoryStorage.scala | 8 +++----- .../BlockSectionProcessor.scala | 3 +-- .../EmptyBlockSectionProcessor.scala | 2 +- .../modifierprocessors/FullBlockProcessor.scala | 2 +- .../FullBlockSectionProcessor.scala | 2 +- .../modifierprocessors/HeadersProcessor.scala | 3 +-- .../nodeView/state/DigestState.scala | 9 ++++----- .../ergoplatform/nodeView/state/UtxoState.scala | 8 +++----- 19 files changed, 56 insertions(+), 75 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/consensus/ProgressInfo.scala b/ergo-core/src/main/scala/org/ergoplatform/consensus/ProgressInfo.scala index 96efe2ec47..15cdbe3459 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/consensus/ProgressInfo.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/consensus/ProgressInfo.scala @@ -16,8 +16,7 @@ import scorex.util.ModifierId case class ProgressInfo[PM <: BlockSection](branchPoint: Option[ModifierId], toRemove: Seq[PM], toApply: Seq[PM], - toDownload: Seq[(NetworkObjectTypeId.Value, ModifierId)]) - (implicit encoder: ScorexEncoder) { + toDownload: Seq[(NetworkObjectTypeId.Value, ModifierId)]) { if (toRemove.nonEmpty) require(branchPoint.isDefined, s"Branch point should be defined for non-empty `toRemove`") @@ -25,7 +24,11 @@ case class ProgressInfo[PM <: BlockSection](branchPoint: Option[ModifierId], lazy val chainSwitchingNeeded: Boolean = toRemove.nonEmpty override def toString: String = { - s"ProgressInfo(BranchPoint: ${branchPoint.map(encoder.encodeId)}, " + + s"ProgressInfo(BranchPoint: ${branchPoint.map(ScorexEncoder.encodeId)}, " + s" to remove: ${toRemove.map(_.encodedId)}, to apply: ${toApply.map(_.encodedId)})" } } + +object ProgressInfo { + val empty = ProgressInfo[BlockSection](None, Seq.empty, Seq.empty, Seq.empty) +} diff --git a/ergo-core/src/main/scala/org/ergoplatform/core/core.scala b/ergo-core/src/main/scala/org/ergoplatform/core/core.scala index 59d6d0ffdd..15fa1a1f8f 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/core/core.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/core/core.scala @@ -13,18 +13,18 @@ package object core { type VersionTag = VersionTag.Type - def idsToString(ids: Seq[(NetworkObjectTypeId.Value, util.ModifierId)])(implicit enc: ScorexEncoder): String = { + def idsToString(ids: Seq[(NetworkObjectTypeId.Value, util.ModifierId)]): String = { List(ids.headOption, ids.lastOption) .flatten - .map { case (typeId, id) => s"($typeId,${enc.encodeId(id)})" } + .map { case (typeId, id) => s"($typeId,${ScorexEncoder.encodeId(id)})" } .mkString("[", "..", "]") } - def idsToString(modifierType: NetworkObjectTypeId.Value, ids: Seq[util.ModifierId])(implicit encoder: ScorexEncoder): String = { + def idsToString(modifierType: NetworkObjectTypeId.Value, ids: Seq[util.ModifierId]): String = { idsToString(ids.map(id => (modifierType, id))) } - def idsToString(invData: InvData)(implicit encoder: ScorexEncoder): String = idsToString(invData.typeId, invData.ids) + def idsToString(invData: InvData): String = idsToString(invData.typeId, invData.ids) def bytesToId: Array[Byte] => util.ModifierId = scorex.util.bytesToId diff --git a/ergo-core/src/main/scala/org/ergoplatform/settings/Algos.scala b/ergo-core/src/main/scala/org/ergoplatform/settings/Algos.scala index ac80a7001d..ed3843a873 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/settings/Algos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/settings/Algos.scala @@ -1,24 +1,24 @@ package org.ergoplatform.settings import org.ergoplatform.utils -import org.ergoplatform.utils.ScorexEncoder import scorex.crypto.authds.LeafData import scorex.crypto.authds.merkle.MerkleTree import scorex.crypto.hash.Digest32 -import scorex.util._ +import scorex.util.encode.BytesEncoder object Algos extends ErgoAlgos with utils.ScorexEncoding { - // ErgoAlgos in sigmastate extends scorex.util.ScorexEncoding where encoder is BytesEncoder - // but here we use scorex.core.utils.ScorexEncoding where encoder is ScorexEncoder - // After ScorexEncoder is moved (there is even a todo for that) from scorex.core to scorex.util - // we can fix this ugliness. - override implicit val encoder: ScorexEncoder = utils.ScorexEncoder.default + override implicit val encoder: BytesEncoder = utils.ScorexEncoder lazy val emptyMerkleTreeRoot: Digest32 = Algos.hash(LeafData @@ Array[Byte]()) - @inline def encode(id: ModifierId): String = encoder.encode(id) + /** + * This method might be useful and reimplemented, if encoding of ModifierId and VersionTag + * is different form default bytes encoding, e.g. this method should be reimplemented together + * with encode() and decode methods + */ + @inline def encode(id: String): String = id /** * A method to build a Merkle tree over binary objects (leafs of the tree) diff --git a/ergo-core/src/main/scala/org/ergoplatform/utils/ScorexEncoder.scala b/ergo-core/src/main/scala/org/ergoplatform/utils/ScorexEncoder.scala index e437be18f6..3911490e10 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/utils/ScorexEncoder.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/utils/ScorexEncoder.scala @@ -6,7 +6,7 @@ import scorex.util.encode.{Base16, BytesEncoder} import scala.util.Try -class ScorexEncoder extends BytesEncoder { +object ScorexEncoder extends BytesEncoder { @inline override val Alphabet: String = Base16.Alphabet @@ -16,14 +16,6 @@ class ScorexEncoder extends BytesEncoder { @inline override def decode(input: String): Try[Array[Byte]] = Base16.decode(input) - /** - * This method might be useful and reimplemented, if encoding of ModifierId and VersionTag - * is different form default bytes encoding, e.g. this method should be reimplemented together - * with encode() and decode methods - */ - @inline - def encode(input: String): String = input - /** * This method might be useful and reimplemented, if encoding of ModifierId and VersionTag * is different form default bytes encoding, e.g. this method should be reimplemented together @@ -41,7 +33,3 @@ class ScorexEncoder extends BytesEncoder { def encodeId(input: ModifierId): String = input } - -object ScorexEncoder { - val default: ScorexEncoder = new ScorexEncoder() -} diff --git a/ergo-core/src/main/scala/org/ergoplatform/utils/ScorexEncoding.scala b/ergo-core/src/main/scala/org/ergoplatform/utils/ScorexEncoding.scala index 089d00b640..916c92dac5 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/utils/ScorexEncoding.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/utils/ScorexEncoding.scala @@ -1,9 +1,11 @@ package org.ergoplatform.utils +import scorex.util.encode.BytesEncoder + /** * Trait with bytes to string encoder * TODO extract to ScorexUtils project */ trait ScorexEncoding { - implicit val encoder: ScorexEncoder = ScorexEncoder.default + val encoder: BytesEncoder = ScorexEncoder } diff --git a/ergo-core/src/main/scala/org/ergoplatform/validation/ModifierValidator.scala b/ergo-core/src/main/scala/org/ergoplatform/validation/ModifierValidator.scala index dab7a2db33..3f6fb3518c 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/validation/ModifierValidator.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/validation/ModifierValidator.scala @@ -25,8 +25,8 @@ import scala.util.{Failure, Success, Try} */ object ModifierValidator { - def apply(settings: ValidationSettings)(implicit e: ScorexEncoder): ValidationState[Unit] = { - ValidationState(ModifierValidator.success, settings)(e) + def apply(settings: ValidationSettings): ValidationState[Unit] = { + ValidationState(ModifierValidator.success, settings) } /** report recoverable modifier error that could be fixed by later retries */ @@ -65,7 +65,7 @@ object ModifierValidator { } /** This is the place where all the validation DSL lives */ -case class ValidationState[T](result: ValidationResult[T], settings: ValidationSettings)(implicit e: ScorexEncoder) { +case class ValidationState[T](result: ValidationResult[T], settings: ValidationSettings) { /** Create the next validation state as the result of given `operation` */ def pass[R](operation: => ValidationResult[R]): ValidationState[R] = { @@ -115,6 +115,7 @@ case class ValidationState[T](result: ValidationResult[T], settings: ValidationS /** Validate the `id`s are equal. The `error` callback will be provided with detail on argument values */ def validateEqualIds(id: Short, `given`: => ModifierId, expected: => ModifierId, modifierTypeId: NetworkObjectTypeId.Value): ValidationState[T] = { + val e = ScorexEncoder pass { if (!settings.isActive(id) || given == expected) result else settings.getError(id, InvalidModifier(s"Given: ${e.encodeId(given)}, expected ${e.encodeId(expected)}", given, modifierTypeId)) diff --git a/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala index 87775dba7a..e98e82c488 100644 --- a/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala @@ -9,7 +9,7 @@ import org.ergoplatform.http.api.ApiError.BadRequest import org.ergoplatform.settings.{ErgoSettings, RESTApiSettings} import org.ergoplatform.{ErgoAddressEncoder, P2PKAddress} import scorex.core.api.http.{ApiResponse, ApiRoute} -import org.ergoplatform.utils.ScorexEncoding +import org.ergoplatform.utils.ScorexEncoder import scorex.crypto.hash.Blake2b256 import scorex.util.encode.Base16 import sigmastate.crypto.DLogProtocol.ProveDlog @@ -18,10 +18,7 @@ import java.security.SecureRandom import scala.util.Failure import sigmastate.serialization.{ErgoTreeSerializer, GroupElementSerializer, SigmaSerializer} -class ErgoUtilsApiRoute(val ergoSettings: ErgoSettings)( - implicit val context: ActorRefFactory -) extends ApiRoute - with ScorexEncoding { +class ErgoUtilsApiRoute(val ergoSettings: ErgoSettings)(implicit val context: ActorRefFactory) extends ApiRoute { private val SeedSize = 32 private val treeSerializer: ErgoTreeSerializer = new ErgoTreeSerializer @@ -46,7 +43,7 @@ class ErgoUtilsApiRoute(val ergoSettings: ErgoSettings)( private def seed(length: Int): String = { val seed = new Array[Byte](length) new SecureRandom().nextBytes(seed) //seed mutated here! - encoder.encode(seed) + ScorexEncoder.encode(seed) } def seedRoute: Route = (get & path("seed")) { @@ -60,7 +57,7 @@ class ErgoUtilsApiRoute(val ergoSettings: ErgoSettings)( def hashBlake2b: Route = { (post & path("hash" / "blake2b") & entity(as[Json])) { json => json.as[String] match { - case Right(message) => ApiResponse(encoder.encode(Blake2b256(message))) + case Right(message) => ApiResponse(ScorexEncoder.encode(Blake2b256(message))) case Left(ex) => ApiError(StatusCodes.BadRequest, ex.getMessage()) } } diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index e2a550282a..2feec87f97 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -22,7 +22,7 @@ import org.ergoplatform.network.message.{InvSpec, MessageSpec, ModifiersSpec, Re import scorex.core.network._ import scorex.core.network.{ConnectedPeer, ModifiersStatus, SendToPeer, SendToPeers} import org.ergoplatform.network.message.{InvData, Message, ModifiersData} -import org.ergoplatform.utils.ScorexEncoding +import org.ergoplatform.utils.ScorexEncoder import org.ergoplatform.validation.MalformedModifierError import scorex.util.{ModifierId, ScorexLogging} import scorex.core.network.DeliveryTracker @@ -53,7 +53,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, settings: ErgoSettings, syncTracker: ErgoSyncTracker, deliveryTracker: DeliveryTracker)(implicit ex: ExecutionContext) - extends Actor with Synchronizer with ScorexLogging with ScorexEncoding { + extends Actor with Synchronizer with ScorexLogging { import org.ergoplatform.network.ErgoNodeViewSynchronizer._ @@ -777,7 +777,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, case _ => // Penalize peer and do nothing - it will be switched to correct state on CheckDelivery penalizeMisbehavingPeer(remote) - log.warn(s"Failed to parse transaction with declared id ${encoder.encodeId(id)} from ${remote.toString}") + log.warn(s"Failed to parse transaction with declared id ${ScorexEncoder.encodeId(id)} from ${remote.toString}") } } } @@ -801,7 +801,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, // Forget about block section, so it will be redownloaded if announced again only deliveryTracker.setUnknown(id, modifierTypeId) penalizeMisbehavingPeer(remote) - log.warn(s"Failed to parse modifier with declared id ${encoder.encodeId(id)} from ${remote.toString}") + log.warn(s"Failed to parse modifier with declared id ${ScorexEncoder.encodeId(id)} from ${remote.toString}") None } } @@ -1230,7 +1230,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } else { // A block section is not delivered on time. log.info(s"Peer ${peer.toString} has not delivered network object " + - s"$modifierTypeId : ${encoder.encodeId(modifierId)} on time") + s"$modifierTypeId : ${ScorexEncoder.encodeId(modifierId)} on time") // Number of delivery checks for a block section, utxo set snapshot chunk or manifest // increased or initialized, except the case where we can have issues with connectivity, diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 7160c169fb..f955cf61f9 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -19,7 +19,6 @@ import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ import org.ergoplatform.nodeView.ErgoNodeViewHolder.{BlockAppliedTransactions, CurrentView, DownloadRequest} import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages._ import org.ergoplatform.modifiers.history.{ADProofs, HistoryModifierSerializer} -import org.ergoplatform.utils.ScorexEncoding import org.ergoplatform.validation.RecoverableModifierError import scorex.util.{ModifierId, ScorexLogging} import spire.syntax.all.cfor @@ -40,7 +39,7 @@ import scala.util.{Failure, Success, Try} * */ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSettings) - extends Actor with ScorexLogging with ScorexEncoding with FileUtils { + extends Actor with ScorexLogging with FileUtils { private implicit lazy val actorSystem: ActorSystem = context.system @@ -573,7 +572,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti log.info("State and history are both empty on startup") Success(stateIn) case (stateId, Some(block), _) if stateId == block.id => - log.info(s"State and history have the same version ${encoder.encode(stateId)}, no recovery needed.") + log.info(s"State and history have the same version ${Algos.encode(stateId)}, no recovery needed.") Success(stateIn) case (_, None, _) => log.info("State and history are inconsistent. History is empty on startup, rollback state to genesis.") diff --git a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala index b3e033d13b..38c8113bf7 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala @@ -134,7 +134,7 @@ trait ErgoHistory case (false, false) => // Modifiers from best header and best full chain are not involved, no rollback and links change required historyStorage.insert(validityRow, BlockSection.emptyArray).map { _ => - this -> ProgressInfo[BlockSection](None, Seq.empty, Seq.empty, Seq.empty) + this -> ProgressInfo.empty } case _ => // Modifiers from best header and best full chain are involved, links change required @@ -146,7 +146,7 @@ trait ErgoHistory newBestHeaderOpt.map(h => BestHeaderKey -> idToBytes(h.id)).toArray, BlockSection.emptyArray ).map { _ => - this -> ProgressInfo[BlockSection](None, Seq.empty, Seq.empty, Seq.empty) + this -> ProgressInfo.empty } } else { val invalidatedChain: Seq[ErgoFullBlock] = bestFullBlockOpt.toSeq @@ -182,7 +182,7 @@ trait ErgoHistory //No headers become invalid. Just mark this modifier as invalid log.warn(s"Modifier ${modifier.encodedId} of type ${modifier.modifierTypeId} is missing corresponding header") historyStorage.insert(Array(validityKey(modifier.id) -> Array(0.toByte)), BlockSection.emptyArray).map { _ => - this -> ProgressInfo[BlockSection](None, Seq.empty, Seq.empty, Seq.empty) + this -> ProgressInfo.empty } } } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala index 3fde451134..044a967590 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala @@ -11,7 +11,6 @@ import org.ergoplatform.nodeView.history.extra.ExtraIndex import org.ergoplatform.nodeView.history.storage._ import org.ergoplatform.nodeView.history.storage.modifierprocessors.{BlockSectionProcessor, HeadersProcessor} import org.ergoplatform.settings.{ErgoSettings, NipopowSettings} -import org.ergoplatform.utils.ScorexEncoding import org.ergoplatform.validation.MalformedModifierError import scorex.util.{ModifierId, ScorexLogging} @@ -27,8 +26,7 @@ trait ErgoHistoryReader with ContainsModifiers[BlockSection] with HeadersProcessor with BlockSectionProcessor - with ScorexLogging - with ScorexEncoding { + with ScorexLogging { type ModifierIds = Seq[(NetworkObjectTypeId.Value, ModifierId)] diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/HistoryStorage.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/HistoryStorage.scala index 88c4a1cd30..1a885eb1fe 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/HistoryStorage.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/HistoryStorage.scala @@ -6,7 +6,6 @@ import org.ergoplatform.modifiers.history.HistoryModifierSerializer import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.nodeView.history.extra.{ExtraIndex, ExtraIndexSerializer, Segment} import org.ergoplatform.settings.{Algos, CacheSettings, ErgoSettings} -import org.ergoplatform.utils.ScorexEncoding import scorex.db.{ByteArrayWrapper, LDBFactory, LDBKVStore} import scorex.util.{ModifierId, ScorexLogging, idToBytes} @@ -28,8 +27,7 @@ import scala.jdk.CollectionConverters.asScalaIteratorConverter */ class HistoryStorage(indexStore: LDBKVStore, objectsStore: LDBKVStore, extraStore: LDBKVStore, config: CacheSettings) extends ScorexLogging - with AutoCloseable - with ScorexEncoding { + with AutoCloseable { private lazy val headersCache = Caffeine.newBuilder() @@ -84,7 +82,7 @@ class HistoryStorage(indexStore: LDBKVStore, objectsStore: LDBKVStore, extraStor cacheModifier(pm) Some(pm) case Failure(_) => - log.warn(s"Failed to parse modifier ${encoder.encode(id)} from db (bytes are: ${Algos.encode(bytes)})") + log.warn(s"Failed to parse modifier ${Algos.encode(id)} from db (bytes are: ${Algos.encode(bytes)})") None } } @@ -99,7 +97,7 @@ class HistoryStorage(indexStore: LDBKVStore, objectsStore: LDBKVStore, extraStor } Some(pm) case Failure(_) => - log.warn(s"Failed to parse index ${encoder.encode(id)} from db (bytes are: ${Algos.encode(bytes)})") + log.warn(s"Failed to parse index ${Algos.encode(id)} from db (bytes are: ${Algos.encode(bytes)})") None } } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/BlockSectionProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/BlockSectionProcessor.scala index 653e592452..4f63bbdf8c 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/BlockSectionProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/BlockSectionProcessor.scala @@ -2,7 +2,6 @@ package org.ergoplatform.nodeView.history.storage.modifierprocessors import org.ergoplatform.consensus.ProgressInfo import org.ergoplatform.modifiers.{BlockSection, NonHeaderBlockSection} -import org.ergoplatform.utils.ScorexEncoding import scala.util.Try @@ -10,7 +9,7 @@ import scala.util.Try * Trait that declares interfaces for validation and processing of various * block sections: BlockTransactions, ADProofs, etc. */ -trait BlockSectionProcessor extends ScorexEncoding { +trait BlockSectionProcessor { /** * Whether state requires to download adProofs before full block application diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/EmptyBlockSectionProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/EmptyBlockSectionProcessor.scala index f7d35e3ea8..6cc442d7aa 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/EmptyBlockSectionProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/EmptyBlockSectionProcessor.scala @@ -12,7 +12,7 @@ import scala.util.{Failure, Success, Try} trait EmptyBlockSectionProcessor extends BlockSectionProcessor { override protected def process(m: NonHeaderBlockSection): Try[ProgressInfo[BlockSection]] = - Success(ProgressInfo[BlockSection](None, Seq.empty, Seq.empty, Seq.empty)) + Success(ProgressInfo.empty) override protected def validate(m: NonHeaderBlockSection): Try[Unit] = Failure(new Error("Regime that does not support block sections processing")) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/FullBlockProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/FullBlockProcessor.scala index 8c84852f09..cb97f9412e 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/FullBlockProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/FullBlockProcessor.scala @@ -136,7 +136,7 @@ trait FullBlockProcessor extends HeadersProcessor { //Orphaned block or full chain is not initialized yet logStatus(Seq(), Seq(), params.fullBlock, None) historyStorage.insert(Array.empty[(ByteArrayWrapper, Array[Byte])], Array(params.newModRow)).map { _ => - ProgressInfo(None, Seq.empty, Seq.empty, Seq.empty) + ProgressInfo.empty } } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/FullBlockSectionProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/FullBlockSectionProcessor.scala index b9c36f987d..0f48baf3ab 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/FullBlockSectionProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/FullBlockSectionProcessor.scala @@ -84,7 +84,7 @@ trait FullBlockSectionProcessor extends BlockSectionProcessor with FullBlockProc private def justPutToHistory(m: NonHeaderBlockSection): Try[ProgressInfo[BlockSection]] = { historyStorage.insert(Array.empty[(ByteArrayWrapper, Array[Byte])], Array[BlockSection](m)).map { _ => - ProgressInfo(None, Seq.empty, Seq.empty, Seq.empty) + ProgressInfo.empty } } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/HeadersProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/HeadersProcessor.scala index 0a801ccd5e..2273d7d478 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/HeadersProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/HeadersProcessor.scala @@ -14,7 +14,6 @@ import org.ergoplatform.nodeView.history.storage.HistoryStorage import org.ergoplatform.settings.Constants.HashLength import org.ergoplatform.settings.ValidationRules._ import org.ergoplatform.settings._ -import org.ergoplatform.utils.ScorexEncoding import org.ergoplatform.validation.{InvalidModifier, ModifierValidator, ValidationResult, ValidationState} import scorex.db.ByteArrayWrapper import scorex.util._ @@ -27,7 +26,7 @@ import scala.util.{Failure, Success, Try} /** * Contains all functions required by History to process Headers. */ -trait HeadersProcessor extends ToDownloadProcessor with PopowProcessor with ScorexLogging with ScorexEncoding { +trait HeadersProcessor extends ToDownloadProcessor with PopowProcessor with ScorexLogging { /** * Key for database record storing ID of best block header diff --git a/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala b/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala index 3c5a3a4721..18d6bed53d 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala @@ -29,8 +29,7 @@ class DigestState protected(override val version: VersionTag, override val store: LDBVersionedStore, override val ergoSettings: ErgoSettings) extends ErgoState[DigestState] - with ScorexLogging - with ScorexEncoding { + with ScorexLogging { store.lastVersionID .foreach(id => require(version == bytesToVersion(id), "version should always be equal to store.lastVersionID")) @@ -87,12 +86,12 @@ class DigestState protected(override val version: VersionTag, @SuppressWarnings(Array("OptionGet")) override def rollbackTo(version: VersionTag): Try[DigestState] = { - log.info(s"Rollback Digest State to version ${Algos.encoder.encode(version)}") + log.info(s"Rollback Digest State to version ${Algos.encode(version)}") val versionBytes = org.ergoplatform.core.versionToBytes(version) Try(store.rollbackTo(versionBytes)).map { _ => store.clean(nodeSettings.keepVersions) val rootHash = ADDigest @@ store.get(versionBytes).get - log.info(s"Rollback to version ${Algos.encoder.encode(version)} with roothash ${Algos.encoder.encode(rootHash)}") + log.info(s"Rollback to version ${Algos.encode(version)} with roothash ${Algos.encoder.encode(rootHash)}") new DigestState(version, rootHash, store, ergoSettings) } } @@ -200,7 +199,7 @@ object DigestState extends ScorexLogging with ScorexEncoding { case Success(state) => state case Failure(e) => store.close() - log.warn(s"Failed to create state with ${versionOpt.map(encoder.encode)} and ${rootHashOpt.map(encoder.encode)}", e) + log.warn(s"Failed to create state with ${versionOpt.map(Algos.encode)} and ${rootHashOpt.map(encoder.encode)}", e) ErgoState.generateGenesisDigestState(dir, settings) } } diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala index 707390e621..ef27e16fb0 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala @@ -12,7 +12,6 @@ import org.ergoplatform.settings.Algos.HF import org.ergoplatform.settings.ValidationRules.{fbDigestIncorrect, fbOperationFailed} import org.ergoplatform.settings.{Algos, ErgoSettings, Parameters} import org.ergoplatform.utils.LoggingUtil -import org.ergoplatform.utils.ScorexEncoding import org.ergoplatform.core._ import org.ergoplatform.nodeView.LocallyGeneratedModifier import org.ergoplatform.validation.ModifierValidator @@ -38,8 +37,7 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 override val store: LDBVersionedStore, override protected val ergoSettings: ErgoSettings) extends ErgoState[UtxoState] - with UtxoStateReader - with ScorexEncoding { + with UtxoStateReader { import UtxoState.metadata @@ -49,7 +47,7 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 override def rollbackTo(version: VersionTag): Try[UtxoState] = persistentProver.synchronized { val p = persistentProver - log.info(s"Rollback UtxoState to version ${Algos.encoder.encode(version)}") + log.info(s"Rollback UtxoState to version ${Algos.encode(version)}") store.get(versionToBytes(version)) match { case Some(hash) => val rootHash: ADDigest = ADDigest @@ hash @@ -58,7 +56,7 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 } rollbackResult case None => - Failure(new Error(s"Unable to get root hash at version ${Algos.encoder.encode(version)}")) + Failure(new Error(s"Unable to get root hash at version ${Algos.encode(version)}")) } } From 8bc72ccb2a62a73f0bf71bea2d40689d03fce868 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 12 Aug 2024 23:03:54 +0300 Subject: [PATCH 030/109] MessageSpecInitial / MessageSpecSubBlock --- .../scala/org/ergoplatform/SubBlockAlgos.scala | 8 ++++---- .../network/HandshakeSerializer.scala | 4 ++-- .../scala/org/ergoplatform/network/Version.scala | 2 ++ .../network/message/GetNipopowProofSpec.scala | 2 +- .../ergoplatform/network/message/InvSpec.scala | 2 +- .../network/message/MessageSpec.scala | 14 ++++++++++++-- .../network/message/ModifiersSpec.scala | 2 +- .../network/message/NipopowProofSpec.scala | 2 +- .../network/message/RequestModifierSpec.scala | 2 +- .../network/message/SyncInfoMessageSpec.scala | 2 +- .../network/VersionBasedPeerFilteringRule.scala | 4 ++-- .../network/message/BasicMessagesRepo.scala | 16 ++++++++-------- 12 files changed, 36 insertions(+), 24 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index d6ff35cddb..1873fb183e 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -4,7 +4,7 @@ import org.ergoplatform.SubBlockAlgos.SubBlockInfo import org.ergoplatform.mining.AutolykosPowScheme import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} import org.ergoplatform.network.message.MessageConstants.MessageCode -import org.ergoplatform.network.message.MessageSpecV1 +import org.ergoplatform.network.message.MessageSpecInitial import org.ergoplatform.serialization.ErgoSerializer import org.ergoplatform.settings.{Constants, Parameters} import scorex.crypto.hash.Digest32 @@ -122,7 +122,7 @@ object SubBlockAlgos { * Message that is informing about sub block produced. * Contains header and link to previous sub block (). */ - object SubBlockMessageSpec extends MessageSpecV1[SubBlockInfo] { + object SubBlockMessageSpec extends MessageSpecInitial[SubBlockInfo] { val MaxMessageSize = 10000 @@ -142,7 +142,7 @@ object SubBlockAlgos { * On receiving sub block or block, the node is sending last sub block or block id it has to get short transaction * ids since then */ - object GetDataSpec extends MessageSpecV1[ModifierId] { + object GetDataSpec extends MessageSpecInitial[ModifierId] { import scorex.util.{bytesToId, idToBytes} @@ -160,7 +160,7 @@ object SubBlockAlgos { case class TransactionsSince(transactionsWithBlockIds: Array[(ModifierId, Array[Array[Byte]])]) - class DataSpec extends MessageSpecV1[TransactionsSince] { + class DataSpec extends MessageSpecInitial[TransactionsSince] { override val messageCode: MessageCode = 92: Byte override val messageName: String = "GetData" diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/HandshakeSerializer.scala b/ergo-core/src/main/scala/org/ergoplatform/network/HandshakeSerializer.scala index f2e6d3db97..90e17465f4 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/HandshakeSerializer.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/HandshakeSerializer.scala @@ -1,7 +1,7 @@ package org.ergoplatform.network import org.ergoplatform.network.message.MessageConstants.MessageCode -import org.ergoplatform.network.message.MessageSpecV1 +import org.ergoplatform.network.message.MessageSpecInitial import scorex.util.serialization.{Reader, Writer} /** @@ -9,7 +9,7 @@ import scorex.util.serialization.{Reader, Writer} * to the receiving node at the beginning of a connection. Until both peers * have exchanged `Handshake` messages, no other messages will be accepted. */ -object HandshakeSerializer extends MessageSpecV1[Handshake] { +object HandshakeSerializer extends MessageSpecInitial[Handshake] { override val messageCode: MessageCode = 75: Byte override val messageName: String = "Handshake" diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/Version.scala b/ergo-core/src/main/scala/org/ergoplatform/network/Version.scala index a91ae9b797..20cd9c5107 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/Version.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/Version.scala @@ -37,6 +37,8 @@ object Version { val Eip37ForkVersion: Version = Version(4, 0, 100) val JitSoftForkVersion: Version = Version(5, 0, 0) + val SubblocksVersion: Version = Version(6, 0, 0) // todo: check before activation + val UtxoSnapsnotActivationVersion: Version = Version(5, 0, 12) val NipopowActivationVersion: Version = Version(5, 0, 13) diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/GetNipopowProofSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/GetNipopowProofSpec.scala index a28dd40e7c..dc08179691 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/GetNipopowProofSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/GetNipopowProofSpec.scala @@ -9,7 +9,7 @@ import scorex.util.serialization.{Reader, Writer} /** * The `GetNipopowProof` message requests a `NipopowProof` message from the receiving node */ -object GetNipopowProofSpec extends MessageSpecV1[NipopowProofData] { +object GetNipopowProofSpec extends MessageSpecInitial[NipopowProofData] { val SizeLimit = 1000 diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/InvSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/InvSpec.scala index dec5a76902..17590ec805 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/InvSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/InvSpec.scala @@ -14,7 +14,7 @@ import scorex.util.serialization.{Reader, Writer} * or it can be sent in reply to a `SyncInfo` message (or application-specific messages like `GetMempool`). * */ -object InvSpec extends MessageSpecV1[InvData] { +object InvSpec extends MessageSpecInitial[InvData] { val maxInvObjects: Int = 400 diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/MessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/MessageSpec.scala index f622f484b1..1f21e48951 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/MessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/MessageSpec.scala @@ -29,10 +29,20 @@ trait MessageSpec[Content] extends ErgoSerializer[Content] { } /** - * P2p messages, that where implemented since the beginning. + * P2p messages, that where implemented before sub-blocks */ -trait MessageSpecV1[Content] extends MessageSpec[Content] { +trait MessageSpecInitial[Content] extends MessageSpec[Content] { override val protocolVersion: Version = Version.initial } + + +/** + * Sub-blocks related messages, V2 of the protocol + */ +trait MessageSpecSubblocks[Content] extends MessageSpec[Content] { + + override val protocolVersion: Version = Version.SubblocksVersion + +} diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/ModifiersSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/ModifiersSpec.scala index c1d1118cd5..25dd76f087 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/ModifiersSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/ModifiersSpec.scala @@ -10,7 +10,7 @@ import scala.collection.immutable /** * The `Modifier` message is a reply to a `RequestModifier` message which requested these modifiers. */ -object ModifiersSpec extends MessageSpecV1[ModifiersData] with ScorexLogging { +object ModifiersSpec extends MessageSpecInitial[ModifiersData] with ScorexLogging { val maxMessageSize: Int = 2048576 diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/NipopowProofSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/NipopowProofSpec.scala index 9806961126..2410851fd3 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/NipopowProofSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/NipopowProofSpec.scala @@ -6,7 +6,7 @@ import scorex.util.serialization.{Reader, Writer} /** * The `NipopowProof` message is a reply to a `GetNipopowProof` message. */ -object NipopowProofSpec extends MessageSpecV1[Array[Byte]] { +object NipopowProofSpec extends MessageSpecInitial[Array[Byte]] { val SizeLimit = 2000000 override val messageCode: Byte = 91 diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/RequestModifierSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/RequestModifierSpec.scala index 1cf0e4d0d6..46095d0b35 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/RequestModifierSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/RequestModifierSpec.scala @@ -15,7 +15,7 @@ import scorex.util.serialization.{Reader, Writer} * data from a node which previously advertised it had that data by sending an `Inv` message. * */ -object RequestModifierSpec extends MessageSpecV1[InvData] { +object RequestModifierSpec extends MessageSpecInitial[InvData] { override val messageCode: MessageCode = 22: Byte override val messageName: String = "RequestModifier" diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/SyncInfoMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/SyncInfoMessageSpec.scala index 824365b62b..2562450dbd 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/SyncInfoMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/SyncInfoMessageSpec.scala @@ -13,7 +13,7 @@ import scorex.util.serialization.{Reader, Writer} * * Payload of this message should be determined in underlying applications. */ -class SyncInfoMessageSpec[SI <: SyncInfo](serializer: ErgoSerializer[SI]) extends MessageSpecV1[SI] { +class SyncInfoMessageSpec[SI <: SyncInfo](serializer: ErgoSerializer[SI]) extends MessageSpecInitial[SI] { override val messageCode: MessageCode = 65: Byte override val messageName: String = "Sync" diff --git a/src/main/scala/org/ergoplatform/network/VersionBasedPeerFilteringRule.scala b/src/main/scala/org/ergoplatform/network/VersionBasedPeerFilteringRule.scala index e3b357eeb4..46c5413a9c 100644 --- a/src/main/scala/org/ergoplatform/network/VersionBasedPeerFilteringRule.scala +++ b/src/main/scala/org/ergoplatform/network/VersionBasedPeerFilteringRule.scala @@ -40,7 +40,7 @@ trait VersionBasedPeerFilteringRule extends PeerFilteringRule { * @return - whether the peer should be selected */ override def condition(peer: ConnectedPeer): Boolean = { - val version = peer.peerInfo.map(_.peerSpec.protocolVersion).getOrElse(Version.initial) + val version = peer.peerInfo.map(_.peerSpec.protocolVersion).getOrElse(Version.Eip37ForkVersion) condition(version) } @@ -83,7 +83,7 @@ object NipopowSupportFilter extends PeerFilteringRule { * @return - whether the peer should be selected */ override def condition(peer: ConnectedPeer): Boolean = { - val version = peer.peerInfo.map(_.peerSpec.protocolVersion).getOrElse(Version.initial) + val version = peer.peerInfo.map(_.peerSpec.protocolVersion).getOrElse(Version.Eip37ForkVersion) peer.mode.flatMap(_.nipopowBootstrapped).isEmpty && version.compare(Version.NipopowActivationVersion) >= 0 diff --git a/src/main/scala/org/ergoplatform/network/message/BasicMessagesRepo.scala b/src/main/scala/org/ergoplatform/network/message/BasicMessagesRepo.scala index b7b5842154..db20f7a216 100644 --- a/src/main/scala/org/ergoplatform/network/message/BasicMessagesRepo.scala +++ b/src/main/scala/org/ergoplatform/network/message/BasicMessagesRepo.scala @@ -16,7 +16,7 @@ import org.ergoplatform.sdk.wallet.Constants.ModifierIdLength * its database of available nodes rather than waiting for unsolicited `Peers` * messages to arrive over time. */ -object GetPeersSpec extends MessageSpecV1[Unit] { +object GetPeersSpec extends MessageSpecInitial[Unit] { override val messageCode: MessageCode = 1: Byte override val messageName: String = "GetPeers message" @@ -41,7 +41,7 @@ object PeersSpec { * The `Peers` message is a reply to a `GetPeer` message and relays connection information about peers * on the network. */ -class PeersSpec(peersLimit: Int) extends MessageSpecV1[Seq[PeerSpec]] { +class PeersSpec(peersLimit: Int) extends MessageSpecInitial[Seq[PeerSpec]] { override val messageCode: MessageCode = PeersSpec.messageCode @@ -64,7 +64,7 @@ class PeersSpec(peersLimit: Int) extends MessageSpecV1[Seq[PeerSpec]] { /** * The `GetSnapshotsInfo` message requests an `SnapshotsInfo` message from the receiving node */ -object GetSnapshotsInfoSpec extends MessageSpecV1[Unit] { +object GetSnapshotsInfoSpec extends MessageSpecInitial[Unit] { private val SizeLimit = 100 override val messageCode: MessageCode = 76: Byte @@ -83,7 +83,7 @@ object GetSnapshotsInfoSpec extends MessageSpecV1[Unit] { * The `SnapshotsInfo` message is a reply to a `GetSnapshotsInfo` message. * It contains information about UTXO set snapshots stored locally. */ -object SnapshotsInfoSpec extends MessageSpecV1[SnapshotsInfo] { +object SnapshotsInfoSpec extends MessageSpecInitial[SnapshotsInfo] { private val SizeLimit = 20000 override val messageCode: MessageCode = 77: Byte @@ -115,7 +115,7 @@ object SnapshotsInfoSpec extends MessageSpecV1[SnapshotsInfo] { /** * The `GetManifest` sends manifest (BatchAVLProverManifest) identifier */ -object GetManifestSpec extends MessageSpecV1[ManifestId] { +object GetManifestSpec extends MessageSpecInitial[ManifestId] { private val SizeLimit = 100 override val messageCode: MessageCode = 78: Byte @@ -136,7 +136,7 @@ object GetManifestSpec extends MessageSpecV1[ManifestId] { * The `Manifest` message is a reply to a `GetManifest` message. * It contains serialized manifest, top subtree of a tree authenticating UTXO set snapshot */ -object ManifestSpec extends MessageSpecV1[Array[Byte]] { +object ManifestSpec extends MessageSpecInitial[Array[Byte]] { private val SizeLimit = 4000000 override val messageCode: MessageCode = 79: Byte @@ -160,7 +160,7 @@ object ManifestSpec extends MessageSpecV1[Array[Byte]] { /** * The `GetUtxoSnapshotChunk` sends send utxo subtree (BatchAVLProverSubtree) identifier */ -object GetUtxoSnapshotChunkSpec extends MessageSpecV1[SubtreeId] { +object GetUtxoSnapshotChunkSpec extends MessageSpecInitial[SubtreeId] { private val SizeLimit = 100 override val messageCode: MessageCode = 80: Byte @@ -181,7 +181,7 @@ object GetUtxoSnapshotChunkSpec extends MessageSpecV1[SubtreeId] { /** * The `UtxoSnapshotChunk` message is a reply to a `GetUtxoSnapshotChunk` message. */ -object UtxoSnapshotChunkSpec extends MessageSpecV1[Array[Byte]] { +object UtxoSnapshotChunkSpec extends MessageSpecInitial[Array[Byte]] { private val SizeLimit = 4000000 override val messageCode: MessageCode = 81: Byte From 4192c61331accab97c550f376a6f95c178ff715b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 14 Aug 2024 17:18:14 +0300 Subject: [PATCH 031/109] merkle proof added to SubBlockInfo, serializer updated, SubBlockMessageSpec externalized --- .../org/ergoplatform/SubBlockAlgos.scala | 60 +------------------ .../subblocks/SubBlockMessageSpec.scala | 26 ++++++++ .../ergoplatform/subblocks/SubBlockInfo.scala | 56 +++++++++++++++++ 3 files changed, 84 insertions(+), 58 deletions(-) create mode 100644 ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala create mode 100644 ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index 1873fb183e..c941946ab0 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -1,13 +1,11 @@ package org.ergoplatform -import org.ergoplatform.SubBlockAlgos.SubBlockInfo import org.ergoplatform.mining.AutolykosPowScheme -import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} +import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.network.message.MessageConstants.MessageCode import org.ergoplatform.network.message.MessageSpecInitial -import org.ergoplatform.serialization.ErgoSerializer import org.ergoplatform.settings.{Constants, Parameters} -import scorex.crypto.hash.Digest32 +import org.ergoplatform.subblocks.SubBlockInfo import scorex.util.Extensions._ import scorex.util.serialization.{Reader, Writer} import scorex.util.{ModifierId, bytesToId, idToBytes} @@ -82,61 +80,7 @@ object SubBlockAlgos { // previous sub block id // transactions since last sub blocks - /** - * Sub-block message, sent by the node to peers when a sub-block is generated - * @param version - message version E(to allow injecting new fields) - * @param subBlock - subblock - * @param prevSubBlockId - previous sub block id `subBlock` is following, if missed, sub-block is linked - * to a previous block - */ - case class SubBlockInfo(version: Byte, subBlock: Header, prevSubBlockId: Option[Array[Byte]]) { - def transactionsConfirmedDigest: Digest32 = subBlock.transactionsRoot - def subblockTransactionsDigest: Digest32 = ??? // read from extension - } - - object SubBlockInfo { - - val initialMessageVersion = 1 - - def serializer: ErgoSerializer[SubBlockInfo] = new ErgoSerializer[SubBlockInfo] { - override def serialize(sbi: SubBlockInfo, w: Writer): Unit = { - w.put(sbi.version) - HeaderSerializer.serialize(sbi.subBlock, w) - w.putOption(sbi.prevSubBlockId){case (w, id) => w.putBytes(id)} - } - - override def parse(r: Reader): SubBlockInfo = { - val version = r.getByte() - if (version == initialMessageVersion) { - val subBlock = HeaderSerializer.parse(r) - val prevSubBlockId = r.getOption(r.getBytes(Constants.ModifierIdSize)) - new SubBlockInfo(version, subBlock, prevSubBlockId) - } else { - throw new Exception("Unsupported sub-block message version") - } - } - } - } - - /** - * Message that is informing about sub block produced. - * Contains header and link to previous sub block (). - */ - object SubBlockMessageSpec extends MessageSpecInitial[SubBlockInfo] { - - val MaxMessageSize = 10000 - override val messageCode: MessageCode = 90: Byte - override val messageName: String = "SubBlock" - - override def serialize(data: SubBlockInfo, w: Writer): Unit = { - SubBlockInfo.serializer.serialize(data, w) - } - - override def parse(r: Reader): SubBlockInfo = { - SubBlockInfo.serializer.parse(r) - } - } /** * On receiving sub block or block, the node is sending last sub block or block id it has to get short transaction diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala new file mode 100644 index 0000000000..508e9487cc --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala @@ -0,0 +1,26 @@ +package org.ergoplatform.network.message.subblocks + +import org.ergoplatform.network.message.MessageConstants.MessageCode +import org.ergoplatform.network.message.MessageSpecInitial +import org.ergoplatform.subblocks.SubBlockInfo +import scorex.util.serialization.{Reader, Writer} + +/** + * Message that is informing about sub block produced. + * Contains header and link to previous sub block (). + */ +object SubBlockMessageSpec extends MessageSpecInitial[SubBlockInfo] { + + val MaxMessageSize = 10000 + + override val messageCode: MessageCode = 90: Byte + override val messageName: String = "SubBlock" + + override def serialize(data: SubBlockInfo, w: Writer): Unit = { + SubBlockInfo.serializer.serialize(data, w) + } + + override def parse(r: Reader): SubBlockInfo = { + SubBlockInfo.serializer.parse(r) + } +} diff --git a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala new file mode 100644 index 0000000000..7281ea1075 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala @@ -0,0 +1,56 @@ +package org.ergoplatform.subblocks + +import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} +import org.ergoplatform.serialization.ErgoSerializer +import org.ergoplatform.settings.Constants +import scorex.crypto.authds.merkle.MerkleProof +import scorex.crypto.hash.Digest32 +import scorex.util.serialization.{Reader, Writer} + +/** + * Sub-block message, sent by the node to peers when a sub-block is generated + * + * @param version - message version E(to allow injecting new fields) + * @param subBlock - subblock + * @param prevSubBlockId - previous sub block id `subBlock` is following, if missed, sub-block is linked + * to a previous block + */ +case class SubBlockInfo(version: Byte, + subBlock: Header, + prevSubBlockId: Option[Array[Byte]], + subblockTransactionsDigest: Digest32, + merkleProof: MerkleProof[Digest32] // Merkle proof for both prevSubBlockId & subblockTransactionsDigest + ) { + // todo: implement Merkle proof serialization + // todo: implement data validity checks + + def transactionsConfirmedDigest: Digest32 = subBlock.transactionsRoot +} + +object SubBlockInfo { + + val initialMessageVersion = 1 + + def serializer: ErgoSerializer[SubBlockInfo] = new ErgoSerializer[SubBlockInfo] { + override def serialize(sbi: SubBlockInfo, w: Writer): Unit = { + w.put(sbi.version) + HeaderSerializer.serialize(sbi.subBlock, w) + w.putOption(sbi.prevSubBlockId){case (w, id) => w.putBytes(id)} + w.putBytes(sbi.subblockTransactionsDigest) + // todo: add Merkle proof serialization + } + + override def parse(r: Reader): SubBlockInfo = { + val version = r.getByte() + if (version == initialMessageVersion) { + val subBlock = HeaderSerializer.parse(r) + val prevSubBlockId = r.getOption(r.getBytes(Constants.ModifierIdSize)) + val subblockTransactionsDigest = Digest32 @@ r.getBytes(Constants.ModifierIdSize) + val merkleProof = null // parse Merkle proof + new SubBlockInfo(version, subBlock, prevSubBlockId, subblockTransactionsDigest, merkleProof) + } else { + throw new Exception("Unsupported sub-block message version") + } + } + } +} From a044955fef7b7340480754c9cc0e96e454e7336c Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 29 Aug 2024 13:35:14 +0300 Subject: [PATCH 032/109] batch merkle proof usage and serialization --- .../ergoplatform/subblocks/SubBlockInfo.scala | 25 +++++++++++++------ .../history/ExtensionCandidateTest.scala | 6 ++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala index 7281ea1075..0b845f375c 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala @@ -3,8 +3,10 @@ package org.ergoplatform.subblocks import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} import org.ergoplatform.serialization.ErgoSerializer import org.ergoplatform.settings.Constants -import scorex.crypto.authds.merkle.MerkleProof -import scorex.crypto.hash.Digest32 +import scorex.crypto.authds.merkle.BatchMerkleProof +import scorex.crypto.authds.merkle.serialization.BatchMerkleProofSerializer +import scorex.crypto.hash.{Blake2b, Blake2b256, CryptographicHash, Digest32} +import scorex.util.Extensions.IntOps import scorex.util.serialization.{Reader, Writer} /** @@ -19,10 +21,13 @@ case class SubBlockInfo(version: Byte, subBlock: Header, prevSubBlockId: Option[Array[Byte]], subblockTransactionsDigest: Digest32, - merkleProof: MerkleProof[Digest32] // Merkle proof for both prevSubBlockId & subblockTransactionsDigest + merkleProof: BatchMerkleProof[Digest32] // Merkle proof for both prevSubBlockId & subblockTransactionsDigest ) { - // todo: implement Merkle proof serialization - // todo: implement data validity checks + + def valid(): Boolean = { + // todo: implement data validity checks + false + } def transactionsConfirmedDigest: Digest32 = subBlock.transactionsRoot } @@ -31,13 +36,17 @@ object SubBlockInfo { val initialMessageVersion = 1 + private val bmp = new BatchMerkleProofSerializer[Digest32, CryptographicHash[Digest32]]()(Blake2b256) + def serializer: ErgoSerializer[SubBlockInfo] = new ErgoSerializer[SubBlockInfo] { override def serialize(sbi: SubBlockInfo, w: Writer): Unit = { w.put(sbi.version) HeaderSerializer.serialize(sbi.subBlock, w) w.putOption(sbi.prevSubBlockId){case (w, id) => w.putBytes(id)} w.putBytes(sbi.subblockTransactionsDigest) - // todo: add Merkle proof serialization + val proof = bmp.serialize(sbi.merkleProof) + w.putUShort(proof.length.toShort) + w.putBytes(proof) } override def parse(r: Reader): SubBlockInfo = { @@ -46,7 +55,9 @@ object SubBlockInfo { val subBlock = HeaderSerializer.parse(r) val prevSubBlockId = r.getOption(r.getBytes(Constants.ModifierIdSize)) val subblockTransactionsDigest = Digest32 @@ r.getBytes(Constants.ModifierIdSize) - val merkleProof = null // parse Merkle proof + val merkleProofSize = r.getUShort().toShortExact + val merkleProofBytes = r.getBytes(merkleProofSize) + val merkleProof = bmp.deserialize(merkleProofBytes).get // parse Merkle proof new SubBlockInfo(version, subBlock, prevSubBlockId, subblockTransactionsDigest, merkleProof) } else { throw new Exception("Unsupported sub-block message version") diff --git a/ergo-core/src/test/scala/org/ergoplatform/modifiers/history/ExtensionCandidateTest.scala b/ergo-core/src/test/scala/org/ergoplatform/modifiers/history/ExtensionCandidateTest.scala index cff39a1c2e..5dc1a9c7f3 100644 --- a/ergo-core/src/test/scala/org/ergoplatform/modifiers/history/ExtensionCandidateTest.scala +++ b/ergo-core/src/test/scala/org/ergoplatform/modifiers/history/ExtensionCandidateTest.scala @@ -33,9 +33,9 @@ class ExtensionCandidateTest extends ErgoCorePropertyTest { val fields = NipopowAlgos.packInterlinks(modifiers) val ext = ExtensionCandidate(fields) - val proof = ext.batchProofFor(fields.map(_._1.clone).toArray: _*) - proof shouldBe defined - proof.get.valid(ext.interlinksDigest) shouldBe true + val proofOpt = ext.batchProofFor(fields.map(_._1.clone).toArray: _*) + proofOpt shouldBe defined + proofOpt.get.valid(ext.interlinksDigest) shouldBe true } } } From c382a381389f79f7b23638f3342c6484ec348329 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 3 Sep 2024 15:50:39 +0300 Subject: [PATCH 033/109] processSubblock started --- .../ergoplatform/subblocks/SubBlockInfo.scala | 4 +++ .../network/ErgoNodeViewSynchronizer.scala | 27 ++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala index 0b845f375c..eb1ad0c820 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala @@ -16,6 +16,10 @@ import scorex.util.serialization.{Reader, Writer} * @param subBlock - subblock * @param prevSubBlockId - previous sub block id `subBlock` is following, if missed, sub-block is linked * to a previous block + * @param subblockTransactionsDigest - digest of new transactions appeared in subblock + * @param merkleProof - batch Merkle proof for `prevSubBlockId`` and `subblockTransactionsDigest` + * (as they are coming from extension section, and committed in `subBlock` header via extension + * digest) */ case class SubBlockInfo(version: Byte, subBlock: Header, diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index 2feec87f97..9f2385c752 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -5,9 +5,8 @@ import akka.actor.{Actor, ActorInitializationException, ActorKilledException, Ac import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} import org.ergoplatform.modifiers.mempool.{ErgoTransaction, ErgoTransactionSerializer, UnconfirmedTransaction} import org.ergoplatform.modifiers.{BlockSection, ErgoNodeViewModifier, ManifestTypeId, NetworkObjectTypeId, SnapshotsInfoTypeId, UtxoSnapshotChunkTypeId} -import org.ergoplatform.nodeView.history.{ErgoSyncInfoV1, ErgoSyncInfoV2} +import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader, ErgoSyncInfo, ErgoSyncInfoMessageSpec, ErgoSyncInfoV1, ErgoSyncInfoV2} import org.ergoplatform.nodeView.ErgoNodeViewHolder.BlockAppliedTransactions -import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoSyncInfo, ErgoSyncInfoMessageSpec} import org.ergoplatform.nodeView.mempool.ErgoMemPool import org.ergoplatform.settings.{Algos, ErgoSettings, NetworkSettings} import org.ergoplatform.nodeView.ErgoNodeViewHolder._ @@ -24,7 +23,7 @@ import scorex.core.network.{ConnectedPeer, ModifiersStatus, SendToPeer, SendToPe import org.ergoplatform.network.message.{InvData, Message, ModifiersData} import org.ergoplatform.utils.ScorexEncoder import org.ergoplatform.validation.MalformedModifierError -import scorex.util.{ModifierId, ScorexLogging} +import scorex.util.{ModifierId, ScorexLogging, bytesToId} import scorex.core.network.DeliveryTracker import org.ergoplatform.network.peer.PenaltyType import scorex.crypto.hash.Digest32 @@ -34,7 +33,9 @@ import org.ergoplatform.consensus.{Equal, Fork, Nonsense, Older, Unknown, Younge import org.ergoplatform.modifiers.history.{ADProofs, ADProofsSerializer, BlockTransactions, BlockTransactionsSerializer} import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionSerializer} import org.ergoplatform.modifiers.transaction.TooHighCostError +import org.ergoplatform.network.message.subblocks.SubBlockMessageSpec import org.ergoplatform.serialization.{ErgoSerializer, ManifestSerializer, SubtreeSerializer} +import org.ergoplatform.subblocks.SubBlockInfo import scorex.crypto.authds.avltree.batch.VersionedLDBAVLStorage.splitDigest import scala.annotation.tailrec @@ -1075,6 +1076,21 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } } + def processSubblock(subBlockInfo: SubBlockInfo, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { + if(subBlockInfo.valid()) { + val prevSbIdOpt = subBlockInfo.prevSubBlockId.map(bytesToId) // link to previous sub-block + + prevSbIdOpt match { + case Some(prevSubBlockId) => + log.debug(s"Processing valid sub-block ${subBlockInfo.subBlock.id} with parent sub-block ${prevSubBlockId}") + case None => + log.debug(s"Processing valid sub-block ${subBlockInfo.subBlock.id} with parent block ${subBlockInfo.subBlock.parentId}") + } + } else { + log.warn(s"Sub-block ${subBlockInfo.subBlock.id} is invalid") + penalizeMisbehavingPeer(remote) + } + } /** * Object ids coming from other node. @@ -1494,6 +1510,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, modifiersReq(hr, mp, data, remote) case (_: ModifiersSpec.type, data: ModifiersData, remote) => modifiersFromRemote(hr, mp, data, remote, blockAppliedTxsCache) + // UTXO snapshot related messages case (spec: MessageSpec[_], _, remote) if spec.messageCode == GetSnapshotsInfoSpec.messageCode => usrOpt match { case Some(usr) => sendSnapshotsInfo(usr, remote) @@ -1518,10 +1535,14 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, case Some(_) => processUtxoSnapshotChunk(serializedChunk, hr, remote) case None => log.warn(s"Asked for snapshot when UTXO set is not supported, remote: $remote") } + // Nipopows related messages case (_: GetNipopowProofSpec.type, data: NipopowProofData, remote) => sendNipopowProof(data, hr, remote) case (_: NipopowProofSpec.type , proofBytes: Array[Byte], remote) => processNipopowProof(proofBytes, hr, remote) + // Sub-blocks related messages + case (_: SubBlockMessageSpec.type, subBlockInfo: SubBlockInfo, remote) => + processSubblock(subBlockInfo, hr, remote) } def initialized(hr: ErgoHistory, From 15ca997b1f2182f7a6a5e29c942ab12a49f7ad5b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 4 Sep 2024 00:55:59 +0300 Subject: [PATCH 034/109] subblock height check --- .../network/ErgoNodeViewSynchronizer.scala | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index 9f2385c752..93cdf1deca 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -1077,18 +1077,24 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } def processSubblock(subBlockInfo: SubBlockInfo, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { - if(subBlockInfo.valid()) { - val prevSbIdOpt = subBlockInfo.prevSubBlockId.map(bytesToId) // link to previous sub-block - - prevSbIdOpt match { - case Some(prevSubBlockId) => - log.debug(s"Processing valid sub-block ${subBlockInfo.subBlock.id} with parent sub-block ${prevSubBlockId}") - case None => - log.debug(s"Processing valid sub-block ${subBlockInfo.subBlock.id} with parent block ${subBlockInfo.subBlock.parentId}") + val subBlockHeader = subBlockInfo.subBlock + if (subBlockHeader.height == hr.fullBlockHeight + 1) { + if (subBlockInfo.valid()) { + val prevSbIdOpt = subBlockInfo.prevSubBlockId.map(bytesToId) // link to previous sub-block + + prevSbIdOpt match { + case Some(prevSubBlockId) => + log.debug(s"Processing valid sub-block ${subBlockHeader.id} with parent sub-block ${prevSubBlockId}") + case None => + log.debug(s"Processing valid sub-block ${subBlockHeader.id} with parent block ${subBlockHeader.parentId}") + } + } else { + log.warn(s"Sub-block ${subBlockHeader.id} is invalid") + penalizeMisbehavingPeer(remote) } } else { - log.warn(s"Sub-block ${subBlockInfo.subBlock.id} is invalid") - penalizeMisbehavingPeer(remote) + log.info(s"Got sub-block for height ${subBlockHeader.height}, while height of our best full-block is ${hr.fullBlockHeight}") + // just ignore the subblock } } From 222c8507c3214cd6ce2fee0eb1252d4294eb57c1 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 4 Sep 2024 13:41:34 +0300 Subject: [PATCH 035/109] SubBlockTransactionsRequestSpec, completing processSubblock stub --- .../subblocks/SubBlockMessageSpec.scala | 4 +-- .../SubBlockTransactionsRequestSpec.scala | 26 +++++++++++++++++++ .../network/ErgoNodeViewSynchronizer.scala | 14 +++++----- .../ErgoNodeViewSynchronizerMessages.scala | 3 +++ .../nodeView/ErgoNodeViewHolder.scala | 4 +++ .../nodeView/history/ErgoHistoryReader.scala | 3 ++- .../modifierprocessors/PopowProcessor.scala | 2 +- .../SubBlocksProcessor.scala | 11 ++++++++ 8 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestSpec.scala create mode 100644 src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala index 508e9487cc..647c415fa6 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala @@ -1,7 +1,7 @@ package org.ergoplatform.network.message.subblocks import org.ergoplatform.network.message.MessageConstants.MessageCode -import org.ergoplatform.network.message.MessageSpecInitial +import org.ergoplatform.network.message.{InvData, MessageSpecInitial, MessageSpecSubblocks} import org.ergoplatform.subblocks.SubBlockInfo import scorex.util.serialization.{Reader, Writer} @@ -9,7 +9,7 @@ import scorex.util.serialization.{Reader, Writer} * Message that is informing about sub block produced. * Contains header and link to previous sub block (). */ -object SubBlockMessageSpec extends MessageSpecInitial[SubBlockInfo] { +object SubBlockMessageSpec extends MessageSpecSubblocks[SubBlockInfo] { val MaxMessageSize = 10000 diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestSpec.scala new file mode 100644 index 0000000000..300c44d232 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestSpec.scala @@ -0,0 +1,26 @@ +package org.ergoplatform.network.message.subblocks + +import org.ergoplatform.network.message.MessageConstants.MessageCode +import org.ergoplatform.network.message.MessageSpecSubblocks +import scorex.util.{ModifierId, bytesToId, idToBytes} +import scorex.util.serialization.{Reader, Writer} + +object SubBlockTransactionsRequestSpec extends MessageSpecSubblocks[ModifierId] { + /** + * Code which identifies what message type is contained in the payload + */ + override val messageCode: MessageCode = 91: Byte + + /** + * Name of this message type. For debug purposes only. + */ + override val messageName: String = "SubBlockTxsReq" + + override def serialize(subBlockId: ModifierId, w: Writer): Unit = { + w.putBytes(idToBytes(subBlockId)) + } + + override def parse(r: Reader): ModifierId = { + bytesToId(r.getBytes(32)) + } +} diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index 93cdf1deca..2957e16600 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -33,7 +33,7 @@ import org.ergoplatform.consensus.{Equal, Fork, Nonsense, Older, Unknown, Younge import org.ergoplatform.modifiers.history.{ADProofs, ADProofsSerializer, BlockTransactions, BlockTransactionsSerializer} import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionSerializer} import org.ergoplatform.modifiers.transaction.TooHighCostError -import org.ergoplatform.network.message.subblocks.SubBlockMessageSpec +import org.ergoplatform.network.message.subblocks.{SubBlockMessageSpec, SubBlockTransactionsRequestSpec} import org.ergoplatform.serialization.{ErgoSerializer, ManifestSerializer, SubtreeSerializer} import org.ergoplatform.subblocks.SubBlockInfo import scorex.crypto.authds.avltree.batch.VersionedLDBAVLStorage.splitDigest @@ -1081,13 +1081,11 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, if (subBlockHeader.height == hr.fullBlockHeight + 1) { if (subBlockInfo.valid()) { val prevSbIdOpt = subBlockInfo.prevSubBlockId.map(bytesToId) // link to previous sub-block - - prevSbIdOpt match { - case Some(prevSubBlockId) => - log.debug(s"Processing valid sub-block ${subBlockHeader.id} with parent sub-block ${prevSubBlockId}") - case None => - log.debug(s"Processing valid sub-block ${subBlockHeader.id} with parent block ${subBlockHeader.parentId}") - } + log.debug(s"Processing valid sub-block ${subBlockHeader.id} with parent sub-block $prevSbIdOpt and parent block ${subBlockHeader.parentId}") + // write sub-block to db, ask for transactions in it + viewHolderRef ! ProcessSubblock(subBlockInfo) + val msg = Message(SubBlockTransactionsRequestSpec, Right(subBlockInfo.subBlock.id), None) + networkControllerRef ! SendToNetwork(msg, SendToPeer(remote)) } else { log.warn(s"Sub-block ${subBlockHeader.id} is invalid") penalizeMisbehavingPeer(remote) diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala index e1cc4de78d..b29c2c0cde 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala @@ -11,6 +11,7 @@ import scorex.core.network.ConnectedPeer import scorex.util.ModifierId import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform.modifiers.history.popow.NipopowProof +import org.ergoplatform.subblocks.SubBlockInfo /** * Repository of messages processed ErgoNodeViewSynchronizer actor @@ -142,4 +143,6 @@ object ErgoNodeViewSynchronizerMessages { * @param nipopowProof - proof to initialize history from */ case class ProcessNipopow(nipopowProof: NipopowProof) + + case class ProcessSubblock(subblock: SubBlockInfo) } diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index f955cf61f9..468e9cb017 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -301,6 +301,10 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti updateNodeView(updatedHistory = Some(history())) } } + + // subblocks related logic + case ProcessSubblock(sbi) => + history().applySubBlockHeader(sbi) } /** diff --git a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala index 044a967590..a0cb5a0eea 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala @@ -9,7 +9,7 @@ import org.ergoplatform.modifiers.{BlockSection, ErgoFullBlock, NetworkObjectTyp import org.ergoplatform.nodeView.history.ErgoHistoryUtils.{EmptyHistoryHeight, GenesisHeight, Height} import org.ergoplatform.nodeView.history.extra.ExtraIndex import org.ergoplatform.nodeView.history.storage._ -import org.ergoplatform.nodeView.history.storage.modifierprocessors.{BlockSectionProcessor, HeadersProcessor} +import org.ergoplatform.nodeView.history.storage.modifierprocessors.{BlockSectionProcessor, HeadersProcessor, SubBlocksProcessor} import org.ergoplatform.settings.{ErgoSettings, NipopowSettings} import org.ergoplatform.validation.MalformedModifierError import scorex.util.{ModifierId, ScorexLogging} @@ -26,6 +26,7 @@ trait ErgoHistoryReader with ContainsModifiers[BlockSection] with HeadersProcessor with BlockSectionProcessor + with SubBlocksProcessor with ScorexLogging { type ModifierIds = Seq[(NetworkObjectTypeId.Value, ModifierId)] diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/PopowProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/PopowProcessor.scala index 59922347a3..881330a7e7 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/PopowProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/PopowProcessor.scala @@ -5,7 +5,7 @@ import org.ergoplatform.local.{CorrectNipopowProofVerificationResult, NipopowPro import org.ergoplatform.modifiers.BlockSection import org.ergoplatform.modifiers.history.extension.Extension import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.modifiers.history.popow.{NipopowAlgos, NipopowProverWithDbAlgs, NipopowProof, NipopowProofSerializer, PoPowHeader, PoPowParams} +import org.ergoplatform.modifiers.history.popow.{NipopowAlgos, NipopowProof, NipopowProofSerializer, NipopowProverWithDbAlgs, PoPowHeader, PoPowParams} import org.ergoplatform.nodeView.history.ErgoHistoryUtils import org.ergoplatform.nodeView.history.ErgoHistoryReader import org.ergoplatform.settings.{ChainSettings, NipopowSettings} diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala new file mode 100644 index 0000000000..8d66447152 --- /dev/null +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala @@ -0,0 +1,11 @@ +package org.ergoplatform.nodeView.history.storage.modifierprocessors + +import org.ergoplatform.subblocks.SubBlockInfo + +trait SubBlocksProcessor { + + // sub-blocks related logic + def applySubBlockHeader(sbi: SubBlockInfo): Unit = { + // todo: implement + } +} From e6ee392974484033ce7aa64ba71cf76ae3002f30 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 5 Sep 2024 20:46:34 +0300 Subject: [PATCH 036/109] SubBlockTransactionsSpec --- .../subblocks/SubBlockTransactionsData.scala | 9 +++++ .../subblocks/SubBlockTransactionsSpec.scala | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsData.scala create mode 100644 ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsSpec.scala diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsData.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsData.scala new file mode 100644 index 0000000000..51b8cc204a --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsData.scala @@ -0,0 +1,9 @@ +package org.ergoplatform.network.message.subblocks + +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import scorex.util.ModifierId + +// todo: send transactions or transactions id ? +case class SubBlockTransactionsData(subblockID: ModifierId, transactions: Seq[ErgoTransaction]){ + +} diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsSpec.scala new file mode 100644 index 0000000000..3ebc8eda16 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsSpec.scala @@ -0,0 +1,36 @@ +package org.ergoplatform.network.message.subblocks + +import org.ergoplatform.modifiers.mempool.ErgoTransactionSerializer +import org.ergoplatform.network.message.MessageConstants.MessageCode +import org.ergoplatform.network.message.MessageSpecSubblocks +import scorex.util.{bytesToId, idToBytes} +import scorex.util.serialization.{Reader, Writer} +import sigma.util.Extensions.LongOps + +object SubBlockTransactionsSpec extends MessageSpecSubblocks[SubBlockTransactionsData]{ + /** + * Code which identifies what message type is contained in the payload + */ + override val messageCode: MessageCode = 92: Byte + /** + * Name of this message type. For debug purposes only. + */ + override val messageName: String = "SubBlockTxs" + + override def serialize(obj: SubBlockTransactionsData, w: Writer): Unit = { + w.putBytes(idToBytes(obj.subblockID)) + w.putUInt(obj.transactions.size) + obj.transactions.foreach { tx => + ErgoTransactionSerializer.serialize(tx, w) + } + } + + override def parse(r: Reader): SubBlockTransactionsData = { + val subBlockId = bytesToId(r.getBytes(32)) + val txsCount = r.getUInt().toIntExact + val transactions = (1 to txsCount).map{_ => + ErgoTransactionSerializer.parse(r) + } + SubBlockTransactionsData(subBlockId, transactions) + } +} From fb98a2e8d05a33c4c1e916b09f3abb5bc55db81a Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sun, 8 Sep 2024 11:56:13 +0300 Subject: [PATCH 037/109] subblocks p2p logic stub --- ... => SubBlockTransactionsMessageSpec.scala} | 3 +- ...BlockTransactionsRequestMessageSpec.scala} | 2 +- .../network/ErgoNodeViewSynchronizer.scala | 30 +++++++++++++++++-- .../ErgoNodeViewSynchronizerMessages.scala | 3 ++ .../nodeView/ErgoNodeViewHolder.scala | 3 ++ .../SubBlocksProcessor.scala | 14 +++++++++ 6 files changed, 50 insertions(+), 5 deletions(-) rename ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/{SubBlockTransactionsSpec.scala => SubBlockTransactionsMessageSpec.scala} (92%) rename ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/{SubBlockTransactionsRequestSpec.scala => SubBlockTransactionsRequestMessageSpec.scala} (89%) diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsMessageSpec.scala similarity index 92% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsSpec.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsMessageSpec.scala index 3ebc8eda16..0abb2be62d 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsMessageSpec.scala @@ -7,7 +7,7 @@ import scorex.util.{bytesToId, idToBytes} import scorex.util.serialization.{Reader, Writer} import sigma.util.Extensions.LongOps -object SubBlockTransactionsSpec extends MessageSpecSubblocks[SubBlockTransactionsData]{ +object SubBlockTransactionsMessageSpec extends MessageSpecSubblocks[SubBlockTransactionsData]{ /** * Code which identifies what message type is contained in the payload */ @@ -33,4 +33,5 @@ object SubBlockTransactionsSpec extends MessageSpecSubblocks[SubBlockTransaction } SubBlockTransactionsData(subBlockId, transactions) } + } diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestMessageSpec.scala similarity index 89% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestSpec.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestMessageSpec.scala index 300c44d232..ff9c057fa5 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestMessageSpec.scala @@ -5,7 +5,7 @@ import org.ergoplatform.network.message.MessageSpecSubblocks import scorex.util.{ModifierId, bytesToId, idToBytes} import scorex.util.serialization.{Reader, Writer} -object SubBlockTransactionsRequestSpec extends MessageSpecSubblocks[ModifierId] { +object SubBlockTransactionsRequestMessageSpec extends MessageSpecSubblocks[ModifierId] { /** * Code which identifies what message type is contained in the payload */ diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index 2957e16600..eb0da81dd8 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -33,7 +33,7 @@ import org.ergoplatform.consensus.{Equal, Fork, Nonsense, Older, Unknown, Younge import org.ergoplatform.modifiers.history.{ADProofs, ADProofsSerializer, BlockTransactions, BlockTransactionsSerializer} import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionSerializer} import org.ergoplatform.modifiers.transaction.TooHighCostError -import org.ergoplatform.network.message.subblocks.{SubBlockMessageSpec, SubBlockTransactionsRequestSpec} +import org.ergoplatform.network.message.subblocks.{SubBlockMessageSpec, SubBlockTransactionsData, SubBlockTransactionsMessageSpec, SubBlockTransactionsRequestMessageSpec} import org.ergoplatform.serialization.{ErgoSerializer, ManifestSerializer, SubtreeSerializer} import org.ergoplatform.subblocks.SubBlockInfo import scorex.crypto.authds.avltree.batch.VersionedLDBAVLStorage.splitDigest @@ -1078,13 +1078,15 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, def processSubblock(subBlockInfo: SubBlockInfo, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { val subBlockHeader = subBlockInfo.subBlock + // apply sub-block if it is on current height if (subBlockHeader.height == hr.fullBlockHeight + 1) { - if (subBlockInfo.valid()) { + if (subBlockInfo.valid()) { // check PoW / Merkle proofs before processing val prevSbIdOpt = subBlockInfo.prevSubBlockId.map(bytesToId) // link to previous sub-block log.debug(s"Processing valid sub-block ${subBlockHeader.id} with parent sub-block $prevSbIdOpt and parent block ${subBlockHeader.parentId}") // write sub-block to db, ask for transactions in it viewHolderRef ! ProcessSubblock(subBlockInfo) - val msg = Message(SubBlockTransactionsRequestSpec, Right(subBlockInfo.subBlock.id), None) + // todo: ask for txs only if subblock's parent is a best subblock ? + val msg = Message(SubBlockTransactionsRequestMessageSpec, Right(subBlockInfo.subBlock.id), None) networkControllerRef ! SendToNetwork(msg, SendToPeer(remote)) } else { log.warn(s"Sub-block ${subBlockHeader.id} is invalid") @@ -1096,6 +1098,24 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } } + def processSubblockTransactionsRequest(subBlockId: ModifierId, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { + hr.getSubBlockTransactions(subBlockId) match { + case Some(transactions) => + val std = SubBlockTransactionsData(subBlockId, transactions) + val msg = Message(SubBlockTransactionsMessageSpec, Right(std), None) + networkControllerRef ! SendToNetwork(msg, SendToPeer(remote)) + case None => + log.warn(s"Transactions not found for requested sub block ${subBlockId}") + } + } + + def processSubblockTransactions(transactionsData: SubBlockTransactionsData, + hr: ErgoHistoryReader, + remote: ConnectedPeer): Unit = { + // todo: check if not spam, ie transaction were requested + viewHolderRef ! ProcessSubblockTransactions(transactionsData) + } + /** * Object ids coming from other node. * Filter out modifier ids that are already in process (requested, received or applied), @@ -1547,6 +1567,10 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, // Sub-blocks related messages case (_: SubBlockMessageSpec.type, subBlockInfo: SubBlockInfo, remote) => processSubblock(subBlockInfo, hr, remote) + case (_: SubBlockTransactionsRequestMessageSpec.type, subBlockId: String, remote) => + processSubblockTransactionsRequest(ModifierId @@ subBlockId, hr, remote) + case (_: SubBlockTransactionsMessageSpec.type, transactions: SubBlockTransactionsData, remote) => + processSubblockTransactions(transactions, hr, remote) } def initialized(hr: ErgoHistory, diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala index b29c2c0cde..79ba571569 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala @@ -11,6 +11,7 @@ import scorex.core.network.ConnectedPeer import scorex.util.ModifierId import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform.modifiers.history.popow.NipopowProof +import org.ergoplatform.network.message.subblocks.SubBlockTransactionsData import org.ergoplatform.subblocks.SubBlockInfo /** @@ -145,4 +146,6 @@ object ErgoNodeViewSynchronizerMessages { case class ProcessNipopow(nipopowProof: NipopowProof) case class ProcessSubblock(subblock: SubBlockInfo) + + case class ProcessSubblockTransactions(std: SubBlockTransactionsData) } diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 468e9cb017..9bc0973eb6 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -305,6 +305,9 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti // subblocks related logic case ProcessSubblock(sbi) => history().applySubBlockHeader(sbi) + + case ProcessSubblockTransactions(std) => + history().applySubBlockTransactions(std.subblockID, std.transactions) } /** diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala index 8d66447152..11a171ddf9 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala @@ -1,11 +1,25 @@ package org.ergoplatform.nodeView.history.storage.modifierprocessors +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.subblocks.SubBlockInfo +import scorex.util.ModifierId trait SubBlocksProcessor { + val subBlockRecords = Map[ModifierId, SubBlockInfo]() + val subBlockTransactions = Map[ModifierId, Seq[ErgoTransaction]]() + // sub-blocks related logic def applySubBlockHeader(sbi: SubBlockInfo): Unit = { // todo: implement } + + def applySubBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { + // todo: implement + } + + def getSubBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { + subBlockTransactions.get(sbId) + } + } From 95fb95ecc337bf6edc5bdbdcb9ab821e254bf5c0 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 13 Sep 2024 09:28:44 +0300 Subject: [PATCH 038/109] LocallyGeneratedSubBlock --- .../nodeView/LocallyGeneratedModifier.scala | 2 +- .../org/ergoplatform/mining/CandidateGenerator.scala | 5 ----- .../ergoplatform/nodeView/ErgoNodeViewHolder.scala | 11 ++++++++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedModifier.scala b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedModifier.scala index 712a185d35..27db8ed56f 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedModifier.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedModifier.scala @@ -5,4 +5,4 @@ import org.ergoplatform.modifiers.BlockSection /** * Wrapper for locally generated block section */ -case class LocallyGeneratedModifier(pmod: BlockSection) +case class LocallyGeneratedModifier(blockSection: BlockSection) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index a42b50e6db..c74f9c9ce4 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -410,10 +410,6 @@ object CandidateGenerator extends ScorexLogging { ergoSettings.votingTargets.softForkOption.getOrElse(0) == 1 } - //todo: remove after 5.0 soft-fork activation - log.debug(s"betterVersion: $betterVersion, forkVotingAllowed: $forkVotingAllowed, " + - s"forkOrdered: $forkOrdered, nextHeightCondition: $nextHeightCondition") - betterVersion && forkVotingAllowed && forkOrdered && @@ -427,7 +423,6 @@ object CandidateGenerator extends ScorexLogging { * @param history - blockchain reader (to extract parent) * @param proposedUpdate - votes for parameters update or/and soft-fork * @param state - UTXO set reader - * @param timeProvider - network time provider * @param poolTxs - memory pool transactions * @param emissionTxOpt - optional emission transaction * @param prioritizedTransactions - transactions which are going into the block in the first place diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 9bc0973eb6..15b5c67e8c 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -25,6 +25,7 @@ import spire.syntax.all.cfor import java.io.File import org.ergoplatform.modifiers.history.extension.Extension +import org.ergoplatform.subblocks.SubBlockInfo import scala.annotation.tailrec import scala.collection.mutable @@ -229,7 +230,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti case (success@Success(updateInfo), modToApply) => if (updateInfo.failedMod.isEmpty) { val chainTipOpt = history.estimatedTip() - updateInfo.state.applyModifier(modToApply, chainTipOpt)(lm => pmodModify(lm.pmod, local = true)) match { + updateInfo.state.applyModifier(modToApply, chainTipOpt)(lm => pmodModify(lm.blockSection, local = true)) match { case Success(stateAfterApply) => history.reportModifierIsValid(modToApply).map { newHis => if (modToApply.modifierTypeId == ErgoFullBlock.modifierTypeId) { @@ -670,8 +671,8 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti protected def processLocallyGeneratedModifiers: Receive = { case lm: LocallyGeneratedModifier => - log.info(s"Got locally generated modifier ${lm.pmod.encodedId} of type ${lm.pmod.modifierTypeId}") - pmodModify(lm.pmod, local = true) + log.info(s"Got locally generated modifier ${lm.blockSection.encodedId} of type ${lm.blockSection.modifierTypeId}") + pmodModify(lm.blockSection, local = true) } protected def getCurrentInfo: Receive = { @@ -724,6 +725,10 @@ object ErgoNodeViewHolder { // Modifiers received from the remote peer with new elements in it case class ModifiersFromRemote(modifiers: Iterable[BlockSection]) + /** + * Wrapper for a locally generated sub-block submitted via API + */ + case class LocallyGeneratedSubBlock(sbi: SubBlockInfo) /** * Wrapper for a transaction submitted via API From 5f66580126244e14e431038eafd61ecb9ae2fa49 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 13 Sep 2024 12:07:53 +0300 Subject: [PATCH 039/109] subblock keys, CandidateGenerator simplification --- .../history/extension/Extension.scala | 6 +++++ .../mining/CandidateGenerator.scala | 27 +++---------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala b/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala index b64fd2974b..4f48ecc771 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala @@ -76,11 +76,17 @@ object Extension extends ApiCodecs { */ val SubBlocksDataPrefix: Byte = 0x03 + val PrevSubBlockIdKey: Array[Byte] = Array(SubBlocksDataPrefix, 0x00) + + val SubBlockTransactionsDigestKey: Array[Byte] = Array(SubBlocksDataPrefix, 0x01) + /** * Prefix for keys related to sidechains data. */ val SidechainsDataPrefix: Byte = 0x04 + + /** * Id a type of network object encoding extension */ diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index c74f9c9ce4..743756bd84 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -77,13 +77,7 @@ class CandidateGenerator( /** first we need to get Readers to have some initial state to work with */ case Readers(h, s: UtxoStateReader, m, _) => val lastHeaders = h.lastHeaders(500).headers - val avgMiningTime = getBlockMiningTimeAvg(lastHeaders.map(_.timestamp)) - val avgTxsCount = getTxsPerBlockCountAvg( - lastHeaders.flatMap(h.getFullBlock).map(_.transactions.size) - ) - log.info( - s"CandidateGenerator initialized, avgMiningTime: ${avgMiningTime.toSeconds}s, avgTxsCount: $avgTxsCount" - ) + log.info(s"CandidateGenerator initialized") context.become( initialized( CandidateGeneratorState( @@ -297,22 +291,6 @@ object CandidateGenerator extends ScorexLogging { solvedBlock.nonEmpty && !solvedBlock.map(_.parentId).contains(bestFullBlockId) } - /** Calculate average mining time from latest block header timestamps */ - def getBlockMiningTimeAvg( - timestamps: IndexedSeq[Header.Timestamp] - ): FiniteDuration = { - val miningTimes = - timestamps.sorted - .sliding(2, 1) - .map { case IndexedSeq(prev, next) => next - prev } - .toVector - Math.round(miningTimes.sum / miningTimes.length.toDouble).millis - } - - /** Get average count of transactions per block */ - def getTxsPerBlockCountAvg(txsPerBlock: IndexedSeq[Int]): Long = - Math.round(txsPerBlock.sum / txsPerBlock.length.toDouble) - /** Helper which is checking that inputs of the transaction are not spent */ private def inputsNotSpent(tx: ErgoTransaction, s: UtxoStateReader): Boolean = tx.inputs.forall(inp => s.boxById(inp.boxId).isDefined) @@ -441,7 +419,8 @@ object CandidateGenerator extends ScorexLogging { ): Try[(Candidate, EliminateTransactions)] = Try { val popowAlgos = new NipopowAlgos(ergoSettings.chainSettings) - // Extract best header and extension of a best block user their data for assembling a new block + + // Extract best header and extension of a best block for assembling a new block val bestHeaderOpt: Option[Header] = history.bestFullBlockOpt.map(_.header) val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) From f118879fc89547cd36fdf955d78a9e0188661cb3 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 13 Sep 2024 13:14:39 +0300 Subject: [PATCH 040/109] more simplification in CandidateGenerator --- .../ergoplatform/mining/CandidateGenerator.scala | 11 +++++++---- .../org/ergoplatform/mining/ErgoMiningThread.scala | 4 ++-- .../mining/CandidateGeneratorPropSpec.scala | 13 ------------- src/test/scala/org/ergoplatform/utils/Stubs.scala | 2 +- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 743756bd84..401887f091 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -76,7 +76,6 @@ class CandidateGenerator( /** first we need to get Readers to have some initial state to work with */ case Readers(h, s: UtxoStateReader, m, _) => - val lastHeaders = h.lastHeaders(500).headers log.info(s"CandidateGenerator initialized") context.become( initialized( @@ -226,7 +225,8 @@ object CandidateGenerator extends ScorexLogging { case class Candidate( candidateBlock: CandidateBlock, externalVersion: WorkMessage, - txsToInclude: Seq[ErgoTransaction] + txsToInclude: Seq[ErgoTransaction], + subBlock: Boolean ) case class GenerateCandidate( @@ -546,7 +546,7 @@ object CandidateGenerator extends ScorexLogging { s" with ${candidate.transactions.size} transactions, msg ${Base16.encode(ext.msg)}" ) Success( - Candidate(candidate, ext, prioritizedTransactions) -> eliminateTransactions + Candidate(candidate, ext, prioritizedTransactions, subBlock = false) -> eliminateTransactions ) case Failure(t: Throwable) => // We can not produce a block for some reason, so print out an error @@ -575,7 +575,8 @@ object CandidateGenerator extends ScorexLogging { Candidate( candidate, deriveWorkMessage(candidate), - prioritizedTransactions + prioritizedTransactions, + subBlock = false ) -> eliminateTransactions } case None => @@ -743,6 +744,8 @@ object CandidateGenerator extends ScorexLogging { blockTxs.map(_._2).sum < maxBlockCost && blockTxs.map(_._1.size).sum < maxBlockSize } + // private var lastSubblockOpt = None + /** * Collects valid non-conflicting transactions from `mandatoryTxs` and then `mempoolTxsIn` and adds a transaction * collecting fees from them to `minerPk`. diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala index 306ddd1f6c..c71df0df5d 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala @@ -40,7 +40,7 @@ class ErgoMiningThread( log.info(s"Stopping miner thread: ${self.path.name}") override def receive: Receive = { - case StatusReply.Success(Candidate(candidateBlock, _, _)) => + case StatusReply.Success(Candidate(candidateBlock, _, _, _)) => log.info(s"Initiating block mining") context.become(mining(nonce = 0, candidateBlock, solvedBlocksCount = 0)) self ! MineCmd @@ -53,7 +53,7 @@ class ErgoMiningThread( candidateBlock: CandidateBlock, solvedBlocksCount: Int ): Receive = { - case StatusReply.Success(Candidate(cb, _, _)) => + case StatusReply.Success(Candidate(cb, _, _, _)) => // if we get new candidate instead of a cached one, mine it if (cb.timestamp != candidateBlock.timestamp) { context.become(mining(nonce = 0, cb, solvedBlocksCount)) diff --git a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala index 7ef123a1d7..29fa577659 100644 --- a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala @@ -9,7 +9,6 @@ import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.scalacheck.Gen import sigmastate.crypto.DLogProtocol.ProveDlog -import scala.concurrent.duration._ class CandidateGeneratorPropSpec extends ErgoCorePropertyTest { import org.ergoplatform.utils.ErgoNodeTestConstants._ @@ -279,16 +278,4 @@ class CandidateGeneratorPropSpec extends ErgoCorePropertyTest { } } - property("it should calculate average block mining time from creation timestamps") { - val timestamps1 = System.currentTimeMillis() - val timestamps2 = timestamps1 + 100 - val timestamps3 = timestamps2 + 200 - val timestamps4 = timestamps3 + 300 - val avgMiningTime = { - CandidateGenerator.getBlockMiningTimeAvg( - Vector(timestamps1, timestamps2, timestamps3, timestamps4) - ) - } - avgMiningTime shouldBe 200.millis - } } diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index b63519caf1..2de3c334f2 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -111,7 +111,7 @@ trait Stubs extends ErgoTestHelpers with TestFileUtils { def receive: Receive = { case CandidateGenerator.GenerateCandidate(_, reply) => if (reply) { - val candidate = Candidate(null, externalWorkMessage, Seq.empty) // API does not use CandidateBlock + val candidate = Candidate(null, externalWorkMessage, Seq.empty, subBlock = false) // API does not use CandidateBlock sender() ! StatusReply.success(candidate) } case _: AutolykosSolution => sender() ! StatusReply.success(()) From e8bcdfa0b22f75f24f3064adfd8670d71dd4d4cd Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 13 Sep 2024 14:25:21 +0300 Subject: [PATCH 041/109] bestSubblock() stub. createCandidate #1 --- .../mining/CandidateGenerator.scala | 24 ++++++++++++++----- .../SubBlocksProcessor.scala | 4 ++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 401887f091..ce1c9f5cd6 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -22,6 +22,7 @@ import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.nodeView.state.{ErgoState, ErgoStateContext, StateType, UtxoStateReader} import org.ergoplatform.settings.{ErgoSettings, ErgoValidationSettingsUpdate, Parameters} import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox +import org.ergoplatform.subblocks.SubBlockInfo import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input} import scorex.crypto.hash.Digest32 @@ -425,18 +426,30 @@ object CandidateGenerator extends ScorexLogging { val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) + val lastSubblockOpt:Option[SubBlockInfo] = history.bestSubblock() + + // there was sub-block generated before for this block + val continueSubblock = lastSubblockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.subBlock.parentId)) + // Make progress in time since last block. // If no progress is made, then, by consensus rules, the block will be rejected. + // todo: review w. subblocks val timestamp = Math.max(System.currentTimeMillis(), bestHeaderOpt.map(_.timestamp + 1).getOrElse(0L)) val stateContext = state.stateContext // Calculate required difficulty for the new block - val nBits: Long = bestHeaderOpt - .map(parent => history.requiredDifficultyAfter(parent)) - .map(d => DifficultySerializer.encodeCompactBits(d)) - .getOrElse(ergoSettings.chainSettings.initialNBits) + val nBits: Long = if(continueSubblock) { + lastSubblockOpt.get.subBlock.nBits // .get is ok as lastSubblockOpt.exists in continueSubblock checks emptiness + } else { + bestHeaderOpt + .map(parent => history.requiredDifficultyAfter(parent)) + .map(d => DifficultySerializer.encodeCompactBits(d)) + .getOrElse(ergoSettings.chainSettings.initialNBits) + } + + // todo: do not recalculate interlink vector if subblock available // Obtain NiPoPoW interlinks vector to pack it into the extension section val updInterlinks = popowAlgos.updateInterlinks(bestHeaderOpt, bestExtensionOpt) @@ -744,8 +757,6 @@ object CandidateGenerator extends ScorexLogging { blockTxs.map(_._2).sum < maxBlockCost && blockTxs.map(_._1.size).sum < maxBlockSize } - // private var lastSubblockOpt = None - /** * Collects valid non-conflicting transactions from `mandatoryTxs` and then `mempoolTxsIn` and adds a transaction * collecting fees from them to `minerPk`. @@ -805,6 +816,7 @@ object CandidateGenerator extends ScorexLogging { val newTxs = acc :+ (tx -> costConsumed) val newBoxes = newTxs.flatMap(_._1.outputs) + // todo: why to collect fees on each tx? collectFees(currentHeight, newTxs.map(_._1), minerPk, upcomingContext) match { case Some(feeTx) => val boxesToSpend = feeTx.inputs.flatMap(i => diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala index 11a171ddf9..e84a2db899 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala @@ -22,4 +22,8 @@ trait SubBlocksProcessor { subBlockTransactions.get(sbId) } + def bestSubblock(): Option[SubBlockInfo] = { + ??? + } + } From 647b6dbbeac0177e7ccd3373f998710f259f2976 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 19 Sep 2024 00:32:27 +0300 Subject: [PATCH 042/109] initial version of best subblock selection --- .../mining/CandidateGenerator.scala | 2 +- .../SubBlocksProcessor.scala | 28 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index ce1c9f5cd6..42a5c57cd4 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -426,7 +426,7 @@ object CandidateGenerator extends ScorexLogging { val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) - val lastSubblockOpt:Option[SubBlockInfo] = history.bestSubblock() + val lastSubblockOpt: Option[SubBlockInfo] = history.bestSubblock() // there was sub-block generated before for this block val continueSubblock = lastSubblockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.subBlock.parentId)) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala index e84a2db899..988d666856 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala @@ -2,20 +2,34 @@ package org.ergoplatform.nodeView.history.storage.modifierprocessors import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.subblocks.SubBlockInfo -import scorex.util.ModifierId +import scorex.util.{ModifierId, ScorexLogging, bytesToId} -trait SubBlocksProcessor { +import scala.collection.mutable - val subBlockRecords = Map[ModifierId, SubBlockInfo]() - val subBlockTransactions = Map[ModifierId, Seq[ErgoTransaction]]() +trait SubBlocksProcessor extends ScorexLogging { + + var _bestSubblock: Option[SubBlockInfo] = None + val subBlockRecords = mutable.Map[ModifierId, SubBlockInfo]() + val subBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() // sub-blocks related logic def applySubBlockHeader(sbi: SubBlockInfo): Unit = { - // todo: implement + subBlockRecords.put(sbi.subBlock.id, sbi) + + // todo: currently only one chain of subblocks considered, + // in fact there could be multiple trees here (one subblocks tree per header) + _bestSubblock match { + case None => _bestSubblock = Some(sbi) + case Some(maybeParent) if (sbi.prevSubBlockId.map(bytesToId).contains(maybeParent.subBlock.id)) => + _bestSubblock = Some(sbi) + case _ => + // todo: record it + log.debug(s"Applying non-best subblock id: ${sbi.subBlock.id}") + } } def applySubBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { - // todo: implement + subBlockTransactions.put(sbId, transactions) } def getSubBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { @@ -23,7 +37,7 @@ trait SubBlocksProcessor { } def bestSubblock(): Option[SubBlockInfo] = { - ??? + _bestSubblock } } From e0feb838de9093dd768259981885cef01216203c Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 20 Sep 2024 00:05:49 +0300 Subject: [PATCH 043/109] input/ordering block distintcion in mining logic #1, input block generation --- .../mining/AutolykosPowScheme.scala | 37 ++++++++++++++----- .../mining/DefaultFakePowScheme.scala | 5 ++- .../org/ergoplatform/mining/mining.scala | 19 ++++++++++ .../mining/AutolykosPowSchemeSpec.scala | 31 ++++++++++------ .../mining/CandidateGenerator.scala | 2 +- .../mining/ErgoMiningThread.scala | 9 ++++- .../SubBlocksProcessor.scala | 12 +++++- 7 files changed, 86 insertions(+), 29 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala index 4681c0f233..853a9a508d 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala @@ -3,6 +3,7 @@ package org.ergoplatform.mining import com.google.common.primitives.{Bytes, Ints, Longs} import org.bouncycastle.util.BigIntegers import org.ergoplatform.ErgoLikeContext.Height +import org.ergoplatform.{InputBlockFound, InputBlockHeaderFound, InputSolutionFound, NothingFound, OrderingBlockFound, OrderingBlockHeeaderFound, OrderingSolutionFound, ProveBlockResult} import org.ergoplatform.mining.difficulty.DifficultySerializer import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.modifiers.history._ @@ -278,6 +279,7 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { //Proving-related code which is not critical for consensus below + /** * Autolykos solver suitable for CPU-mining in testnet and devnets. * @@ -295,7 +297,7 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { votes: Array[Byte], sk: PrivateKey, minNonce: Long = Long.MinValue, - maxNonce: Long = Long.MaxValue): Option[Header] = { + maxNonce: Long = Long.MaxValue): ProveBlockResult = { val (parentId, height) = AutolykosPowScheme.derivedHeaderFields(parentOpt) val h = HeaderWithoutPow(version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, @@ -305,7 +307,11 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { val x = randomSecret() val hbs = Ints.toByteArray(h.height) val N = calcN(h) - checkNonces(version, hbs, msg, sk, x, b, N, minNonce, maxNonce).map(solution => h.toHeader(solution)) + checkNonces(version, hbs, msg, sk, x, b, N, minNonce, maxNonce) match { + case NothingFound => NothingFound + case InputSolutionFound(as) => InputBlockHeaderFound(h.toHeader(as)) + case OrderingSolutionFound(as) => OrderingBlockHeeaderFound(h.toHeader(as)) + } } /** @@ -323,18 +329,24 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { votes: Array[Byte], sk: PrivateKey, minNonce: Long = Long.MinValue, - maxNonce: Long = Long.MaxValue): Option[ErgoFullBlock] = { + maxNonce: Long = Long.MaxValue): ProveBlockResult = { val transactionsRoot = BlockTransactions.transactionsRoot(transactions, version) val adProofsRoot = ADProofs.proofDigest(adProofBytes) - prove(parentOpt, version, nBits, stateRoot, adProofsRoot, transactionsRoot, - timestamp, extensionCandidate.digest, votes, sk, minNonce, maxNonce).map { h => + def constructBlockFromHeader(h: Header) = { val adProofs = ADProofs(h.id, adProofBytes) val blockTransactions = BlockTransactions(h.id, version, transactions) val extension = extensionCandidate.toExtension(h.id) new ErgoFullBlock(h, blockTransactions, extension, Some(adProofs)) } + + prove(parentOpt, version, nBits, stateRoot, adProofsRoot, transactionsRoot, + timestamp, extensionCandidate.digest, votes, sk, minNonce, maxNonce) match { + case NothingFound => NothingFound + case InputBlockHeaderFound(h) => InputBlockFound(constructBlockFromHeader(h)) + case OrderingBlockHeeaderFound(h) => OrderingBlockFound(constructBlockFromHeader(h)) + } } /** @@ -344,7 +356,7 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { def proveCandidate(candidateBlock: CandidateBlock, sk: PrivateKey, minNonce: Long = Long.MinValue, - maxNonce: Long = Long.MaxValue): Option[ErgoFullBlock] = { + maxNonce: Long = Long.MaxValue): ProveBlockResult = { proveBlock(candidateBlock.parentOpt, candidateBlock.version, candidateBlock.nBits, @@ -372,14 +384,17 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { b: BigInt, N: Int, startNonce: Long, - endNonce: Long): Option[AutolykosSolution] = { + endNonce: Long): ProveBlockResult = { + + val subblocksPerBlock = 10 // todo : make configurable + log.debug(s"Going to check nonces from $startNonce to $endNonce") val p1 = groupElemToBytes(genPk(sk)) val p2 = groupElemToBytes(genPk(x)) @tailrec - def loop(i: Long): Option[AutolykosSolution] = if (i == endNonce) { - None + def loop(i: Long): ProveBlockResult = if (i == endNonce) { + NothingFound } else { if (i % 1000000 == 0 && i > 0) println(s"$i nonce tested") val nonce = Longs.toByteArray(i) @@ -398,7 +413,9 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { } if (d <= b) { log.debug(s"Solution found at $i") - Some(AutolykosSolution(genPk(sk), genPk(x), nonce, d)) + OrderingSolutionFound(AutolykosSolution(genPk(sk), genPk(x), nonce, d)) + } else if (d <= b * subblocksPerBlock) { + InputSolutionFound(AutolykosSolution(genPk(sk), genPk(x), nonce, d)) } else { loop(i + 1) } diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala index c3375a45c6..5ae56566cd 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala @@ -1,5 +1,6 @@ package org.ergoplatform.mining +import org.ergoplatform.{OrderingBlockHeeaderFound, ProveBlockResult} import org.ergoplatform.modifiers.history.header.Header import scorex.crypto.authds.ADDigest import scorex.crypto.hash.Digest32 @@ -25,14 +26,14 @@ class DefaultFakePowScheme(k: Int, n: Int) extends AutolykosPowScheme(k, n) { votes: Array[Byte], sk: PrivateKey, minNonce: Long = Long.MinValue, - maxNonce: Long = Long.MaxValue): Option[Header] = { + maxNonce: Long = Long.MaxValue): ProveBlockResult = { val (parentId, height) = AutolykosPowScheme.derivedHeaderFields(parentOpt) val pk: EcPointType = genPk(sk) val w: EcPointType = genPk(Random.nextLong()) val n: Array[Byte] = Array.fill(8)(0: Byte) val d: BigInt = q / (height + 10) val s = AutolykosSolution(pk, w, n, d) - Some(Header(version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, + OrderingBlockHeeaderFound(Header(version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, nBits, height, extensionHash, s, votes, Array.emptyByteArray)) } diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala index cf4ce0637f..b9425c450b 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala @@ -1,11 +1,30 @@ package org.ergoplatform import org.bouncycastle.util.BigIntegers +import org.ergoplatform.mining.AutolykosSolution +import org.ergoplatform.modifiers.ErgoFullBlock +import org.ergoplatform.modifiers.history.header.Header import scorex.crypto.hash.Blake2b256 import sigma.crypto.{BcDlogGroup, CryptoConstants, EcPointType} import sigma.serialization.{GroupElementSerializer, SigmaSerializer} import sigmastate.crypto.DLogProtocol.DLogProverInput +sealed trait ProveBlockResult + +case object NothingFound extends ProveBlockResult + +case class OrderingBlockFound(fb: ErgoFullBlock) extends ProveBlockResult + +case class OrderingBlockHeeaderFound(h: Header) extends ProveBlockResult + +case class InputBlockFound(fb: ErgoFullBlock) extends ProveBlockResult + +case class InputBlockHeaderFound(h: Header) extends ProveBlockResult + +case class InputSolutionFound(as: AutolykosSolution) extends ProveBlockResult + +case class OrderingSolutionFound(as: AutolykosSolution) extends ProveBlockResult + package object mining { type PrivateKey = BigInt diff --git a/ergo-core/src/test/scala/org/ergoplatform/mining/AutolykosPowSchemeSpec.scala b/ergo-core/src/test/scala/org/ergoplatform/mining/AutolykosPowSchemeSpec.scala index 5c0d1e24c4..e4f13f7727 100644 --- a/ergo-core/src/test/scala/org/ergoplatform/mining/AutolykosPowSchemeSpec.scala +++ b/ergo-core/src/test/scala/org/ergoplatform/mining/AutolykosPowSchemeSpec.scala @@ -8,6 +8,7 @@ import org.scalacheck.Gen import scorex.crypto.hash.Blake2b256 import scorex.util.encode.Base16 import cats.syntax.either._ +import org.ergoplatform.{InputSolutionFound, OrderingSolutionFound} class AutolykosPowSchemeSpec extends ErgoCorePropertyTest { import org.ergoplatform.utils.ErgoCoreTestConstants._ @@ -26,18 +27,24 @@ class AutolykosPowSchemeSpec extends ErgoCorePropertyTest { val b = pow.getB(h.nBits) val hbs = Ints.toByteArray(h.height) val N = pow.calcN(h) - val newHeader = pow.checkNonces(ver, hbs, msg, sk, x, b, N, 0, 1000) - .map(s => h.copy(powSolution = s)).get - pow.validate(newHeader) shouldBe 'success - - if(ver > Header.InitialVersion) { - // We remove last byte of "msg", perform PoW and check that it fails validation - require(HeaderSerializer.bytesWithoutPow(h).last == 0) - val msg2 = Blake2b256(HeaderSerializer.bytesWithoutPow(h).dropRight(1)) - - val newHeader2 = pow.checkNonces(ver, hbs, msg2, sk, x, b, N, 0, 1000) - .map(s => h.copy(powSolution = s)).get - pow.validate(newHeader2) shouldBe 'failure + pow.checkNonces(ver, hbs, msg, sk, x, b, N, 0, 1000) match { + case OrderingSolutionFound(as) => + val nh = h.copy(powSolution = as) + pow.validate(nh) shouldBe 'success + + if (ver > Header.InitialVersion) { + // We remove last byte of "msg", perform PoW and check that it fails validation + require(HeaderSerializer.bytesWithoutPow(h).last == 0) + val msg2 = Blake2b256(HeaderSerializer.bytesWithoutPow(h).dropRight(1)) + + pow.checkNonces(ver, hbs, msg2, sk, x, b, N, 0, 1000) match { + case OrderingSolutionFound(as2) => + val nh2 = h.copy(powSolution = as2) + pow.validate(nh2) shouldBe 'failure + case _ => + } + } + case _ => } } } diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 51115eac8a..246f3fddc6 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -437,7 +437,7 @@ object CandidateGenerator extends ScorexLogging { val stateContext = state.stateContext - // Calculate required difficulty for the new block + // Calculate required difficulty for the new block, the same diff for subblock val nBits: Long = if(continueSubblock) { lastSubblockOpt.get.subBlock.nBits // .get is ok as lastSubblockOpt.exists in continueSubblock checks emptiness } else { diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala index c71df0df5d..b8e5a61d4d 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala @@ -2,6 +2,7 @@ package org.ergoplatform.mining import akka.actor.{Actor, ActorRef, ActorRefFactory, Props} import akka.pattern.StatusReply +import org.ergoplatform.{InputBlockFound, NothingFound, OrderingBlockFound} import org.ergoplatform.mining.CandidateGenerator.{Candidate, GenerateCandidate} import org.ergoplatform.settings.ErgoSettings import scorex.util.ScorexLogging @@ -67,13 +68,17 @@ class ErgoMiningThread( case MineCmd => val lastNonceToCheck = nonce + NonceStep powScheme.proveCandidate(candidateBlock, sk, nonce, lastNonceToCheck) match { - case Some(newBlock) => + case OrderingBlockFound(newBlock) => log.info(s"Found solution, sending it for validation") candidateGenerator ! newBlock.header.powSolution - case None => + case InputBlockFound(_) => + // todo: process + case NothingFound => log.info(s"Trying nonce $lastNonceToCheck") context.become(mining(lastNonceToCheck, candidateBlock, solvedBlocksCount)) self ! MineCmd + case _ => + //todo : rework ProveBlockResult hierarchy to avoid this branch } case GetSolvedBlocksCount => sender() ! SolvedBlocksCount(solvedBlocksCount) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala index 9b8eed5bb9..99aeceede0 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala @@ -13,15 +13,23 @@ trait SubBlocksProcessor extends ScorexLogging { val subBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() def resetState() = { - + _bestSubblock = None + + // todo: subBlockRecords & subBlockTransactions should be cleared a bit later, as other peers may still ask for them + subBlockRecords.clear() + subBlockTransactions.clear() } // sub-blocks related logic def applySubBlockHeader(sbi: SubBlockInfo): Unit = { + if (sbi.subBlock.height > _bestSubblock.map(_.subBlock.height).getOrElse(-1)) { + resetState() + } + subBlockRecords.put(sbi.subBlock.id, sbi) // todo: currently only one chain of subblocks considered, - // in fact there could be multiple trees here (one subblocks tree per header) + // todo: in fact there could be multiple trees here (one subblocks tree per header) _bestSubblock match { case None => _bestSubblock = Some(sbi) case Some(maybeParent) if (sbi.prevSubBlockId.map(bytesToId).contains(maybeParent.subBlock.id)) => From 9ebe5db4214fbeb4ff2d89318b475ea094e33e73 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 1 Oct 2024 17:34:41 +0300 Subject: [PATCH 044/109] subblocks generation --- .../ergoplatform/mining/AutolykosPowScheme.scala | 14 +++++++------- .../ergoplatform/mining/AutolykosSolution.scala | 1 + .../ergoplatform/mining/DefaultFakePowScheme.scala | 4 ++-- .../scala/org/ergoplatform/mining/mining.scala | 14 +++++++++++--- .../ergoplatform/mining/CandidateGenerator.scala | 12 +++++++++--- .../org/ergoplatform/mining/ErgoMiningThread.scala | 11 ++++++----- .../mining/CandidateGeneratorSpec.scala | 11 +++++++---- .../org/ergoplatform/mining/ErgoMinerSpec.scala | 5 +++-- .../nodeView/history/extra/ChainGenerator.scala | 2 +- .../scala/org/ergoplatform/sanity/ErgoSanity.scala | 6 ++++-- .../org/ergoplatform/tools/ChainGenerator.scala | 2 +- .../scala/org/ergoplatform/tools/MinerBench.scala | 6 +++++- src/test/scala/org/ergoplatform/utils/Stubs.scala | 6 ++++-- .../utils/generators/ChainGenerator.scala | 11 +++++++---- .../utils/generators/ValidBlocksGenerators.scala | 8 +++++--- 15 files changed, 73 insertions(+), 40 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala index 853a9a508d..97f2953221 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala @@ -3,7 +3,7 @@ package org.ergoplatform.mining import com.google.common.primitives.{Bytes, Ints, Longs} import org.bouncycastle.util.BigIntegers import org.ergoplatform.ErgoLikeContext.Height -import org.ergoplatform.{InputBlockFound, InputBlockHeaderFound, InputSolutionFound, NothingFound, OrderingBlockFound, OrderingBlockHeeaderFound, OrderingSolutionFound, ProveBlockResult} +import org.ergoplatform.{BlockSolutionSearchResult, InputBlockFound, InputBlockHeaderFound, InputSolutionFound, NoSolutionFound, NothingFound, OrderingBlockFound, OrderingBlockHeaderFound, OrderingSolutionFound, ProveBlockResult} import org.ergoplatform.mining.difficulty.DifficultySerializer import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.modifiers.history._ @@ -308,9 +308,9 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { val hbs = Ints.toByteArray(h.height) val N = calcN(h) checkNonces(version, hbs, msg, sk, x, b, N, minNonce, maxNonce) match { - case NothingFound => NothingFound + case NoSolutionFound => NothingFound case InputSolutionFound(as) => InputBlockHeaderFound(h.toHeader(as)) - case OrderingSolutionFound(as) => OrderingBlockHeeaderFound(h.toHeader(as)) + case OrderingSolutionFound(as) => OrderingBlockHeaderFound(h.toHeader(as)) } } @@ -345,7 +345,7 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { timestamp, extensionCandidate.digest, votes, sk, minNonce, maxNonce) match { case NothingFound => NothingFound case InputBlockHeaderFound(h) => InputBlockFound(constructBlockFromHeader(h)) - case OrderingBlockHeeaderFound(h) => OrderingBlockFound(constructBlockFromHeader(h)) + case OrderingBlockHeaderFound(h) => OrderingBlockFound(constructBlockFromHeader(h)) } } @@ -384,7 +384,7 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { b: BigInt, N: Int, startNonce: Long, - endNonce: Long): ProveBlockResult = { + endNonce: Long): BlockSolutionSearchResult = { val subblocksPerBlock = 10 // todo : make configurable @@ -393,8 +393,8 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { val p2 = groupElemToBytes(genPk(x)) @tailrec - def loop(i: Long): ProveBlockResult = if (i == endNonce) { - NothingFound + def loop(i: Long): BlockSolutionSearchResult = if (i == endNonce) { + NoSolutionFound } else { if (i % 1000000 == 0 && i > 0) println(s"$i nonce tested") val nonce = Longs.toByteArray(i) diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala index aa11aff0b7..f7ea1fc5f9 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala @@ -64,6 +64,7 @@ case class WeakAutolykosSolution(pk: EcPointType, n: Array[Byte]) { } object WeakAutolykosSolution extends ApiCodecs { + implicit val jsonEncoder: Encoder[WeakAutolykosSolution] = { s: WeakAutolykosSolution => Map( "pk" -> s.pk.asJson, diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala index 5ae56566cd..06e9948ddd 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala @@ -1,6 +1,6 @@ package org.ergoplatform.mining -import org.ergoplatform.{OrderingBlockHeeaderFound, ProveBlockResult} +import org.ergoplatform.{OrderingBlockHeaderFound, ProveBlockResult} import org.ergoplatform.modifiers.history.header.Header import scorex.crypto.authds.ADDigest import scorex.crypto.hash.Digest32 @@ -33,7 +33,7 @@ class DefaultFakePowScheme(k: Int, n: Int) extends AutolykosPowScheme(k, n) { val n: Array[Byte] = Array.fill(8)(0: Byte) val d: BigInt = q / (height + 10) val s = AutolykosSolution(pk, w, n, d) - OrderingBlockHeeaderFound(Header(version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, + OrderingBlockHeaderFound(Header(version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, nBits, height, extensionHash, s, votes, Array.emptyByteArray)) } diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala index b9425c450b..2d57c825ab 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/mining.scala @@ -15,15 +15,23 @@ case object NothingFound extends ProveBlockResult case class OrderingBlockFound(fb: ErgoFullBlock) extends ProveBlockResult -case class OrderingBlockHeeaderFound(h: Header) extends ProveBlockResult +case class OrderingBlockHeaderFound(h: Header) extends ProveBlockResult case class InputBlockFound(fb: ErgoFullBlock) extends ProveBlockResult case class InputBlockHeaderFound(h: Header) extends ProveBlockResult -case class InputSolutionFound(as: AutolykosSolution) extends ProveBlockResult +sealed trait BlockSolutionSearchResult -case class OrderingSolutionFound(as: AutolykosSolution) extends ProveBlockResult +case object NoSolutionFound extends BlockSolutionSearchResult + +sealed trait SolutionFound extends BlockSolutionSearchResult { + val as: AutolykosSolution +} + +case class InputSolutionFound(override val as: AutolykosSolution) extends SolutionFound + +case class OrderingSolutionFound(override val as: AutolykosSolution) extends SolutionFound package object mining { diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 246f3fddc6..e0c93473d9 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -24,7 +24,7 @@ import org.ergoplatform.settings.{ErgoSettings, ErgoValidationSettingsUpdate, Pa import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox import org.ergoplatform.subblocks.SubBlockInfo import org.ergoplatform.wallet.interpreter.ErgoInterpreter -import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input} +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, InputSolutionFound, OrderingSolutionFound, SolutionFound} import scorex.crypto.hash.Digest32 import scorex.util.encode.Base16 import scorex.util.{ModifierId, ScorexLogging} @@ -172,9 +172,10 @@ class CandidateGenerator( } } - case preSolution: AutolykosSolution + case sf: SolutionFound if state.solvedBlock.isEmpty && state.cache.nonEmpty => // Inject node pk if it is not externally set (in Autolykos 2) + val preSolution = sf.as val solution = if (CryptoFacade.isInfinityPoint(preSolution.pk)) { AutolykosSolution(minerPk.value, preSolution.w, preSolution.n, preSolution.d) @@ -187,10 +188,15 @@ class CandidateGenerator( ergoSettings.chainSettings.powScheme .validate(newBlock.header) .map(_ => newBlock) match { - case Success(newBlock) => + case Success(newBlock) if sf.isInstanceOf[OrderingSolutionFound] => sendToNodeView(newBlock) context.become(initialized(state.copy(solvedBlock = Some(newBlock)))) StatusReply.success(()) + case Success(_) if sf.isInstanceOf[InputSolutionFound] => + log.info("Sub-block mined!") + StatusReply.error( + new Exception(s"Input block found!", new Exception()) + ) case Failure(exception) => log.warn(s"Removing candidate due to invalid block", exception) context.become(initialized(state.copy(cache = None))) diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala index b8e5a61d4d..9ff5103f8a 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala @@ -2,7 +2,7 @@ package org.ergoplatform.mining import akka.actor.{Actor, ActorRef, ActorRefFactory, Props} import akka.pattern.StatusReply -import org.ergoplatform.{InputBlockFound, NothingFound, OrderingBlockFound} +import org.ergoplatform.{InputBlockFound, InputSolutionFound, NothingFound, OrderingBlockFound, OrderingSolutionFound} import org.ergoplatform.mining.CandidateGenerator.{Candidate, GenerateCandidate} import org.ergoplatform.settings.ErgoSettings import scorex.util.ScorexLogging @@ -69,10 +69,11 @@ class ErgoMiningThread( val lastNonceToCheck = nonce + NonceStep powScheme.proveCandidate(candidateBlock, sk, nonce, lastNonceToCheck) match { case OrderingBlockFound(newBlock) => - log.info(s"Found solution, sending it for validation") - candidateGenerator ! newBlock.header.powSolution - case InputBlockFound(_) => - // todo: process + log.info(s"Found solution for ordering block, sending it for validation") + candidateGenerator ! OrderingSolutionFound(newBlock.header.powSolution) + case InputBlockFound(newBlock) => + log.info(s"Found solution for input block, sending it for validation") + candidateGenerator ! InputSolutionFound(newBlock.header.powSolution) case NothingFound => log.info(s"Trying nonce $lastNonceToCheck") context.become(mining(lastNonceToCheck, candidateBlock, solvedBlocksCount)) diff --git a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala index cb245c6dc6..f234d39a37 100644 --- a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala @@ -16,7 +16,7 @@ import org.ergoplatform.nodeView.state.StateType import org.ergoplatform.nodeView.{ErgoNodeViewRef, ErgoReadersHolderRef} import org.ergoplatform.settings.{ErgoSettings, ErgoSettingsReader} import org.ergoplatform.utils.ErgoTestHelpers -import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input} +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, OrderingBlockFound} import org.scalatest.concurrent.Eventually import org.scalatest.flatspec.AnyFlatSpec import sigma.ast.ErgoTree @@ -139,7 +139,8 @@ class CandidateGeneratorSpec extends AnyFlatSpec with Matchers with ErgoTestHelp case StatusReply.Success(candidate: Candidate) => defaultSettings.chainSettings.powScheme .proveCandidate(candidate.candidateBlock, defaultMinerSecret.w, 0, 1000) - .get + .asInstanceOf[OrderingBlockFound] // todo: fix + .fb } // now block should be cached @@ -182,7 +183,8 @@ class CandidateGeneratorSpec extends AnyFlatSpec with Matchers with ErgoTestHelp case StatusReply.Success(candidate: Candidate) => val block = defaultSettings.chainSettings.powScheme .proveCandidate(candidate.candidateBlock, defaultMinerSecret.w, 0, 1000) - .get + .asInstanceOf[OrderingBlockFound] // todo: fix + .fb // let's pretend we are mining at least a bit so it is realistic expectNoMessage(200.millis) candidateGenerator.tell(block.header.powSolution, testProbe.ref) @@ -228,7 +230,8 @@ class CandidateGeneratorSpec extends AnyFlatSpec with Matchers with ErgoTestHelp case StatusReply.Success(candidate: Candidate) => val block = defaultSettings.chainSettings.powScheme .proveCandidate(candidate.candidateBlock, defaultMinerSecret.w, 0, 1000) - .get + .asInstanceOf[OrderingBlockFound] // todo: fix + .fb testProbe.expectNoMessage(200.millis) candidateGenerator.tell(block.header.powSolution, testProbe.ref) diff --git a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala index e1543727fc..84ba6a126e 100644 --- a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala @@ -20,7 +20,7 @@ import org.ergoplatform.nodeView.{ErgoNodeViewRef, ErgoReadersHolderRef} import org.ergoplatform.settings.{ErgoSettings, ErgoSettingsReader} import org.ergoplatform.utils.ErgoTestHelpers import org.ergoplatform.wallet.interpreter.ErgoInterpreter -import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input} +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, OrderingBlockFound} import org.scalatest.concurrent.Eventually import org.scalatest.flatspec.AnyFlatSpec import sigma.ast.{ErgoTree, SigmaAnd, SigmaPropConstant} @@ -267,7 +267,8 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with Eventually { case StatusReply.Success(candidate: Candidate) => val block = defaultSettings.chainSettings.powScheme .proveCandidate(candidate.candidateBlock, defaultMinerSecret.w, 0, 1000) - .get + .asInstanceOf[OrderingBlockFound] // todo: fix + .fb testProbe.expectNoMessage(200.millis) minerRef.tell(block.header.powSolution, testProbe.ref) diff --git a/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala b/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala index 6480e04a9b..94150ac444 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala @@ -194,7 +194,7 @@ object ChainGenerator extends ErgoTestHelpers with Matchers { log.info(s"Trying to prove block with parent ${candidate.parentOpt.map(_.encodedId)} and timestamp ${candidate.timestamp}") pow.proveCandidate(candidate, defaultProver.hdKeys.head.privateInput.w) match { - case Some(fb) => fb + case OrderingBlockFound(fb) => fb case _ => val interlinks = candidate.parentOpt .map(nipopowAlgos.updateInterlinks(_, NipopowAlgos.unpackInterlinks(candidate.extension.fields).get)) diff --git a/src/test/scala/org/ergoplatform/sanity/ErgoSanity.scala b/src/test/scala/org/ergoplatform/sanity/ErgoSanity.scala index 1eb85d64d0..a12306d8d1 100644 --- a/src/test/scala/org/ergoplatform/sanity/ErgoSanity.scala +++ b/src/test/scala/org/ergoplatform/sanity/ErgoSanity.scala @@ -1,7 +1,7 @@ package org.ergoplatform.sanity import akka.actor.ActorRef -import org.ergoplatform.ErgoBox +import org.ergoplatform.{ErgoBox, OrderingBlockFound} import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.history.BlockTransactions import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} @@ -61,7 +61,9 @@ trait ErgoSanity[ST <: ErgoState[ST]] extends NodeViewSynchronizerTests[ST] Digest32 @@ Array.fill(HashLength)(0.toByte), Array.fill(3)(0: Byte), defaultMinerSecretNumber - ).get + ).asInstanceOf[OrderingBlockFound] // todo: fix + .fb + .header } override def syntacticallyInvalidModifier(history: HT): PM = diff --git a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala index 5e41d64c34..f21658c230 100644 --- a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala @@ -208,7 +208,7 @@ object ChainGenerator extends App with ErgoTestHelpers with Matchers { log.info(s"Trying to prove block with parent ${candidate.parentOpt.map(_.encodedId)} and timestamp ${candidate.timestamp}") pow.proveCandidate(candidate, prover.hdKeys.head.privateInput.w) match { - case Some(fb) => fb + case OrderingBlockFound(fb) => fb case _ => val interlinks = candidate.parentOpt .map(nipopowAlgos.updateInterlinks(_, NipopowAlgos.unpackInterlinks(candidate.extension.fields).get)) diff --git a/src/test/scala/org/ergoplatform/tools/MinerBench.scala b/src/test/scala/org/ergoplatform/tools/MinerBench.scala index 461b298a52..b013dfdc5a 100644 --- a/src/test/scala/org/ergoplatform/tools/MinerBench.scala +++ b/src/test/scala/org/ergoplatform/tools/MinerBench.scala @@ -2,6 +2,7 @@ package org.ergoplatform.tools import com.google.common.primitives.Bytes import org.bouncycastle.util.BigIntegers +import org.ergoplatform.OrderingBlockFound import org.ergoplatform.mining._ import org.ergoplatform.mining.difficulty.DifficultySerializer import org.ergoplatform.modifiers.history.extension.ExtensionCandidate @@ -76,7 +77,10 @@ object MinerBench extends App with ErgoTestHelpers { System.currentTimeMillis(), ExtensionCandidate(Seq.empty), Array()) - val newHeader = pow.proveCandidate(candidate, sk).get.header + val newHeader = pow.proveCandidate(candidate, sk) + .asInstanceOf[OrderingBlockFound] // todo: fix + .fb + .header val Steps = 10000 diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index ce0fb2c842..4867c914e0 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -3,7 +3,7 @@ package org.ergoplatform.utils import akka.actor.{Actor, ActorRef, ActorSystem, Props} import akka.pattern.StatusReply import org.bouncycastle.util.BigIntegers -import org.ergoplatform.P2PKAddress +import org.ergoplatform.{OrderingBlockFound, P2PKAddress} import org.ergoplatform.mining.CandidateGenerator.Candidate import org.ergoplatform.mining.{AutolykosSolution, CandidateGenerator, ErgoMiner, WorkMessage} import org.ergoplatform.modifiers.ErgoFullBlock @@ -407,7 +407,9 @@ trait Stubs extends ErgoTestHelpers with TestFileUtils { Digest32 @@ Array.fill(HashLength)(0.toByte), Array.fill(3)(0: Byte), defaultMinerSecretNumber - ).value + ).asInstanceOf[OrderingBlockFound] // todo: fix + .fb + .header } } diff --git a/src/test/scala/org/ergoplatform/utils/generators/ChainGenerator.scala b/src/test/scala/org/ergoplatform/utils/generators/ChainGenerator.scala index 3930a45a83..5427919dde 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/ChainGenerator.scala @@ -1,6 +1,6 @@ package org.ergoplatform.utils.generators -import org.ergoplatform.Input +import org.ergoplatform.{Input, OrderingBlockFound} import org.ergoplatform.mining.difficulty.DifficultyAdjustment import org.ergoplatform.modifiers.history.HeaderChain import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionCandidate} @@ -100,7 +100,7 @@ object ChainGenerator { extensionHash: Digest32 = EmptyDigest32, tsOpt: Option[Long] = None, diffBitsOpt: Option[Long] = None, - useRealTs: Boolean): Header = + useRealTs: Boolean): Header = { powScheme.prove( prev, Header.InitialVersion, @@ -113,7 +113,9 @@ object ChainGenerator { extensionHash, Array.fill(3)(0: Byte), defaultMinerSecretNumber - ).get + ).asInstanceOf[OrderingBlockFound] // todo: fix + .fb.header + } def genChain(height: Int): Seq[ErgoFullBlock] = blockStream(None).take(height) @@ -168,7 +170,8 @@ object ChainGenerator { validExtension, Array.fill(3)(0: Byte), defaultMinerSecretNumber - ).get + ).asInstanceOf[OrderingBlockFound] // todo: fix + .fb } def applyHeaderChain(historyIn: ErgoHistory, chain: HeaderChain): ErgoHistory = { diff --git a/src/test/scala/org/ergoplatform/utils/generators/ValidBlocksGenerators.scala b/src/test/scala/org/ergoplatform/utils/generators/ValidBlocksGenerators.scala index e09ef9c9cc..b29231f4b5 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/ValidBlocksGenerators.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/ValidBlocksGenerators.scala @@ -1,6 +1,6 @@ package org.ergoplatform.utils.generators -import org.ergoplatform.ErgoBox +import org.ergoplatform.{ErgoBox, OrderingBlockFound} import org.ergoplatform.mining.CandidateGenerator import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionCandidate} @@ -213,7 +213,8 @@ object ValidBlocksGenerators val votes = Array.fill(3)(0: Byte) powScheme.proveBlock(parentOpt.map(_.header), Header.InitialVersion, settings.chainSettings.initialNBits, updStateDigest, adProofBytes, - transactions, time, extension, votes, defaultMinerSecretNumber).get + transactions, time, extension, votes, defaultMinerSecretNumber).asInstanceOf[OrderingBlockFound] // todo: fix + .fb } /** @@ -237,7 +238,8 @@ object ValidBlocksGenerators val votes = Array.fill(3)(0: Byte) powScheme.proveBlock(parentOpt, Header.InitialVersion, settings.chainSettings.initialNBits, updStateDigest, - adProofBytes, transactions, time, extension, votes, defaultMinerSecretNumber).get + adProofBytes, transactions, time, extension, votes, defaultMinerSecretNumber).asInstanceOf[OrderingBlockFound] // todo: fix + .fb } private def checkPayload(transactions: Seq[ErgoTransaction], us: UtxoState): Unit = { From 5e623b414bc029e04eec154a70c4e2f348ce6bfa Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 1 Oct 2024 18:09:33 +0300 Subject: [PATCH 045/109] fixing delivery of sub-blocks to processing --- .../mining/CandidateGenerator.scala | 39 ++++++++++--------- .../mining/ErgoMiningThread.scala | 2 + 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index e0c93473d9..58ef8559ef 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -183,25 +183,28 @@ class CandidateGenerator( preSolution } val result: StatusReply[Unit] = { - val newBlock = completeBlock(state.cache.get.candidateBlock, solution) - log.info(s"New block mined, header: ${newBlock.header}") - ergoSettings.chainSettings.powScheme - .validate(newBlock.header) - .map(_ => newBlock) match { - case Success(newBlock) if sf.isInstanceOf[OrderingSolutionFound] => - sendToNodeView(newBlock) - context.become(initialized(state.copy(solvedBlock = Some(newBlock)))) - StatusReply.success(()) - case Success(_) if sf.isInstanceOf[InputSolutionFound] => - log.info("Sub-block mined!") - StatusReply.error( - new Exception(s"Input block found!", new Exception()) - ) - case Failure(exception) => - log.warn(s"Removing candidate due to invalid block", exception) - context.become(initialized(state.copy(cache = None))) + sf match { + case _: OrderingSolutionFound => + val newBlock = completeBlock(state.cache.get.candidateBlock, solution) + log.info(s"New block mined, header: ${newBlock.header}") + ergoSettings.chainSettings.powScheme + .validate(newBlock.header) + .map(_ => newBlock) match { + case Success(newBlock) => + sendToNodeView(newBlock) + context.become(initialized(state.copy(solvedBlock = Some(newBlock)))) + StatusReply.success(()) + case Failure(exception) => + log.warn(s"Removing candidate due to invalid block", exception) + context.become(initialized(state.copy(cache = None))) + StatusReply.error( + new Exception(s"Invalid block mined: ${exception.getMessage}", exception) + ) + } + case _: InputSolutionFound => + log.info("Input=block mined!") StatusReply.error( - new Exception(s"Invalid block mined: ${exception.getMessage}", exception) + new Exception(s"Input block found!") ) } } diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala index 9ff5103f8a..4a09785486 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala @@ -62,6 +62,8 @@ class ErgoMiningThread( } case StatusReply.Error(ex) => log.error(s"Accepting solution or preparing candidate did not succeed", ex) + context.become(mining(nonce + 1, candidateBlock, solvedBlocksCount)) + self ! MineCmd case StatusReply.Success(()) => log.info(s"Solution accepted") context.become(mining(nonce, candidateBlock, solvedBlocksCount + 1)) From c9de5169ffa7eb1500c79e52135d935422703156 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 10 Oct 2024 19:39:01 +0300 Subject: [PATCH 046/109] input block pow validation --- .../src/main/scala/org/ergoplatform/SubBlockAlgos.scala | 8 ++++++++ .../org/ergoplatform/mining/CandidateGenerator.scala | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index c941946ab0..e5109fea80 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -72,6 +72,14 @@ object SubBlockAlgos { } } + def checkInputBlockPoW(header: Header): Boolean = { + val hit = powScheme.hitForVersion2(header) // todo: cache hit in header + + val orderingTarget = powScheme.getB(header.nBits) + val inputTarget = orderingTarget * subsPerBlock + hit < inputTarget + } + // messages: // // sub block signal: diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 58ef8559ef..3326342e4a 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -24,7 +24,7 @@ import org.ergoplatform.settings.{ErgoSettings, ErgoValidationSettingsUpdate, Pa import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox import org.ergoplatform.subblocks.SubBlockInfo import org.ergoplatform.wallet.interpreter.ErgoInterpreter -import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, InputSolutionFound, OrderingSolutionFound, SolutionFound} +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, InputSolutionFound, OrderingSolutionFound, SolutionFound, SubBlockAlgos} import scorex.crypto.hash.Digest32 import scorex.util.encode.Base16 import scorex.util.{ModifierId, ScorexLogging} @@ -202,9 +202,12 @@ class CandidateGenerator( ) } case _: InputSolutionFound => - log.info("Input=block mined!") + log.info("Input-block mined!") + val newBlock = completeBlock(state.cache.get.candidateBlock, solution) + val powValid = SubBlockAlgos.checkInputBlockPoW(newBlock.header) + // todo: check links? send to node view, update state StatusReply.error( - new Exception(s"Input block found!") + new Exception(s"Input block found! PoW valid: $powValid") ) } } From b908142282fb52167456c3eb6b257800b25cdc28 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 10 Oct 2024 22:56:36 +0300 Subject: [PATCH 047/109] completeOrderingBlock / completeInputBlock --- .../ergoplatform/mining/CandidateGenerator.scala | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 3326342e4a..c0129aede8 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -185,7 +185,7 @@ class CandidateGenerator( val result: StatusReply[Unit] = { sf match { case _: OrderingSolutionFound => - val newBlock = completeBlock(state.cache.get.candidateBlock, solution) + val newBlock = completeOrderingBlock(state.cache.get.candidateBlock, solution) log.info(s"New block mined, header: ${newBlock.header}") ergoSettings.chainSettings.powScheme .validate(newBlock.header) @@ -203,7 +203,7 @@ class CandidateGenerator( } case _: InputSolutionFound => log.info("Input-block mined!") - val newBlock = completeBlock(state.cache.get.candidateBlock, solution) + val newBlock = completeInputBlock(state.cache.get.candidateBlock, solution) val powValid = SubBlockAlgos.checkInputBlockPoW(newBlock.header) // todo: check links? send to node view, update state StatusReply.error( @@ -911,7 +911,15 @@ object CandidateGenerator extends ScorexLogging { /** * Assemble `ErgoFullBlock` using candidate block and provided pow solution. */ - def completeBlock(candidate: CandidateBlock, solution: AutolykosSolution): ErgoFullBlock = { + def completeOrderingBlock(candidate: CandidateBlock, solution: AutolykosSolution): ErgoFullBlock = { + val header = deriveUnprovenHeader(candidate).toHeader(solution, None) + val adProofs = ADProofs(header.id, candidate.adProofBytes) + val blockTransactions = BlockTransactions(header.id, candidate.version, candidate.transactions) + val extension = Extension(header.id, candidate.extension.fields) + new ErgoFullBlock(header, blockTransactions, extension, Some(adProofs)) + } + + def completeInputBlock(candidate: CandidateBlock, solution: AutolykosSolution): ErgoFullBlock = { val header = deriveUnprovenHeader(candidate).toHeader(solution, None) val adProofs = ADProofs(header.id, candidate.adProofBytes) val blockTransactions = BlockTransactions(header.id, candidate.version, candidate.transactions) From 08c52d6e0c4914d1cd8c90a553136a60c6290b0e Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 15 Oct 2024 19:15:56 +0300 Subject: [PATCH 048/109] sendInputToNodeView stub (pre-refactoring) --- .../ergoplatform/mining/CandidateGenerator.scala | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index c0129aede8..ac54738bf2 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -58,9 +58,9 @@ class CandidateGenerator( } /** Send solved block to local blockchain controller */ - private def sendToNodeView(newBlock: ErgoFullBlock): Unit = { + private def sendOrderingToNodeView(newBlock: ErgoFullBlock): Unit = { log.info( - s"New block ${newBlock.id} w. nonce ${Longs.fromByteArray(newBlock.header.powSolution.n)}" + s"New ordering block ${newBlock.id} w. nonce ${Longs.fromByteArray(newBlock.header.powSolution.n)}" ) viewHolderRef ! LocallyGeneratedModifier(newBlock.header) val sectionsToApply = if (ergoSettings.nodeSettings.stateType == StateType.Digest) { @@ -71,6 +71,12 @@ class CandidateGenerator( sectionsToApply.foreach(viewHolderRef ! LocallyGeneratedModifier(_)) } + private def sendInputToNodeView(newBlock: ErgoFullBlock): Unit = { + log.info( + s"New input block ${newBlock.id} w. nonce ${Longs.fromByteArray(newBlock.header.powSolution.n)}" + ) + } + override def receive: Receive = { // first we need to get Readers to have some initial state to work with @@ -188,10 +194,10 @@ class CandidateGenerator( val newBlock = completeOrderingBlock(state.cache.get.candidateBlock, solution) log.info(s"New block mined, header: ${newBlock.header}") ergoSettings.chainSettings.powScheme - .validate(newBlock.header) + .validate(newBlock.header) // check header PoW only .map(_ => newBlock) match { case Success(newBlock) => - sendToNodeView(newBlock) + sendOrderingToNodeView(newBlock) context.become(initialized(state.copy(solvedBlock = Some(newBlock)))) StatusReply.success(()) case Failure(exception) => @@ -206,6 +212,7 @@ class CandidateGenerator( val newBlock = completeInputBlock(state.cache.get.candidateBlock, solution) val powValid = SubBlockAlgos.checkInputBlockPoW(newBlock.header) // todo: check links? send to node view, update state + sendInputToNodeView(newBlock) StatusReply.error( new Exception(s"Input block found! PoW valid: $powValid") ) From ac824a46a532a85bb2676682de7522bbb6a5740e Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 16 Oct 2024 12:07:43 +0300 Subject: [PATCH 049/109] LocallyGeneratedInputBlock / LocallyGeneratedOrderingBlock --- ...ala => LocallyGeneratedBlockSection.scala} | 2 +- .../nodeView/LocallyGeneratedInputBlock.scala | 5 +++ .../LocallyGeneratedOrderingBlock.scala | 5 +++ .../http/api/BlocksApiRoute.scala | 6 +-- .../mining/CandidateGenerator.scala | 19 ++++---- .../nodeView/ErgoNodeViewHolder.scala | 16 ++++++- .../nodeView/state/DigestState.scala | 4 +- .../nodeView/state/ErgoState.scala | 4 +- .../nodeView/state/UtxoState.scala | 8 ++-- .../state/wrapped/WrappedDigestState.scala | 4 +- .../state/wrapped/WrappedUtxoState.scala | 4 +- .../viewholder/ErgoNodeViewHolderSpec.scala | 44 +++++++++---------- .../viewholder/PrunedNodeViewHolderSpec.scala | 4 +- .../ergoplatform/utils/NodeViewTestOps.scala | 8 ++-- .../properties/NodeViewHolderTests.scala | 36 +++++++-------- 15 files changed, 95 insertions(+), 74 deletions(-) rename ergo-core/src/main/scala/org/ergoplatform/nodeView/{LocallyGeneratedModifier.scala => LocallyGeneratedBlockSection.scala} (67%) create mode 100644 ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala create mode 100644 ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedOrderingBlock.scala diff --git a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedModifier.scala b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedBlockSection.scala similarity index 67% rename from ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedModifier.scala rename to ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedBlockSection.scala index 27db8ed56f..681c56955b 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedModifier.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedBlockSection.scala @@ -5,4 +5,4 @@ import org.ergoplatform.modifiers.BlockSection /** * Wrapper for locally generated block section */ -case class LocallyGeneratedModifier(blockSection: BlockSection) +case class LocallyGeneratedBlockSection(blockSection: BlockSection) diff --git a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala new file mode 100644 index 0000000000..0c5be7ead1 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala @@ -0,0 +1,5 @@ +package org.ergoplatform.nodeView + +import org.ergoplatform.modifiers.ErgoFullBlock + +case class LocallyGeneratedInputBlock(efb: ErgoFullBlock) diff --git a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedOrderingBlock.scala b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedOrderingBlock.scala new file mode 100644 index 0000000000..10d716f4e9 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedOrderingBlock.scala @@ -0,0 +1,5 @@ +package org.ergoplatform.nodeView + +import org.ergoplatform.modifiers.ErgoFullBlock + +case class LocallyGeneratedOrderingBlock(efb: ErgoFullBlock) diff --git a/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala index 41fc7d8ea9..5cb5cc4cb8 100644 --- a/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala @@ -12,7 +12,7 @@ import org.ergoplatform.nodeView.ErgoReadersHolder.GetDataFromHistory import org.ergoplatform.nodeView.history.ErgoHistoryReader import org.ergoplatform.settings.{Algos, ErgoSettings, RESTApiSettings} import org.ergoplatform.http.api.ApiError.BadRequest -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import scorex.core.api.http.ApiResponse import scorex.crypto.authds.merkle.MerkleProof import scorex.crypto.hash.Digest32 @@ -127,9 +127,9 @@ case class BlocksApiRoute(viewHolderRef: ActorRef, readersHolder: ActorRef, ergo if (ergoSettings.chainSettings.powScheme.validate(block.header).isSuccess) { log.info("Received a new valid block through the API: " + block) - viewHolderRef ! LocallyGeneratedModifier(block.header) + viewHolderRef ! LocallyGeneratedBlockSection(block.header) block.blockSections.foreach { - viewHolderRef ! LocallyGeneratedModifier(_) + viewHolderRef ! LocallyGeneratedBlockSection(_) } ApiResponse.OK diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index ac54738bf2..caa12cd204 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -15,11 +15,11 @@ import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransacti import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.EliminateTransactions import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.{LocallyGeneratedInputBlock, LocallyGeneratedOrderingBlock} import org.ergoplatform.nodeView.history.ErgoHistoryUtils.Height import org.ergoplatform.nodeView.history.{ErgoHistoryReader, ErgoHistoryUtils} import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader -import org.ergoplatform.nodeView.state.{ErgoState, ErgoStateContext, StateType, UtxoStateReader} +import org.ergoplatform.nodeView.state.{ErgoState, ErgoStateContext, UtxoStateReader} import org.ergoplatform.settings.{ErgoSettings, ErgoValidationSettingsUpdate, Parameters} import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox import org.ergoplatform.subblocks.SubBlockInfo @@ -57,24 +57,20 @@ class CandidateGenerator( readersHolderRef ! GetReaders } - /** Send solved block to local blockchain controller */ + /** Send solved ordering block to processing */ private def sendOrderingToNodeView(newBlock: ErgoFullBlock): Unit = { log.info( s"New ordering block ${newBlock.id} w. nonce ${Longs.fromByteArray(newBlock.header.powSolution.n)}" ) - viewHolderRef ! LocallyGeneratedModifier(newBlock.header) - val sectionsToApply = if (ergoSettings.nodeSettings.stateType == StateType.Digest) { - newBlock.blockSections - } else { - newBlock.mandatoryBlockSections - } - sectionsToApply.foreach(viewHolderRef ! LocallyGeneratedModifier(_)) + viewHolderRef ! LocallyGeneratedOrderingBlock(newBlock) } + /** Send solved input block to processing */ private def sendInputToNodeView(newBlock: ErgoFullBlock): Unit = { log.info( s"New input block ${newBlock.id} w. nonce ${Longs.fromByteArray(newBlock.header.powSolution.n)}" ) + viewHolderRef ! LocallyGeneratedInputBlock(newBlock) } override def receive: Receive = { @@ -211,7 +207,8 @@ class CandidateGenerator( log.info("Input-block mined!") val newBlock = completeInputBlock(state.cache.get.candidateBlock, solution) val powValid = SubBlockAlgos.checkInputBlockPoW(newBlock.header) - // todo: check links? send to node view, update state + // todo: check links? + // todo: update candidate generator state sendInputToNodeView(newBlock) StatusReply.error( new Exception(s"Input block found! PoW valid: $powValid") diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 93fe7258c1..df47773158 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -670,9 +670,23 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti } protected def processLocallyGeneratedModifiers: Receive = { - case lm: LocallyGeneratedModifier => + case lm: LocallyGeneratedBlockSection => log.info(s"Got locally generated modifier ${lm.blockSection.encodedId} of type ${lm.blockSection.modifierTypeId}") pmodModify(lm.blockSection, local = true) + case LocallyGeneratedOrderingBlock(efb) => + log.info(s"Got locally generated ordering block ${efb.id}") + pmodModify(efb.header, local = true) + val sectionsToApply = if (settings.nodeSettings.stateType == StateType.Digest) { + efb.blockSections + } else { + efb.mandatoryBlockSections + } + sectionsToApply.foreach { section => + pmodModify(section, local = true) + } + case LocallyGeneratedInputBlock(efb) => + log.info(s"Got locally generated input block ${efb.id}") + // todo: real processing } protected def getCurrentInfo: Receive = { diff --git a/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala b/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala index 65ce34f074..cb3826cec2 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala @@ -13,7 +13,7 @@ import org.ergoplatform.utils.LoggingUtil import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import scorex.db.{ByteArrayWrapper, LDBVersionedStore} import org.ergoplatform.core._ -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import org.ergoplatform.utils.ScorexEncoding import scorex.crypto.authds.ADDigest import scorex.util.ScorexLogging @@ -81,7 +81,7 @@ class DigestState protected(override val version: VersionTag, Failure(new Exception(s"Modifier not validated: $a")) } - override def applyModifier(mod: BlockSection, estimatedTip: Option[Height])(generate: LocallyGeneratedModifier => Unit): Try[DigestState] = + override def applyModifier(mod: BlockSection, estimatedTip: Option[Height])(generate: LocallyGeneratedBlockSection => Unit): Try[DigestState] = (processFullBlock orElse processHeader orElse processOther) (mod) @SuppressWarnings(Array("OptionGet")) diff --git a/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala b/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala index 2dc8e87b83..01ebc180d6 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/ErgoState.scala @@ -17,7 +17,7 @@ import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.ergoplatform.validation.ValidationResult.Valid import org.ergoplatform.validation.{ModifierValidator, ValidationResult} import org.ergoplatform.core.{VersionTag, idToVersion} -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import scorex.crypto.authds.avltree.batch.{Insert, Lookup, Remove} import scorex.crypto.authds.{ADDigest, ADValue} import scorex.util.encode.Base16 @@ -53,7 +53,7 @@ trait ErgoState[IState <: ErgoState[IState]] extends ErgoStateReader { * @param generate function that handles newly created modifier as a result of application the current one * @return new State */ - def applyModifier(mod: BlockSection, estimatedTip: Option[Height])(generate: LocallyGeneratedModifier => Unit): Try[IState] + def applyModifier(mod: BlockSection, estimatedTip: Option[Height])(generate: LocallyGeneratedBlockSection => Unit): Try[IState] def rollbackTo(version: VersionTag): Try[IState] diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala index ef27e16fb0..c23c90535a 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala @@ -13,7 +13,7 @@ import org.ergoplatform.settings.ValidationRules.{fbDigestIncorrect, fbOperation import org.ergoplatform.settings.{Algos, ErgoSettings, Parameters} import org.ergoplatform.utils.LoggingUtil import org.ergoplatform.core._ -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import org.ergoplatform.validation.ModifierValidator import scorex.crypto.authds.avltree.batch._ import scorex.crypto.authds.avltree.batch.serialization.{BatchAVLProverManifest, BatchAVLProverSubtree} @@ -108,7 +108,7 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 } private def applyFullBlock(fb: ErgoFullBlock, estimatedTip: Option[Height]) - (generate: LocallyGeneratedModifier => Unit): Try[UtxoState] = { + (generate: LocallyGeneratedBlockSection => Unit): Try[UtxoState] = { val keepVersions = ergoSettings.nodeSettings.keepVersions // avoid storing versioned information in the database when block being processed is behind @@ -192,7 +192,7 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 if (fb.adProofs.isEmpty) { if (fb.height >= estimatedTip.getOrElse(Int.MaxValue) - ergoSettings.nodeSettings.adProofsSuffixLength) { val adProofs = ADProofs(fb.header.id, proofBytes) - generate(LocallyGeneratedModifier(adProofs)) + generate(LocallyGeneratedBlockSection(adProofs)) } } @@ -213,7 +213,7 @@ class UtxoState(override val persistentProver: PersistentBatchAVLProver[Digest32 } override def applyModifier(mod: BlockSection, estimatedTip: Option[Height]) - (generate: LocallyGeneratedModifier => Unit): Try[UtxoState] = mod match { + (generate: LocallyGeneratedBlockSection => Unit): Try[UtxoState] = mod match { case fb: ErgoFullBlock => applyFullBlock(fb, estimatedTip)(generate) case bs: BlockSection => diff --git a/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedDigestState.scala b/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedDigestState.scala index 256b4c9492..5bad43b8a4 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedDigestState.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedDigestState.scala @@ -5,7 +5,7 @@ import org.ergoplatform.modifiers.BlockSection import org.ergoplatform.nodeView.state.DigestState import org.ergoplatform.settings.ErgoSettings import org.ergoplatform.core.VersionTag -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import scala.util.Try @@ -15,7 +15,7 @@ class WrappedDigestState(val digestState: DigestState, extends DigestState(digestState.version, digestState.rootDigest, digestState.store, settings) { override def applyModifier(mod: BlockSection, estimatedTip: Option[Height]) - (generate: LocallyGeneratedModifier => Unit): Try[WrappedDigestState] = { + (generate: LocallyGeneratedBlockSection => Unit): Try[WrappedDigestState] = { wrapped(super.applyModifier(mod, estimatedTip)(_ => ()), wrappedUtxoState.applyModifier(mod, estimatedTip)(_ => ())) } diff --git a/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedUtxoState.scala b/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedUtxoState.scala index 563387eea6..01cc758d9b 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedUtxoState.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/wrapped/WrappedUtxoState.scala @@ -10,7 +10,7 @@ import org.ergoplatform.settings.{ErgoSettings, Parameters} import org.ergoplatform.settings.Algos.HF import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import org.ergoplatform.core.{VersionTag, idToVersion} -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import scorex.crypto.authds.avltree.batch._ import scorex.crypto.hash.Digest32 import scorex.db.{ByteArrayWrapper, LDBVersionedStore} @@ -36,7 +36,7 @@ class WrappedUtxoState(prover: PersistentBatchAVLProver[Digest32, HF], } override def applyModifier(mod: BlockSection, estimatedTip: Option[Height] = None) - (generate: LocallyGeneratedModifier => Unit): Try[WrappedUtxoState] = + (generate: LocallyGeneratedBlockSection => Unit): Try[WrappedUtxoState] = super.applyModifier(mod, estimatedTip)(generate) match { case Success(us) => mod match { diff --git a/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala b/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala index 3891af32bc..38e2313d4b 100644 --- a/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala @@ -12,7 +12,7 @@ import org.ergoplatform.settings.{Algos, Constants, ErgoSettings} import org.ergoplatform.utils.{ErgoCorePropertyTest, NodeViewTestConfig, NodeViewTestOps, TestCase} import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages._ import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ -import org.ergoplatform.nodeView.{ErgoNodeViewHolder, LocallyGeneratedModifier} +import org.ergoplatform.nodeView.{ErgoNodeViewHolder, LocallyGeneratedBlockSection} import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.ChainProgress import org.ergoplatform.nodeView.mempool.ErgoMemPoolUtils.ProcessingOutcome.Accepted import org.ergoplatform.wallet.utils.FileUtils @@ -66,7 +66,7 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w subscribeEvents(classOf[SyntacticallySuccessfulModifier]) //sending header - nodeViewHolderRef ! LocallyGeneratedModifier(block.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(block.header) expectMsgType[SyntacticallySuccessfulModifier] getHistoryHeight shouldBe GenesisHeight @@ -106,15 +106,15 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w val genesis = validFullBlock(parentOpt = None, us, bh) subscribeEvents(classOf[SyntacticallySuccessfulModifier]) - nodeViewHolderRef ! LocallyGeneratedModifier(genesis.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(genesis.header) expectMsgType[SyntacticallySuccessfulModifier] if (verifyTransactions) { - nodeViewHolderRef ! LocallyGeneratedModifier(genesis.blockTransactions) + nodeViewHolderRef ! LocallyGeneratedBlockSection(genesis.blockTransactions) expectMsgType[SyntacticallySuccessfulModifier] - nodeViewHolderRef ! LocallyGeneratedModifier(genesis.adProofs.value) + nodeViewHolderRef ! LocallyGeneratedBlockSection(genesis.adProofs.value) expectMsgType[SyntacticallySuccessfulModifier] - nodeViewHolderRef ! LocallyGeneratedModifier(genesis.extension) + nodeViewHolderRef ! LocallyGeneratedBlockSection(genesis.extension) expectMsgType[SyntacticallySuccessfulModifier] getBestFullBlockOpt shouldBe Some(genesis) } @@ -256,9 +256,9 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w val (us, bh) = createUtxoState(fixture.settings) val genesis = validFullBlock(parentOpt = None, us, bh) - nodeViewHolderRef ! LocallyGeneratedModifier(genesis.header) - nodeViewHolderRef ! LocallyGeneratedModifier(genesis.blockTransactions) - nodeViewHolderRef ! LocallyGeneratedModifier(genesis.extension) + nodeViewHolderRef ! LocallyGeneratedBlockSection(genesis.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(genesis.blockTransactions) + nodeViewHolderRef ! LocallyGeneratedBlockSection(genesis.extension) getBestFullBlockOpt shouldBe Some(genesis) getModifierById(genesis.adProofs.value.id) shouldBe genesis.adProofs @@ -306,7 +306,7 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w subscribeEvents(classOf[RecoverableFailedModification]) subscribeEvents(classOf[SyntacticallySuccessfulModifier]) - nodeViewHolderRef ! LocallyGeneratedModifier(chain2block1.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(chain2block1.header) expectMsgType[SyntacticallySuccessfulModifier] applyBlock(chain2block2, excludeExt = true) shouldBe 'success @@ -330,7 +330,7 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w subscribeEvents(classOf[SyntacticallyFailedModification]) //sending header - nodeViewHolderRef ! LocallyGeneratedModifier(block.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(block.header) expectMsgType[SyntacticallySuccessfulModifier] val currentHeight = getHistoryHeight currentHeight shouldBe GenesisHeight @@ -357,16 +357,16 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w block.blockTransactions.copy(txs = wrongTxs) } - nodeViewHolderRef ! LocallyGeneratedModifier(recoverableTxs) + nodeViewHolderRef ! LocallyGeneratedBlockSection(recoverableTxs) expectMsgType[RecoverableFailedModification] - nodeViewHolderRef ! LocallyGeneratedModifier(invalidTxsWithWrongOutputs) + nodeViewHolderRef ! LocallyGeneratedBlockSection(invalidTxsWithWrongOutputs) expectMsgType[SyntacticallyFailedModification] - nodeViewHolderRef ! LocallyGeneratedModifier(invalidTxsWithWrongInputs) + nodeViewHolderRef ! LocallyGeneratedBlockSection(invalidTxsWithWrongInputs) expectMsgType[SyntacticallyFailedModification] - nodeViewHolderRef ! LocallyGeneratedModifier(block.blockTransactions) + nodeViewHolderRef ! LocallyGeneratedBlockSection(block.blockTransactions) expectMsgType[SyntacticallySuccessfulModifier] } @@ -384,7 +384,7 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w subscribeEvents(classOf[SyntacticallyFailedModification]) //sending header - nodeViewHolderRef ! LocallyGeneratedModifier(block.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(block.header) expectMsgType[SyntacticallySuccessfulModifier] val randomId = modifierIdGen.sample.value @@ -392,13 +392,13 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w val wrongProofs1 = block.adProofs.map(_.copy(headerId = randomId)) val wrongProofs2 = block.adProofs.map(_.copy(proofBytes = wrongProofsBytes)) - nodeViewHolderRef ! LocallyGeneratedModifier(wrongProofs1.value) + nodeViewHolderRef ! LocallyGeneratedBlockSection(wrongProofs1.value) expectMsgType[RecoverableFailedModification] - nodeViewHolderRef ! LocallyGeneratedModifier(wrongProofs2.value) + nodeViewHolderRef ! LocallyGeneratedBlockSection(wrongProofs2.value) expectMsgType[SyntacticallyFailedModification] - nodeViewHolderRef ! LocallyGeneratedModifier(block.adProofs.value) + nodeViewHolderRef ! LocallyGeneratedBlockSection(block.adProofs.value) expectMsgType[SyntacticallySuccessfulModifier] } @@ -417,7 +417,7 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w subscribeEvents(classOf[SyntacticallyFailedModification]) //sending header - nodeViewHolderRef ! LocallyGeneratedModifier(block.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(block.header) expectMsgType[SyntacticallyFailedModification] getBestHeaderOpt shouldBe None getHistoryHeight shouldBe EmptyHistoryHeight @@ -436,7 +436,7 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w subscribeEvents(classOf[SyntacticallySuccessfulModifier]) subscribeEvents(classOf[SyntacticallyFailedModification]) - nodeViewHolderRef ! LocallyGeneratedModifier(block.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(block.header) expectMsgType[SyntacticallySuccessfulModifier] getHistoryHeight shouldBe GenesisHeight getHeightOf(block.header.id) shouldBe Some(GenesisHeight) @@ -485,7 +485,7 @@ class ErgoNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps w subscribeEvents(classOf[SyntacticallySuccessfulModifier]) subscribeEvents(classOf[SyntacticallyFailedModification]) - nodeViewHolderRef ! LocallyGeneratedModifier(header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(header) expectMsgType[SyntacticallyFailedModification] getHistoryHeight shouldBe EmptyHistoryHeight getHeightOf(header.id) shouldBe None diff --git a/src/test/scala/org/ergoplatform/nodeView/viewholder/PrunedNodeViewHolderSpec.scala b/src/test/scala/org/ergoplatform/nodeView/viewholder/PrunedNodeViewHolderSpec.scala index 4f7e11e2c7..5dbb7a2e86 100644 --- a/src/test/scala/org/ergoplatform/nodeView/viewholder/PrunedNodeViewHolderSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/viewholder/PrunedNodeViewHolderSpec.scala @@ -3,7 +3,7 @@ package org.ergoplatform.nodeView.viewholder import akka.actor.ActorRef import org.ergoplatform.mining.DefaultFakePowScheme import org.ergoplatform.modifiers.ErgoFullBlock -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import org.ergoplatform.nodeView.state.wrapped.WrappedUtxoState import org.ergoplatform.nodeView.state.{DigestState, StateType} import org.ergoplatform.settings.{ErgoSettings, ErgoSettingsReader, VotingSettings} @@ -59,7 +59,7 @@ class PrunedNodeViewHolderSpec extends ErgoCorePropertyTest with NodeViewTestOps fullChain.takeRight(totalBlocks - toSkip).foreach { block => block.blockSections.foreach { section => - nodeViewHolderRef ! LocallyGeneratedModifier(section) + nodeViewHolderRef ! LocallyGeneratedBlockSection(section) Thread.sleep(50) } } diff --git a/src/test/scala/org/ergoplatform/utils/NodeViewTestOps.scala b/src/test/scala/org/ergoplatform/utils/NodeViewTestOps.scala index f18c6e5d93..d850a925a5 100644 --- a/src/test/scala/org/ergoplatform/utils/NodeViewTestOps.scala +++ b/src/test/scala/org/ergoplatform/utils/NodeViewTestOps.scala @@ -13,7 +13,7 @@ import org.ergoplatform.settings.Algos import org.ergoplatform.nodeView.ErgoNodeViewHolder.CurrentView import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.GetDataFromCurrentView import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import org.ergoplatform.utils.ErgoNodeTestConstants.defaultTimeout import org.ergoplatform.utils.generators.ValidBlocksGenerators.validFullBlock import org.ergoplatform.validation.MalformedModifierError @@ -44,13 +44,13 @@ trait NodeViewBaseOps extends ErgoTestHelpers { def applyHeader(header: Header)(implicit ctx: Ctx): Try[Unit] = { subscribeModificationOutcome() - nodeViewHolderRef ! LocallyGeneratedModifier(header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(header) expectModificationOutcome(header) } def applyBlock(fullBlock: ErgoFullBlock, excludeExt: Boolean = false)(implicit ctx: Ctx): Try[Unit] = { subscribeModificationOutcome() - nodeViewHolderRef ! LocallyGeneratedModifier(fullBlock.header) + nodeViewHolderRef ! LocallyGeneratedBlockSection(fullBlock.header) expectModificationOutcome(fullBlock.header).flatMap(_ => applyPayload(fullBlock, excludeExt)) } @@ -65,7 +65,7 @@ trait NodeViewBaseOps extends ErgoTestHelpers { } sections.foldLeft(Success(()): Try[Unit]) { (lastResult, section) => lastResult.flatMap { _ => - nodeViewHolderRef ! LocallyGeneratedModifier(section) + nodeViewHolderRef ! LocallyGeneratedBlockSection(section) section match { case Extension(_, Seq(), _) => Success(()) // doesn't send back any outcome case _ => expectModificationOutcome(section) // normal flow diff --git a/src/test/scala/scorex/testkit/properties/NodeViewHolderTests.scala b/src/test/scala/scorex/testkit/properties/NodeViewHolderTests.scala index 25f63eb68c..d10908f375 100644 --- a/src/test/scala/scorex/testkit/properties/NodeViewHolderTests.scala +++ b/src/test/scala/scorex/testkit/properties/NodeViewHolderTests.scala @@ -9,7 +9,7 @@ import org.scalatest.propspec.AnyPropSpec import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ import org.ergoplatform.nodeView.ErgoNodeViewHolder.CurrentView import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.GetDataFromCurrentView -import org.ergoplatform.nodeView.LocallyGeneratedModifier +import org.ergoplatform.nodeView.LocallyGeneratedBlockSection import org.ergoplatform.nodeView.state.ErgoState import scorex.testkit.generators import scorex.testkit.utils.AkkaFixture @@ -74,7 +74,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] system.eventStream.subscribe(eventListener.ref, classOf[SyntacticallySuccessfulModifier]) p.send(node, GetDataFromCurrentView[ST, BlockSection] { v => totallyValidModifiers(v.history, v.state, 2).head }) val mod = p.expectMsgClass(classOf[BlockSection]) - p.send(node, LocallyGeneratedModifier(mod)) + p.send(node, LocallyGeneratedBlockSection(mod)) eventListener.expectMsgType[SyntacticallySuccessfulModifier] } } @@ -86,7 +86,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] system.eventStream.subscribe(eventListener.ref, classOf[SyntacticallyFailedModification]) val invalid = syntacticallyInvalidModifier(h) - p.send(node, LocallyGeneratedModifier(invalid)) + p.send(node, LocallyGeneratedBlockSection(invalid)) eventListener.expectMsgType[SyntacticallyFailedModification] } } @@ -100,7 +100,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] system.eventStream.subscribe(eventListener.ref, classOf[FullBlockApplied]) p.send(node, GetDataFromCurrentView[ST, BlockSection] { v => totallyValidModifiers(v.history, v.state, 2).head }) val mod = p.expectMsgClass(classOf[BlockSection]) - p.send(node, LocallyGeneratedModifier(mod)) + p.send(node, LocallyGeneratedBlockSection(mod)) eventListener.expectMsgType[SyntacticallySuccessfulModifier] eventListener.expectMsgType[FullBlockApplied] } @@ -115,7 +115,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] system.eventStream.subscribe(eventListener.ref, classOf[SemanticallyFailedModification]) p.send(node, GetDataFromCurrentView[ST, BlockSection] { v => semanticallyInvalidModifier(v.state) }) val invalid = p.expectMsgClass(classOf[BlockSection]) - p.send(node, LocallyGeneratedModifier(invalid)) + p.send(node, LocallyGeneratedBlockSection(invalid)) eventListener.expectMsgType[SyntacticallySuccessfulModifier] eventListener.expectMsgType[SemanticallyFailedModification] } @@ -130,7 +130,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] system.eventStream.subscribe(eventListener.ref, classOf[FullBlockApplied]) p.send(node, GetDataFromCurrentView[ST, BlockSection] { v => totallyValidModifiers(v.history, v.state, 2).head }) val mod = p.expectMsgClass(classOf[BlockSection]) - p.send(node, LocallyGeneratedModifier(mod)) + p.send(node, LocallyGeneratedBlockSection(mod)) eventListener.expectMsgType[SyntacticallySuccessfulModifier] eventListener.expectMsgType[FullBlockApplied] } @@ -173,7 +173,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] val mods = p.expectMsgClass(classOf[Seq[BlockSection]]) mods.foreach { mod => - p.send(node, LocallyGeneratedModifier(mod)) + p.send(node, LocallyGeneratedBlockSection(mod)) } (1 to mods.size).foreach(_ => eventListener.expectMsgType[SyntacticallySuccessfulModifier]) @@ -190,11 +190,11 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] val invalid = syntacticallyInvalidModifier(h) - p.send(node, LocallyGeneratedModifier(invalid)) + p.send(node, LocallyGeneratedBlockSection(invalid)) eventListener.expectMsgType[SyntacticallyFailedModification] - p.send(node, LocallyGeneratedModifier(mod)) + p.send(node, LocallyGeneratedBlockSection(mod)) eventListener.expectMsgType[SyntacticallySuccessfulModifier] @@ -219,7 +219,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] p.send(node, GetDataFromCurrentView[ST, Seq[BlockSection]] { v => totallyValidModifiers(v.history, v.state, 2) }) val initMods = p.expectMsgClass(waitDuration, classOf[Seq[BlockSection]]) initMods.foreach { mod => - p.send(node, LocallyGeneratedModifier(mod)) + p.send(node, LocallyGeneratedBlockSection(mod)) eventListener.expectMsgType[SyntacticallySuccessfulModifier] } @@ -233,8 +233,8 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] }) val fork2Mod = p.expectMsgClass(waitDuration, classOf[BlockSection]) - p.send(node, LocallyGeneratedModifier(fork1Mod)) - p.send(node, LocallyGeneratedModifier(fork2Mod)) + p.send(node, LocallyGeneratedBlockSection(fork1Mod)) + p.send(node, LocallyGeneratedBlockSection(fork2Mod)) eventListener.expectMsgType[SyntacticallySuccessfulModifier] eventListener.expectMsgType[SyntacticallySuccessfulModifier] @@ -268,7 +268,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] totallyValidModifiers(v.history, v.state, opCountBeforeFork) }) val plainMods = p.expectMsgClass(waitDuration, classOf[Seq[BlockSection]]) - plainMods.foreach { mod => p.send(node, LocallyGeneratedModifier(mod)) } + plainMods.foreach { mod => p.send(node, LocallyGeneratedBlockSection(mod)) } p.send(node, GetDataFromCurrentView[ST, Seq[BlockSection]] { v => val mods = totallyValidModifiers(v.history, v.state, fork1OpCount) @@ -282,8 +282,8 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] }) val fork2Mods = p.expectMsgClass(waitDuration, classOf[Seq[BlockSection]]) - fork1Mods.foreach { mod => p.send(node, LocallyGeneratedModifier(mod)) } - fork2Mods.foreach { mod => p.send(node, LocallyGeneratedModifier(mod)) } + fork1Mods.foreach { mod => p.send(node, LocallyGeneratedBlockSection(mod)) } + fork2Mods.foreach { mod => p.send(node, LocallyGeneratedBlockSection(mod)) } p.send(node, GetDataFromCurrentView[ST, Boolean] { v => v.history.bestFullBlockIdOpt.orElse(v.history.bestHeaderIdOpt).contains(fork2Mods.last.id) @@ -303,7 +303,7 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] withView(node) { v => totallyValidModifiers(v.history, v.state, opCountBeforeFork) }.foreach { - mod => node ! LocallyGeneratedModifier(mod) + mod => node ! LocallyGeneratedBlockSection(mod) } // generate the first fork with valid blocks val fork1Mods = withView(node) { v => @@ -319,9 +319,9 @@ trait NodeViewHolderTests[ST <: ErgoState[ST]] generators.Valid, generators.Valid, generators.Valid, generators.Valid, generators.Valid, generators.Valid)) } // apply the first fork with valid blocks - fork1Mods.foreach { mod => node ! LocallyGeneratedModifier(mod) } + fork1Mods.foreach { mod => node ! LocallyGeneratedBlockSection(mod) } // apply the second fork with invalid block - fork2Mods.foreach { mod => node ! LocallyGeneratedModifier(mod) } + fork2Mods.foreach { mod => node ! LocallyGeneratedBlockSection(mod) } // verify that open surface consist of last block of the first chain, // or first block of the second chain, or both, but no any other option withView(node) { v => From 4fc43c5ca06b3f3f9c03b13d695e26ab4416b543 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 18 Oct 2024 16:05:18 +0300 Subject: [PATCH 050/109] reworked LocallyGeneratedInputBlock signature --- .../nodeView/LocallyGeneratedInputBlock.scala | 5 +++-- .../org/ergoplatform/mining/CandidateGenerator.scala | 12 ++++++++---- .../ergoplatform/nodeView/ErgoNodeViewHolder.scala | 8 +++++--- .../modifierprocessors/SubBlocksProcessor.scala | 4 ++++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala index 0c5be7ead1..8a554e3f99 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala @@ -1,5 +1,6 @@ package org.ergoplatform.nodeView -import org.ergoplatform.modifiers.ErgoFullBlock +import org.ergoplatform.network.message.subblocks.SubBlockTransactionsData +import org.ergoplatform.subblocks.SubBlockInfo -case class LocallyGeneratedInputBlock(efb: ErgoFullBlock) +case class LocallyGeneratedInputBlock(sbi: SubBlockInfo, sbt: SubBlockTransactionsData) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index caa12cd204..66de02ec39 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -13,6 +13,7 @@ import org.ergoplatform.modifiers.history.header.{Header, HeaderWithoutPow} import org.ergoplatform.modifiers.history.popow.NipopowAlgos import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ +import org.ergoplatform.network.message.subblocks.SubBlockTransactionsData import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.EliminateTransactions import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.{LocallyGeneratedInputBlock, LocallyGeneratedOrderingBlock} @@ -66,11 +67,11 @@ class CandidateGenerator( } /** Send solved input block to processing */ - private def sendInputToNodeView(newBlock: ErgoFullBlock): Unit = { + private def sendInputToNodeView(sbi: SubBlockInfo, sbt: SubBlockTransactionsData): Unit = { log.info( - s"New input block ${newBlock.id} w. nonce ${Longs.fromByteArray(newBlock.header.powSolution.n)}" + s"New input block ${sbi.subBlock.id} w. nonce ${Longs.fromByteArray(sbi.subBlock.powSolution.n)}" ) - viewHolderRef ! LocallyGeneratedInputBlock(newBlock) + viewHolderRef ! LocallyGeneratedInputBlock(sbi, sbt) } override def receive: Receive = { @@ -209,7 +210,10 @@ class CandidateGenerator( val powValid = SubBlockAlgos.checkInputBlockPoW(newBlock.header) // todo: check links? // todo: update candidate generator state - sendInputToNodeView(newBlock) + // todo: form and send real data + val sbi: SubBlockInfo = null + val sbt : SubBlockTransactionsData = null + sendInputToNodeView(sbi, sbt) StatusReply.error( new Exception(s"Input block found! PoW valid: $powValid") ) diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index df47773158..6424690e14 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -684,9 +684,11 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti sectionsToApply.foreach { section => pmodModify(section, local = true) } - case LocallyGeneratedInputBlock(efb) => - log.info(s"Got locally generated input block ${efb.id}") - // todo: real processing + case LocallyGeneratedInputBlock(sbi, sbt) => + log.info(s"Got locally generated input block ${sbi.subBlock.id}") + history().applySubBlockHeader(sbi) + history().applySubBlockTransactions(sbi.subBlock.id, sbt.transactions) + // todo: finish processing } protected def getCurrentInfo: Receive = { diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala index 99aeceede0..d18033026c 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala @@ -8,7 +8,11 @@ import scala.collection.mutable trait SubBlocksProcessor extends ScorexLogging { + /** + * Pointer to a best input-block known + */ var _bestSubblock: Option[SubBlockInfo] = None + val subBlockRecords = mutable.Map[ModifierId, SubBlockInfo]() val subBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() From ef02664c73a34035f7aebc8ad70b7f8babbe70c8 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 30 Oct 2024 12:45:50 +0300 Subject: [PATCH 051/109] forum post --- .../ergoplatform/subblocks/SubBlockInfo.scala | 2 +- papers/subblocks-forum.md | 28 +++++++++++++++++++ .../mining/CandidateGenerator.scala | 8 +++++- 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 papers/subblocks-forum.md diff --git a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala index eb1ad0c820..c8e058dc77 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala @@ -38,7 +38,7 @@ case class SubBlockInfo(version: Byte, object SubBlockInfo { - val initialMessageVersion = 1 + val initialMessageVersion = 1.toByte private val bmp = new BatchMerkleProofSerializer[Digest32, CryptographicHash[Digest32]]()(Blake2b256) diff --git a/papers/subblocks-forum.md b/papers/subblocks-forum.md new file mode 100644 index 0000000000..1a88648dfc --- /dev/null +++ b/papers/subblocks-forum.md @@ -0,0 +1,28 @@ +Ok, so after re-checking Prism and checking some new papers (such as new parallel PoW paper https://iacr.org/submit/files/slides/2024/eurocrypt/eurocrypt2024/482/slides.pdf ), I think, it makes sense to split blocks into input blocks and ordering blocks with some new block validation rules introduced via SF, however, with rich context available during script execution, there are some complexities which are not covered in the papers and we have to bypass: + +assume number of sub-blocks (input blocks) per (ordering) block is equal to 128 (but it can be adjustable via miners voting): + +* an ordering block is defined as block in Ergo now, hash(block) < Target +* input block is defined as sub-block , Target <= hash(block_header) < Target * 128, actually, 2-for-1 PoW option (so reverse(hash(block_header)) < Target * 128) + from GKL15 / parallel PoW papers is likely better but need to check what is needed from pools to support that + +thus we have blockchain like + +(ordering) block - input block - input block - input block - (ordering) block - input block - input block - (ordering) block + +* transactions are broken into two classes, for first one result of transaction validation can't change from one input block to other , for the second, validation result can vary (this is true for transactions relying on block timestamp, miner pubkey, timestamp). +* only transactions of the first class (about 99% of all transactions normally) can be included in input (sub) blocks only. Transactions of the second class can be included in both kinds of blocks. +* as a miner does not know in advance, he is preparing for both options by: + - setting Merkle tree root of the block header to transactions seen in the last input block and before that (since the last ordering block) plus new second-class transactions + setting 3 new fields in extension field of a block: + - setting a new field to new transactions included + - setting a new field to removed second-class transactions (first-class cant be removed) + - setting a new field to reference to a last seen input block (or Merkle tree of input blocks seen since last ordering block maybe) +* miners are getting tx fees and storage rent from input (sub) blocks, constant reward from (ordering) blocks. For tx fees to be collectable in input blocks, fee script should be changed to "true" just (I have early draft of such EIP for long time, this script would be good to make transactions more lightweight as well) + + +This should provide fast and quite reliable confirmations for most of transactions. + +And only mining nodes update would be needed, while older nodes can receive ordinary block transactions message after every ordering block. + +And all the new rules will be made soft-forkable. \ No newline at end of file diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 66de02ec39..a65080a35a 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -26,6 +26,7 @@ import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox import org.ergoplatform.subblocks.SubBlockInfo import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, InputSolutionFound, OrderingSolutionFound, SolutionFound, SubBlockAlgos} +import scorex.crypto.authds.merkle.BatchMerkleProof import scorex.crypto.hash.Digest32 import scorex.util.encode.Base16 import scorex.util.{ModifierId, ScorexLogging} @@ -211,7 +212,12 @@ class CandidateGenerator( // todo: check links? // todo: update candidate generator state // todo: form and send real data - val sbi: SubBlockInfo = null + + val prevSubBlockId: Option[Array[Byte]] = null + val subblockTransactionsDigest: Digest32 = null + val merkleProof: BatchMerkleProof[Digest32] = null + + val sbi: SubBlockInfo = SubBlockInfo(SubBlockInfo.initialMessageVersion, newBlock.header, prevSubBlockId, subblockTransactionsDigest, merkleProof) val sbt : SubBlockTransactionsData = null sendInputToNodeView(sbi, sbt) StatusReply.error( From 4011aa5477602fbb4af4d33c53767ebd337f6582 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 30 Oct 2024 13:43:28 +0300 Subject: [PATCH 052/109] removing subblock field from Candidate --- papers/subblocks-forum.md | 2 +- .../org/ergoplatform/mining/CandidateGenerator.scala | 8 +++----- .../scala/org/ergoplatform/mining/ErgoMiningThread.scala | 4 ++-- src/test/scala/org/ergoplatform/utils/Stubs.scala | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/papers/subblocks-forum.md b/papers/subblocks-forum.md index 1a88648dfc..69cda824c0 100644 --- a/papers/subblocks-forum.md +++ b/papers/subblocks-forum.md @@ -10,7 +10,7 @@ thus we have blockchain like (ordering) block - input block - input block - input block - (ordering) block - input block - input block - (ordering) block -* transactions are broken into two classes, for first one result of transaction validation can't change from one input block to other , for the second, validation result can vary (this is true for transactions relying on block timestamp, miner pubkey, timestamp). +* transactions are broken into two classes, for first one result of transaction validation can't change from one input block to other , for the second, validation result can vary (this is true for transactions relying on block timestamp, miner pubkey). * only transactions of the first class (about 99% of all transactions normally) can be included in input (sub) blocks only. Transactions of the second class can be included in both kinds of blocks. * as a miner does not know in advance, he is preparing for both options by: - setting Merkle tree root of the block header to transactions seen in the last input block and before that (since the last ordering block) plus new second-class transactions diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index a65080a35a..d35dc85412 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -250,8 +250,7 @@ object CandidateGenerator extends ScorexLogging { case class Candidate( candidateBlock: CandidateBlock, externalVersion: WorkMessage, - txsToInclude: Seq[ErgoTransaction], - subBlock: Boolean + txsToInclude: Seq[ErgoTransaction] ) case class GenerateCandidate( @@ -583,7 +582,7 @@ object CandidateGenerator extends ScorexLogging { s" with ${candidate.transactions.size} transactions, msg ${Base16.encode(ext.msg)}" ) Success( - Candidate(candidate, ext, prioritizedTransactions, subBlock = false) -> eliminateTransactions + Candidate(candidate, ext, prioritizedTransactions) -> eliminateTransactions ) case Failure(t: Throwable) => // We can not produce a block for some reason, so print out an error @@ -612,8 +611,7 @@ object CandidateGenerator extends ScorexLogging { Candidate( candidate, deriveWorkMessage(candidate), - prioritizedTransactions, - subBlock = false + prioritizedTransactions ) -> eliminateTransactions } case None => diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala index 4a09785486..350b1fff6f 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala @@ -41,7 +41,7 @@ class ErgoMiningThread( log.info(s"Stopping miner thread: ${self.path.name}") override def receive: Receive = { - case StatusReply.Success(Candidate(candidateBlock, _, _, _)) => + case StatusReply.Success(Candidate(candidateBlock, _, _)) => log.info(s"Initiating block mining") context.become(mining(nonce = 0, candidateBlock, solvedBlocksCount = 0)) self ! MineCmd @@ -54,7 +54,7 @@ class ErgoMiningThread( candidateBlock: CandidateBlock, solvedBlocksCount: Int ): Receive = { - case StatusReply.Success(Candidate(cb, _, _, _)) => + case StatusReply.Success(Candidate(cb, _, _)) => // if we get new candidate instead of a cached one, mine it if (cb.timestamp != candidateBlock.timestamp) { context.become(mining(nonce = 0, cb, solvedBlocksCount)) diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index 4867c914e0..d4b76fa072 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -113,7 +113,7 @@ trait Stubs extends ErgoTestHelpers with TestFileUtils { def receive: Receive = { case CandidateGenerator.GenerateCandidate(_, reply) => if (reply) { - val candidate = Candidate(null, externalWorkMessage, Seq.empty, subBlock = false) // API does not use CandidateBlock + val candidate = Candidate(null, externalWorkMessage, Seq.empty) // API does not use CandidateBlock sender() ! StatusReply.success(candidate) } case _: AutolykosSolution => sender() ! StatusReply.success(()) From c81839dc2d8a082a1061e7e9bb25fa372293cca1 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 10 Dec 2024 19:16:01 +0300 Subject: [PATCH 053/109] papers/subblocks folder --- papers/{ => subblocks}/subblocks-forum.md | 0 papers/{ => subblocks}/subblocks.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename papers/{ => subblocks}/subblocks-forum.md (100%) rename papers/{ => subblocks}/subblocks.md (100%) diff --git a/papers/subblocks-forum.md b/papers/subblocks/subblocks-forum.md similarity index 100% rename from papers/subblocks-forum.md rename to papers/subblocks/subblocks-forum.md diff --git a/papers/subblocks.md b/papers/subblocks/subblocks.md similarity index 100% rename from papers/subblocks.md rename to papers/subblocks/subblocks.md From 319f7a63f1347dc9ab9b654271ac410992d09ce8 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 11 Dec 2024 00:27:16 +0300 Subject: [PATCH 054/109] forming SubBlockInfo, SubBlockTransactionsData moved to completeInputBlock --- .../subblocks/SubBlockMessageSpec.scala | 2 +- .../mining/CandidateGenerator.scala | 37 ++++++++++--------- .../nodeView/ErgoNodeViewHolder.scala | 8 ++-- .../SubBlocksProcessor.scala | 2 + 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala index 647c415fa6..c611bc6085 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala @@ -1,7 +1,7 @@ package org.ergoplatform.network.message.subblocks import org.ergoplatform.network.message.MessageConstants.MessageCode -import org.ergoplatform.network.message.{InvData, MessageSpecInitial, MessageSpecSubblocks} +import org.ergoplatform.network.message.MessageSpecSubblocks import org.ergoplatform.subblocks.SubBlockInfo import scorex.util.serialization.{Reader, Writer} diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index d35dc85412..5929023ce3 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -207,21 +207,11 @@ class CandidateGenerator( } case _: InputSolutionFound => log.info("Input-block mined!") - val newBlock = completeInputBlock(state.cache.get.candidateBlock, solution) - val powValid = SubBlockAlgos.checkInputBlockPoW(newBlock.header) - // todo: check links? - // todo: update candidate generator state - // todo: form and send real data - - val prevSubBlockId: Option[Array[Byte]] = null - val subblockTransactionsDigest: Digest32 = null - val merkleProof: BatchMerkleProof[Digest32] = null - - val sbi: SubBlockInfo = SubBlockInfo(SubBlockInfo.initialMessageVersion, newBlock.header, prevSubBlockId, subblockTransactionsDigest, merkleProof) - val sbt : SubBlockTransactionsData = null + val (sbi, sbt) = completeInputBlock(state.cache.get.candidateBlock, solution) sendInputToNodeView(sbi, sbt) + StatusReply.error( - new Exception(s"Input block found! PoW valid: $powValid") + new Exception(s"Input block found! PoW valid: ${SubBlockAlgos.checkInputBlockPoW(sbi.subBlock)}") ) } } @@ -931,12 +921,23 @@ object CandidateGenerator extends ScorexLogging { new ErgoFullBlock(header, blockTransactions, extension, Some(adProofs)) } - def completeInputBlock(candidate: CandidateBlock, solution: AutolykosSolution): ErgoFullBlock = { + def completeInputBlock(candidate: CandidateBlock, solution: AutolykosSolution): (SubBlockInfo, SubBlockTransactionsData) = { + + // todo: check links? + // todo: update candidate generator state + // todo: form and send real data instead of null + + val prevSubBlockId: Option[Array[Byte]] = null + val subblockTransactionsDigest: Digest32 = null + val merkleProof: BatchMerkleProof[Digest32] = null + val header = deriveUnprovenHeader(candidate).toHeader(solution, None) - val adProofs = ADProofs(header.id, candidate.adProofBytes) - val blockTransactions = BlockTransactions(header.id, candidate.version, candidate.transactions) - val extension = Extension(header.id, candidate.extension.fields) - new ErgoFullBlock(header, blockTransactions, extension, Some(adProofs)) + val txs = candidate.transactions + + val sbi: SubBlockInfo = SubBlockInfo(SubBlockInfo.initialMessageVersion, header, prevSubBlockId, subblockTransactionsDigest, merkleProof) + val sbt : SubBlockTransactionsData = SubBlockTransactionsData(sbi.subBlock.id, txs) + + (sbi, sbt) } } diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 6424690e14..8ffca3bcae 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -684,10 +684,10 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti sectionsToApply.foreach { section => pmodModify(section, local = true) } - case LocallyGeneratedInputBlock(sbi, sbt) => - log.info(s"Got locally generated input block ${sbi.subBlock.id}") - history().applySubBlockHeader(sbi) - history().applySubBlockTransactions(sbi.subBlock.id, sbt.transactions) + case LocallyGeneratedInputBlock(subblockInfo, subBlockTransactionsData) => + log.info(s"Got locally generated input block ${subblockInfo.subBlock.id}") + history().applySubBlockHeader(subblockInfo) + history().applySubBlockTransactions(subblockInfo.subBlock.id, subBlockTransactionsData.transactions) // todo: finish processing } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala index d18033026c..9baec15136 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala @@ -16,6 +16,7 @@ trait SubBlocksProcessor extends ScorexLogging { val subBlockRecords = mutable.Map[ModifierId, SubBlockInfo]() val subBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() + // reset sub-blocks structures, should be called on receiving ordering block (or slightly later?) def resetState() = { _bestSubblock = None @@ -26,6 +27,7 @@ trait SubBlocksProcessor extends ScorexLogging { // sub-blocks related logic def applySubBlockHeader(sbi: SubBlockInfo): Unit = { + // new ordering block arrived ( should be processed outside ? ) if (sbi.subBlock.height > _bestSubblock.map(_.subBlock.height).getOrElse(-1)) { resetState() } From caf9f3f76497ee33acd8ccc37021c67cf9a002ed Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 23 Dec 2024 21:16:49 +0300 Subject: [PATCH 055/109] subblock -> inputblock --- .../org/ergoplatform/SubBlockAlgos.scala | 8 +-- .../network/message/MessageSpec.scala | 2 +- ...Spec.scala => InputBlockMessageSpec.scala} | 14 ++-- ...scala => InputBlockTransactionsData.scala} | 2 +- ...> InputBlockTransactionsMessageSpec.scala} | 14 ++-- ...BlockTransactionsRequestMessageSpec.scala} | 5 +- .../nodeView/LocallyGeneratedInputBlock.scala | 6 +- ...ubBlockInfo.scala => InputBlockInfo.scala} | 36 +++++----- .../mining/CandidateGenerator.scala | 22 +++--- .../network/ErgoNodeViewSynchronizer.scala | 42 +++++------ .../ErgoNodeViewSynchronizerMessages.scala | 8 +-- .../nodeView/ErgoNodeViewHolder.scala | 20 +++--- .../nodeView/history/ErgoHistoryReader.scala | 4 +- .../InputBlocksProcessor.scala | 69 +++++++++++++++++++ .../SubBlocksProcessor.scala | 61 ---------------- 15 files changed, 161 insertions(+), 152 deletions(-) rename ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/{SubBlockMessageSpec.scala => InputBlockMessageSpec.scala} (52%) rename ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/{SubBlockTransactionsData.scala => InputBlockTransactionsData.scala} (65%) rename ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/{SubBlockTransactionsMessageSpec.scala => InputBlockTransactionsMessageSpec.scala} (64%) rename ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/{SubBlockTransactionsRequestMessageSpec.scala => InputBlockTransactionsRequestMessageSpec.scala} (81%) rename ergo-core/src/main/scala/org/ergoplatform/subblocks/{SubBlockInfo.scala => InputBlockInfo.scala} (60%) create mode 100644 src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala delete mode 100644 src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index e5109fea80..ef63904bef 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -5,7 +5,7 @@ import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.network.message.MessageConstants.MessageCode import org.ergoplatform.network.message.MessageSpecInitial import org.ergoplatform.settings.{Constants, Parameters} -import org.ergoplatform.subblocks.SubBlockInfo +import org.ergoplatform.subblocks.InputBlockInfo import scorex.util.Extensions._ import scorex.util.serialization.{Reader, Writer} import scorex.util.{ModifierId, bytesToId, idToBytes} @@ -164,9 +164,9 @@ object structures { * @param sbi * @return - sub-block ids to download, sub-block transactions to download */ - def processSubBlock(sbi: SubBlockInfo): (Seq[ModifierId], Seq[ModifierId]) = { - val sbHeader = sbi.subBlock - val prevSbIdOpt = sbi.prevSubBlockId.map(bytesToId) + def processSubBlock(sbi: InputBlockInfo): (Seq[ModifierId], Seq[ModifierId]) = { + val sbHeader = sbi.header + val prevSbIdOpt = sbi.prevInputBlockId.map(bytesToId) val sbHeight = sbHeader.height def emptyResult: (Seq[ModifierId], Seq[ModifierId]) = Seq.empty -> Seq.empty diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/MessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/MessageSpec.scala index 1f21e48951..08c8ecbc69 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/MessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/MessageSpec.scala @@ -41,7 +41,7 @@ trait MessageSpecInitial[Content] extends MessageSpec[Content] { /** * Sub-blocks related messages, V2 of the protocol */ -trait MessageSpecSubblocks[Content] extends MessageSpec[Content] { +trait MessageSpecInputBlocks[Content] extends MessageSpec[Content] { override val protocolVersion: Version = Version.SubblocksVersion diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockMessageSpec.scala similarity index 52% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockMessageSpec.scala index c611bc6085..68e6cb7ba0 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockMessageSpec.scala @@ -1,26 +1,26 @@ package org.ergoplatform.network.message.subblocks import org.ergoplatform.network.message.MessageConstants.MessageCode -import org.ergoplatform.network.message.MessageSpecSubblocks -import org.ergoplatform.subblocks.SubBlockInfo +import org.ergoplatform.network.message.MessageSpecInputBlocks +import org.ergoplatform.subblocks.InputBlockInfo import scorex.util.serialization.{Reader, Writer} /** * Message that is informing about sub block produced. * Contains header and link to previous sub block (). */ -object SubBlockMessageSpec extends MessageSpecSubblocks[SubBlockInfo] { +object InputBlockMessageSpec extends MessageSpecInputBlocks[InputBlockInfo] { val MaxMessageSize = 10000 override val messageCode: MessageCode = 90: Byte override val messageName: String = "SubBlock" - override def serialize(data: SubBlockInfo, w: Writer): Unit = { - SubBlockInfo.serializer.serialize(data, w) + override def serialize(data: InputBlockInfo, w: Writer): Unit = { + InputBlockInfo.serializer.serialize(data, w) } - override def parse(r: Reader): SubBlockInfo = { - SubBlockInfo.serializer.parse(r) + override def parse(r: Reader): InputBlockInfo = { + InputBlockInfo.serializer.parse(r) } } diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsData.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsData.scala similarity index 65% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsData.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsData.scala index 51b8cc204a..4ad2514c74 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsData.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsData.scala @@ -4,6 +4,6 @@ import org.ergoplatform.modifiers.mempool.ErgoTransaction import scorex.util.ModifierId // todo: send transactions or transactions id ? -case class SubBlockTransactionsData(subblockID: ModifierId, transactions: Seq[ErgoTransaction]){ +case class InputBlockTransactionsData(inputBlockID: ModifierId, transactions: Seq[ErgoTransaction]){ } diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsMessageSpec.scala similarity index 64% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsMessageSpec.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsMessageSpec.scala index 0abb2be62d..63d9022730 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsMessageSpec.scala @@ -2,12 +2,12 @@ package org.ergoplatform.network.message.subblocks import org.ergoplatform.modifiers.mempool.ErgoTransactionSerializer import org.ergoplatform.network.message.MessageConstants.MessageCode -import org.ergoplatform.network.message.MessageSpecSubblocks +import org.ergoplatform.network.message.MessageSpecInputBlocks import scorex.util.{bytesToId, idToBytes} import scorex.util.serialization.{Reader, Writer} import sigma.util.Extensions.LongOps -object SubBlockTransactionsMessageSpec extends MessageSpecSubblocks[SubBlockTransactionsData]{ +object InputBlockTransactionsMessageSpec extends MessageSpecInputBlocks[InputBlockTransactionsData]{ /** * Code which identifies what message type is contained in the payload */ @@ -15,23 +15,23 @@ object SubBlockTransactionsMessageSpec extends MessageSpecSubblocks[SubBlockTran /** * Name of this message type. For debug purposes only. */ - override val messageName: String = "SubBlockTxs" + override val messageName: String = "InputBlockTxs" - override def serialize(obj: SubBlockTransactionsData, w: Writer): Unit = { - w.putBytes(idToBytes(obj.subblockID)) + override def serialize(obj: InputBlockTransactionsData, w: Writer): Unit = { + w.putBytes(idToBytes(obj.inputBlockID)) w.putUInt(obj.transactions.size) obj.transactions.foreach { tx => ErgoTransactionSerializer.serialize(tx, w) } } - override def parse(r: Reader): SubBlockTransactionsData = { + override def parse(r: Reader): InputBlockTransactionsData = { val subBlockId = bytesToId(r.getBytes(32)) val txsCount = r.getUInt().toIntExact val transactions = (1 to txsCount).map{_ => ErgoTransactionSerializer.parse(r) } - SubBlockTransactionsData(subBlockId, transactions) + InputBlockTransactionsData(subBlockId, transactions) } } diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsRequestMessageSpec.scala similarity index 81% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestMessageSpec.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsRequestMessageSpec.scala index ff9c057fa5..e1f8f2df21 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsRequestMessageSpec.scala @@ -1,11 +1,11 @@ package org.ergoplatform.network.message.subblocks import org.ergoplatform.network.message.MessageConstants.MessageCode -import org.ergoplatform.network.message.MessageSpecSubblocks +import org.ergoplatform.network.message.MessageSpecInputBlocks import scorex.util.{ModifierId, bytesToId, idToBytes} import scorex.util.serialization.{Reader, Writer} -object SubBlockTransactionsRequestMessageSpec extends MessageSpecSubblocks[ModifierId] { +object InputBlockTransactionsRequestMessageSpec extends MessageSpecInputBlocks[ModifierId] { /** * Code which identifies what message type is contained in the payload */ @@ -23,4 +23,5 @@ object SubBlockTransactionsRequestMessageSpec extends MessageSpecSubblocks[Modif override def parse(r: Reader): ModifierId = { bytesToId(r.getBytes(32)) } + } diff --git a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala index 8a554e3f99..a19d6758b4 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala @@ -1,6 +1,6 @@ package org.ergoplatform.nodeView -import org.ergoplatform.network.message.subblocks.SubBlockTransactionsData -import org.ergoplatform.subblocks.SubBlockInfo +import org.ergoplatform.network.message.subblocks.InputBlockTransactionsData +import org.ergoplatform.subblocks.InputBlockInfo -case class LocallyGeneratedInputBlock(sbi: SubBlockInfo, sbt: SubBlockTransactionsData) +case class LocallyGeneratedInputBlock(sbi: InputBlockInfo, sbt: InputBlockTransactionsData) diff --git a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala b/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala similarity index 60% rename from ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala rename to ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala index c8e058dc77..727785ac3a 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala @@ -13,19 +13,19 @@ import scorex.util.serialization.{Reader, Writer} * Sub-block message, sent by the node to peers when a sub-block is generated * * @param version - message version E(to allow injecting new fields) - * @param subBlock - subblock - * @param prevSubBlockId - previous sub block id `subBlock` is following, if missed, sub-block is linked + * @param header - subblock + * @param prevInputBlockId - previous sub block id `subBlock` is following, if missed, sub-block is linked * to a previous block - * @param subblockTransactionsDigest - digest of new transactions appeared in subblock + * @param transactionsDigest - digest of new transactions appeared in subblock * @param merkleProof - batch Merkle proof for `prevSubBlockId`` and `subblockTransactionsDigest` * (as they are coming from extension section, and committed in `subBlock` header via extension * digest) */ -case class SubBlockInfo(version: Byte, - subBlock: Header, - prevSubBlockId: Option[Array[Byte]], - subblockTransactionsDigest: Digest32, - merkleProof: BatchMerkleProof[Digest32] // Merkle proof for both prevSubBlockId & subblockTransactionsDigest +case class InputBlockInfo(version: Byte, + header: Header, + prevInputBlockId: Option[Array[Byte]], + transactionsDigest: Digest32, + merkleProof: BatchMerkleProof[Digest32] // Merkle proof for both prevSubBlockId & subblockTransactionsDigest ) { def valid(): Boolean = { @@ -33,36 +33,36 @@ case class SubBlockInfo(version: Byte, false } - def transactionsConfirmedDigest: Digest32 = subBlock.transactionsRoot + def transactionsConfirmedDigest: Digest32 = header.transactionsRoot } -object SubBlockInfo { +object InputBlockInfo { val initialMessageVersion = 1.toByte private val bmp = new BatchMerkleProofSerializer[Digest32, CryptographicHash[Digest32]]()(Blake2b256) - def serializer: ErgoSerializer[SubBlockInfo] = new ErgoSerializer[SubBlockInfo] { - override def serialize(sbi: SubBlockInfo, w: Writer): Unit = { + def serializer: ErgoSerializer[InputBlockInfo] = new ErgoSerializer[InputBlockInfo] { + override def serialize(sbi: InputBlockInfo, w: Writer): Unit = { w.put(sbi.version) - HeaderSerializer.serialize(sbi.subBlock, w) - w.putOption(sbi.prevSubBlockId){case (w, id) => w.putBytes(id)} - w.putBytes(sbi.subblockTransactionsDigest) + HeaderSerializer.serialize(sbi.header, w) + w.putOption(sbi.prevInputBlockId){case (w, id) => w.putBytes(id)} + w.putBytes(sbi.transactionsDigest) val proof = bmp.serialize(sbi.merkleProof) w.putUShort(proof.length.toShort) w.putBytes(proof) } - override def parse(r: Reader): SubBlockInfo = { + override def parse(r: Reader): InputBlockInfo = { val version = r.getByte() if (version == initialMessageVersion) { val subBlock = HeaderSerializer.parse(r) val prevSubBlockId = r.getOption(r.getBytes(Constants.ModifierIdSize)) - val subblockTransactionsDigest = Digest32 @@ r.getBytes(Constants.ModifierIdSize) + val transactionsDigest = Digest32 @@ r.getBytes(Constants.ModifierIdSize) val merkleProofSize = r.getUShort().toShortExact val merkleProofBytes = r.getBytes(merkleProofSize) val merkleProof = bmp.deserialize(merkleProofBytes).get // parse Merkle proof - new SubBlockInfo(version, subBlock, prevSubBlockId, subblockTransactionsDigest, merkleProof) + new InputBlockInfo(version, subBlock, prevSubBlockId, transactionsDigest, merkleProof) } else { throw new Exception("Unsupported sub-block message version") } diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 5929023ce3..1d84c1e3ac 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -13,7 +13,7 @@ import org.ergoplatform.modifiers.history.header.{Header, HeaderWithoutPow} import org.ergoplatform.modifiers.history.popow.NipopowAlgos import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ -import org.ergoplatform.network.message.subblocks.SubBlockTransactionsData +import org.ergoplatform.network.message.subblocks.InputBlockTransactionsData import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.EliminateTransactions import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.{LocallyGeneratedInputBlock, LocallyGeneratedOrderingBlock} @@ -23,7 +23,7 @@ import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.nodeView.state.{ErgoState, ErgoStateContext, UtxoStateReader} import org.ergoplatform.settings.{ErgoSettings, ErgoValidationSettingsUpdate, Parameters} import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox -import org.ergoplatform.subblocks.SubBlockInfo +import org.ergoplatform.subblocks.InputBlockInfo import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, InputSolutionFound, OrderingSolutionFound, SolutionFound, SubBlockAlgos} import scorex.crypto.authds.merkle.BatchMerkleProof @@ -68,9 +68,9 @@ class CandidateGenerator( } /** Send solved input block to processing */ - private def sendInputToNodeView(sbi: SubBlockInfo, sbt: SubBlockTransactionsData): Unit = { + private def sendInputToNodeView(sbi: InputBlockInfo, sbt: InputBlockTransactionsData): Unit = { log.info( - s"New input block ${sbi.subBlock.id} w. nonce ${Longs.fromByteArray(sbi.subBlock.powSolution.n)}" + s"New input block ${sbi.header.id} w. nonce ${Longs.fromByteArray(sbi.header.powSolution.n)}" ) viewHolderRef ! LocallyGeneratedInputBlock(sbi, sbt) } @@ -211,7 +211,7 @@ class CandidateGenerator( sendInputToNodeView(sbi, sbt) StatusReply.error( - new Exception(s"Input block found! PoW valid: ${SubBlockAlgos.checkInputBlockPoW(sbi.subBlock)}") + new Exception(s"Input block found! PoW valid: ${SubBlockAlgos.checkInputBlockPoW(sbi.header)}") ) } } @@ -439,10 +439,10 @@ object CandidateGenerator extends ScorexLogging { val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) - val lastSubblockOpt: Option[SubBlockInfo] = history.bestSubblock() + val lastSubblockOpt: Option[InputBlockInfo] = history.bestSubblock() // there was sub-block generated before for this block - val continueSubblock = lastSubblockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.subBlock.parentId)) + val continueSubblock = lastSubblockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.header.parentId)) // Make progress in time since last block. // If no progress is made, then, by consensus rules, the block will be rejected. @@ -454,7 +454,7 @@ object CandidateGenerator extends ScorexLogging { // Calculate required difficulty for the new block, the same diff for subblock val nBits: Long = if(continueSubblock) { - lastSubblockOpt.get.subBlock.nBits // .get is ok as lastSubblockOpt.exists in continueSubblock checks emptiness + lastSubblockOpt.get.header.nBits // .get is ok as lastSubblockOpt.exists in continueSubblock checks emptiness } else { bestHeaderOpt .map(parent => history.requiredDifficultyAfter(parent)) @@ -921,7 +921,7 @@ object CandidateGenerator extends ScorexLogging { new ErgoFullBlock(header, blockTransactions, extension, Some(adProofs)) } - def completeInputBlock(candidate: CandidateBlock, solution: AutolykosSolution): (SubBlockInfo, SubBlockTransactionsData) = { + def completeInputBlock(candidate: CandidateBlock, solution: AutolykosSolution): (InputBlockInfo, InputBlockTransactionsData) = { // todo: check links? // todo: update candidate generator state @@ -934,8 +934,8 @@ object CandidateGenerator extends ScorexLogging { val header = deriveUnprovenHeader(candidate).toHeader(solution, None) val txs = candidate.transactions - val sbi: SubBlockInfo = SubBlockInfo(SubBlockInfo.initialMessageVersion, header, prevSubBlockId, subblockTransactionsDigest, merkleProof) - val sbt : SubBlockTransactionsData = SubBlockTransactionsData(sbi.subBlock.id, txs) + val sbi: InputBlockInfo = InputBlockInfo(InputBlockInfo.initialMessageVersion, header, prevSubBlockId, subblockTransactionsDigest, merkleProof) + val sbt : InputBlockTransactionsData = InputBlockTransactionsData(sbi.header.id, txs) (sbi, sbt) } diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index eb0da81dd8..d327e6b9a1 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -33,9 +33,9 @@ import org.ergoplatform.consensus.{Equal, Fork, Nonsense, Older, Unknown, Younge import org.ergoplatform.modifiers.history.{ADProofs, ADProofsSerializer, BlockTransactions, BlockTransactionsSerializer} import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionSerializer} import org.ergoplatform.modifiers.transaction.TooHighCostError -import org.ergoplatform.network.message.subblocks.{SubBlockMessageSpec, SubBlockTransactionsData, SubBlockTransactionsMessageSpec, SubBlockTransactionsRequestMessageSpec} +import org.ergoplatform.network.message.subblocks.{InputBlockMessageSpec, InputBlockTransactionsData, InputBlockTransactionsMessageSpec, InputBlockTransactionsRequestMessageSpec} import org.ergoplatform.serialization.{ErgoSerializer, ManifestSerializer, SubtreeSerializer} -import org.ergoplatform.subblocks.SubBlockInfo +import org.ergoplatform.subblocks.InputBlockInfo import scorex.crypto.authds.avltree.batch.VersionedLDBAVLStorage.splitDigest import scala.annotation.tailrec @@ -1076,17 +1076,17 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } } - def processSubblock(subBlockInfo: SubBlockInfo, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { - val subBlockHeader = subBlockInfo.subBlock + def processInputBlock(inputBlockInfo: InputBlockInfo, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { + val subBlockHeader = inputBlockInfo.header // apply sub-block if it is on current height if (subBlockHeader.height == hr.fullBlockHeight + 1) { - if (subBlockInfo.valid()) { // check PoW / Merkle proofs before processing - val prevSbIdOpt = subBlockInfo.prevSubBlockId.map(bytesToId) // link to previous sub-block + if (inputBlockInfo.valid()) { // check PoW / Merkle proofs before processing + val prevSbIdOpt = inputBlockInfo.prevInputBlockId.map(bytesToId) // link to previous sub-block log.debug(s"Processing valid sub-block ${subBlockHeader.id} with parent sub-block $prevSbIdOpt and parent block ${subBlockHeader.parentId}") // write sub-block to db, ask for transactions in it - viewHolderRef ! ProcessSubblock(subBlockInfo) + viewHolderRef ! ProcessInputBlock(inputBlockInfo) // todo: ask for txs only if subblock's parent is a best subblock ? - val msg = Message(SubBlockTransactionsRequestMessageSpec, Right(subBlockInfo.subBlock.id), None) + val msg = Message(InputBlockTransactionsRequestMessageSpec, Right(inputBlockInfo.header.id), None) networkControllerRef ! SendToNetwork(msg, SendToPeer(remote)) } else { log.warn(s"Sub-block ${subBlockHeader.id} is invalid") @@ -1098,22 +1098,22 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } } - def processSubblockTransactionsRequest(subBlockId: ModifierId, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { + def processInputBlockTransactionsRequest(subBlockId: ModifierId, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { hr.getSubBlockTransactions(subBlockId) match { case Some(transactions) => - val std = SubBlockTransactionsData(subBlockId, transactions) - val msg = Message(SubBlockTransactionsMessageSpec, Right(std), None) + val std = InputBlockTransactionsData(subBlockId, transactions) + val msg = Message(InputBlockTransactionsMessageSpec, Right(std), None) networkControllerRef ! SendToNetwork(msg, SendToPeer(remote)) case None => log.warn(s"Transactions not found for requested sub block ${subBlockId}") } } - def processSubblockTransactions(transactionsData: SubBlockTransactionsData, - hr: ErgoHistoryReader, - remote: ConnectedPeer): Unit = { + def processInputBlockTransactions(transactionsData: InputBlockTransactionsData, + hr: ErgoHistoryReader, + remote: ConnectedPeer): Unit = { // todo: check if not spam, ie transaction were requested - viewHolderRef ! ProcessSubblockTransactions(transactionsData) + viewHolderRef ! ProcessInputBlockTransactions(transactionsData) } /** @@ -1565,12 +1565,12 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, case (_: NipopowProofSpec.type , proofBytes: Array[Byte], remote) => processNipopowProof(proofBytes, hr, remote) // Sub-blocks related messages - case (_: SubBlockMessageSpec.type, subBlockInfo: SubBlockInfo, remote) => - processSubblock(subBlockInfo, hr, remote) - case (_: SubBlockTransactionsRequestMessageSpec.type, subBlockId: String, remote) => - processSubblockTransactionsRequest(ModifierId @@ subBlockId, hr, remote) - case (_: SubBlockTransactionsMessageSpec.type, transactions: SubBlockTransactionsData, remote) => - processSubblockTransactions(transactions, hr, remote) + case (_: InputBlockMessageSpec.type, subBlockInfo: InputBlockInfo, remote) => + processInputBlock(subBlockInfo, hr, remote) + case (_: InputBlockTransactionsRequestMessageSpec.type, subBlockId: String, remote) => + processInputBlockTransactionsRequest(ModifierId @@ subBlockId, hr, remote) + case (_: InputBlockTransactionsMessageSpec.type, transactions: InputBlockTransactionsData, remote) => + processInputBlockTransactions(transactions, hr, remote) } def initialized(hr: ErgoHistory, diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala index 79ba571569..2ae70d7b0b 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala @@ -11,8 +11,8 @@ import scorex.core.network.ConnectedPeer import scorex.util.ModifierId import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform.modifiers.history.popow.NipopowProof -import org.ergoplatform.network.message.subblocks.SubBlockTransactionsData -import org.ergoplatform.subblocks.SubBlockInfo +import org.ergoplatform.network.message.subblocks.InputBlockTransactionsData +import org.ergoplatform.subblocks.InputBlockInfo /** * Repository of messages processed ErgoNodeViewSynchronizer actor @@ -145,7 +145,7 @@ object ErgoNodeViewSynchronizerMessages { */ case class ProcessNipopow(nipopowProof: NipopowProof) - case class ProcessSubblock(subblock: SubBlockInfo) + case class ProcessInputBlock(subblock: InputBlockInfo) - case class ProcessSubblockTransactions(std: SubBlockTransactionsData) + case class ProcessInputBlockTransactions(std: InputBlockTransactionsData) } diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 8ffca3bcae..b8c1789fe3 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -25,7 +25,7 @@ import spire.syntax.all.cfor import java.io.File import org.ergoplatform.modifiers.history.extension.Extension -import org.ergoplatform.subblocks.SubBlockInfo +import org.ergoplatform.subblocks.InputBlockInfo import scala.annotation.tailrec import scala.collection.mutable @@ -304,11 +304,11 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti } // subblocks related logic - case ProcessSubblock(sbi) => - history().applySubBlockHeader(sbi) + case ProcessInputBlock(sbi) => + history().applyInputBlock(sbi) - case ProcessSubblockTransactions(std) => - history().applySubBlockTransactions(std.subblockID, std.transactions) + case ProcessInputBlockTransactions(std) => + history().applySubBlockTransactions(std.inputBlockID, std.transactions) } /** @@ -685,9 +685,9 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti pmodModify(section, local = true) } case LocallyGeneratedInputBlock(subblockInfo, subBlockTransactionsData) => - log.info(s"Got locally generated input block ${subblockInfo.subBlock.id}") - history().applySubBlockHeader(subblockInfo) - history().applySubBlockTransactions(subblockInfo.subBlock.id, subBlockTransactionsData.transactions) + log.info(s"Got locally generated input block ${subblockInfo.header.id}") + history().applyInputBlock(subblockInfo) + history().applySubBlockTransactions(subblockInfo.header.id, subBlockTransactionsData.transactions) // todo: finish processing } @@ -742,9 +742,9 @@ object ErgoNodeViewHolder { case class ModifiersFromRemote(modifiers: Iterable[BlockSection]) /** - * Wrapper for a locally generated sub-block submitted via API + * Wrapper for a locally generated input-block submitted via API */ - case class LocallyGeneratedSubBlock(sbi: SubBlockInfo) + case class LocallyGeneratedInputBlock(sbi: InputBlockInfo) /** * Wrapper for a transaction submitted via API diff --git a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala index dd2d39f7f9..41631b2880 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala @@ -9,7 +9,7 @@ import org.ergoplatform.modifiers.{BlockSection, ErgoFullBlock, NetworkObjectTyp import org.ergoplatform.nodeView.history.ErgoHistoryUtils.{EmptyHistoryHeight, GenesisHeight, Height} import org.ergoplatform.nodeView.history.extra.ExtraIndex import org.ergoplatform.nodeView.history.storage._ -import org.ergoplatform.nodeView.history.storage.modifierprocessors.{BlockSectionProcessor, HeadersProcessor, SubBlocksProcessor} +import org.ergoplatform.nodeView.history.storage.modifierprocessors.{BlockSectionProcessor, HeadersProcessor, InputBlocksProcessor} import org.ergoplatform.settings.{ErgoSettings, NipopowSettings} import org.ergoplatform.validation.MalformedModifierError import scorex.util.{ModifierId, ScorexLogging} @@ -26,7 +26,7 @@ trait ErgoHistoryReader with ContainsModifiers[BlockSection] with HeadersProcessor with BlockSectionProcessor - with SubBlocksProcessor + with InputBlocksProcessor with ScorexLogging { type ModifierIds = Seq[(NetworkObjectTypeId.Value, ModifierId)] diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala new file mode 100644 index 0000000000..dfbccb3f4c --- /dev/null +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -0,0 +1,69 @@ +package org.ergoplatform.nodeView.history.storage.modifierprocessors + +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.subblocks.InputBlockInfo +import scorex.util.{ModifierId, ScorexLogging, bytesToId} + +import scala.collection.mutable + +/** + * Storing and processing input-blocks related data + * Desiderata: + * * store input blocks for short time only + */ +trait InputBlocksProcessor extends ScorexLogging { + + /** + * Pointer to a best input-block known + */ + var _bestInputBlock: Option[InputBlockInfo] = None + + // input block id -> input block index + val inputBlockRecords = mutable.Map[ModifierId, InputBlockInfo]() + + // input block id -> input block transactions index + val inputBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() + + // reset sub-blocks structures, should be called on receiving ordering block (or slightly later?) + def resetState() = { + _bestInputBlock = None + + // todo: subBlockRecords & subBlockTransactions should be cleared a bit later, as other peers may still ask for them + inputBlockRecords.clear() + inputBlockTransactions.clear() + } + + // sub-blocks related logic + def applyInputBlock(sbi: InputBlockInfo): Unit = { + // new ordering block arrived ( should be processed outside ? ) + if (sbi.header.height > _bestInputBlock.map(_.header.height).getOrElse(-1)) { + resetState() + } + + inputBlockRecords.put(sbi.header.id, sbi) + + // todo: currently only one chain of subblocks considered, + // todo: in fact there could be multiple trees here (one subblocks tree per header) + _bestInputBlock match { + case None => _bestInputBlock = Some(sbi) + case Some(maybeParent) if (sbi.prevInputBlockId.map(bytesToId).contains(maybeParent.header.id)) => + _bestInputBlock = Some(sbi) + case _ => + // todo: record it + log.debug(s"Applying non-best inpu block #: ${sbi.header.id}") + } + } + + def applySubBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { + inputBlockTransactions.put(sbId, transactions) + } + + def getSubBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { + inputBlockTransactions.get(sbId) + } + + def bestSubblock(): Option[InputBlockInfo] = { + _bestInputBlock + } + +} diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala deleted file mode 100644 index 9baec15136..0000000000 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala +++ /dev/null @@ -1,61 +0,0 @@ -package org.ergoplatform.nodeView.history.storage.modifierprocessors - -import org.ergoplatform.modifiers.mempool.ErgoTransaction -import org.ergoplatform.subblocks.SubBlockInfo -import scorex.util.{ModifierId, ScorexLogging, bytesToId} - -import scala.collection.mutable - -trait SubBlocksProcessor extends ScorexLogging { - - /** - * Pointer to a best input-block known - */ - var _bestSubblock: Option[SubBlockInfo] = None - - val subBlockRecords = mutable.Map[ModifierId, SubBlockInfo]() - val subBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() - - // reset sub-blocks structures, should be called on receiving ordering block (or slightly later?) - def resetState() = { - _bestSubblock = None - - // todo: subBlockRecords & subBlockTransactions should be cleared a bit later, as other peers may still ask for them - subBlockRecords.clear() - subBlockTransactions.clear() - } - - // sub-blocks related logic - def applySubBlockHeader(sbi: SubBlockInfo): Unit = { - // new ordering block arrived ( should be processed outside ? ) - if (sbi.subBlock.height > _bestSubblock.map(_.subBlock.height).getOrElse(-1)) { - resetState() - } - - subBlockRecords.put(sbi.subBlock.id, sbi) - - // todo: currently only one chain of subblocks considered, - // todo: in fact there could be multiple trees here (one subblocks tree per header) - _bestSubblock match { - case None => _bestSubblock = Some(sbi) - case Some(maybeParent) if (sbi.prevSubBlockId.map(bytesToId).contains(maybeParent.subBlock.id)) => - _bestSubblock = Some(sbi) - case _ => - // todo: record it - log.debug(s"Applying non-best subblock id: ${sbi.subBlock.id}") - } - } - - def applySubBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { - subBlockTransactions.put(sbId, transactions) - } - - def getSubBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { - subBlockTransactions.get(sbId) - } - - def bestSubblock(): Option[SubBlockInfo] = { - _bestSubblock - } - -} From 87a91d598659dc6992ba705ab5dd3260eeb15d14 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 24 Dec 2024 14:07:31 +0300 Subject: [PATCH 056/109] input blocks data pruning --- .../InputBlockMessageSpec.scala | 2 +- .../InputBlockTransactionsData.scala | 2 +- .../InputBlockTransactionsMessageSpec.scala | 2 +- ...tBlockTransactionsRequestMessageSpec.scala | 2 +- .../nodeView/LocallyGeneratedInputBlock.scala | 2 +- .../mining/CandidateGenerator.scala | 4 +- .../network/ErgoNodeViewSynchronizer.scala | 4 +- .../ErgoNodeViewSynchronizerMessages.scala | 2 +- .../nodeView/ErgoNodeViewHolder.scala | 4 +- .../InputBlocksProcessor.scala | 52 +++++++++++++------ 10 files changed, 49 insertions(+), 27 deletions(-) rename ergo-core/src/main/scala/org/ergoplatform/network/message/{subblocks => inputblocks}/InputBlockMessageSpec.scala (93%) rename ergo-core/src/main/scala/org/ergoplatform/network/message/{subblocks => inputblocks}/InputBlockTransactionsData.scala (82%) rename ergo-core/src/main/scala/org/ergoplatform/network/message/{subblocks => inputblocks}/InputBlockTransactionsMessageSpec.scala (95%) rename ergo-core/src/main/scala/org/ergoplatform/network/message/{subblocks => inputblocks}/InputBlockTransactionsRequestMessageSpec.scala (93%) diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockMessageSpec.scala similarity index 93% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockMessageSpec.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockMessageSpec.scala index 68e6cb7ba0..1cb5c2d75d 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockMessageSpec.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.network.message.subblocks +package org.ergoplatform.network.message.inputblocks import org.ergoplatform.network.message.MessageConstants.MessageCode import org.ergoplatform.network.message.MessageSpecInputBlocks diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsData.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsData.scala similarity index 82% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsData.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsData.scala index 4ad2514c74..20548e6b35 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsData.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsData.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.network.message.subblocks +package org.ergoplatform.network.message.inputblocks import org.ergoplatform.modifiers.mempool.ErgoTransaction import scorex.util.ModifierId diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsMessageSpec.scala similarity index 95% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsMessageSpec.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsMessageSpec.scala index 63d9022730..f6e555cd45 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsMessageSpec.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.network.message.subblocks +package org.ergoplatform.network.message.inputblocks import org.ergoplatform.modifiers.mempool.ErgoTransactionSerializer import org.ergoplatform.network.message.MessageConstants.MessageCode diff --git a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsRequestMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsRequestMessageSpec.scala similarity index 93% rename from ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsRequestMessageSpec.scala rename to ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsRequestMessageSpec.scala index e1f8f2df21..718131c769 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/InputBlockTransactionsRequestMessageSpec.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/inputblocks/InputBlockTransactionsRequestMessageSpec.scala @@ -1,4 +1,4 @@ -package org.ergoplatform.network.message.subblocks +package org.ergoplatform.network.message.inputblocks import org.ergoplatform.network.message.MessageConstants.MessageCode import org.ergoplatform.network.message.MessageSpecInputBlocks diff --git a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala index a19d6758b4..ccdb18eaf1 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala @@ -1,6 +1,6 @@ package org.ergoplatform.nodeView -import org.ergoplatform.network.message.subblocks.InputBlockTransactionsData +import org.ergoplatform.network.message.inputblocks.InputBlockTransactionsData import org.ergoplatform.subblocks.InputBlockInfo case class LocallyGeneratedInputBlock(sbi: InputBlockInfo, sbt: InputBlockTransactionsData) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 1d84c1e3ac..1b82fa0837 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -13,7 +13,7 @@ import org.ergoplatform.modifiers.history.header.{Header, HeaderWithoutPow} import org.ergoplatform.modifiers.history.popow.NipopowAlgos import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ -import org.ergoplatform.network.message.subblocks.InputBlockTransactionsData +import org.ergoplatform.network.message.inputblocks.InputBlockTransactionsData import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.EliminateTransactions import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.{LocallyGeneratedInputBlock, LocallyGeneratedOrderingBlock} @@ -439,7 +439,7 @@ object CandidateGenerator extends ScorexLogging { val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) - val lastSubblockOpt: Option[InputBlockInfo] = history.bestSubblock() + val lastSubblockOpt: Option[InputBlockInfo] = history.bestInputBlock() // there was sub-block generated before for this block val continueSubblock = lastSubblockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.header.parentId)) diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index d327e6b9a1..842a1a2ecc 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -33,7 +33,7 @@ import org.ergoplatform.consensus.{Equal, Fork, Nonsense, Older, Unknown, Younge import org.ergoplatform.modifiers.history.{ADProofs, ADProofsSerializer, BlockTransactions, BlockTransactionsSerializer} import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionSerializer} import org.ergoplatform.modifiers.transaction.TooHighCostError -import org.ergoplatform.network.message.subblocks.{InputBlockMessageSpec, InputBlockTransactionsData, InputBlockTransactionsMessageSpec, InputBlockTransactionsRequestMessageSpec} +import org.ergoplatform.network.message.inputblocks.{InputBlockMessageSpec, InputBlockTransactionsData, InputBlockTransactionsMessageSpec, InputBlockTransactionsRequestMessageSpec} import org.ergoplatform.serialization.{ErgoSerializer, ManifestSerializer, SubtreeSerializer} import org.ergoplatform.subblocks.InputBlockInfo import scorex.crypto.authds.avltree.batch.VersionedLDBAVLStorage.splitDigest @@ -1099,7 +1099,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } def processInputBlockTransactionsRequest(subBlockId: ModifierId, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { - hr.getSubBlockTransactions(subBlockId) match { + hr.getInputBlockTransactions(subBlockId) match { case Some(transactions) => val std = InputBlockTransactionsData(subBlockId, transactions) val msg = Message(InputBlockTransactionsMessageSpec, Right(std), None) diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala index 2ae70d7b0b..da5d301080 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala @@ -11,7 +11,7 @@ import scorex.core.network.ConnectedPeer import scorex.util.ModifierId import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform.modifiers.history.popow.NipopowProof -import org.ergoplatform.network.message.subblocks.InputBlockTransactionsData +import org.ergoplatform.network.message.inputblocks.InputBlockTransactionsData import org.ergoplatform.subblocks.InputBlockInfo /** diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index b8c1789fe3..f77cdabfc3 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -308,7 +308,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti history().applyInputBlock(sbi) case ProcessInputBlockTransactions(std) => - history().applySubBlockTransactions(std.inputBlockID, std.transactions) + history().applyInputBlockTransactions(std.inputBlockID, std.transactions) } /** @@ -687,7 +687,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti case LocallyGeneratedInputBlock(subblockInfo, subBlockTransactionsData) => log.info(s"Got locally generated input block ${subblockInfo.header.id}") history().applyInputBlock(subblockInfo) - history().applySubBlockTransactions(subblockInfo.header.id, subBlockTransactionsData.transactions) + history().applyInputBlockTransactions(subblockInfo.header.id, subBlockTransactionsData.transactions) // todo: finish processing } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index dfbccb3f4c..3816fd884f 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -1,5 +1,6 @@ package org.ergoplatform.nodeView.history.storage.modifierprocessors +import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.subblocks.InputBlockInfo import scorex.util.{ModifierId, ScorexLogging, bytesToId} @@ -24,45 +25,66 @@ trait InputBlocksProcessor extends ScorexLogging { // input block id -> input block transactions index val inputBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() + private def bestInputBlockHeight: Option[Height] = _bestInputBlock.map(_.header.height) + + private def prune() = { + val BlocksThreshold = 2 // we remove input-blocks data after 2 ordering blocks + + val bestHeight = bestInputBlockHeight.getOrElse(0) + val idsToRemove = inputBlockRecords.flatMap{case (id, ibi) => + val res = (bestHeight - ibi.header.height) > BlocksThreshold + if(res){ + Some(id) + } else { + None + } + } + idsToRemove.foreach{ id => + inputBlockRecords.remove(id) + inputBlockTransactions.remove(id) + } + } + // reset sub-blocks structures, should be called on receiving ordering block (or slightly later?) - def resetState() = { + private def resetState() = { _bestInputBlock = None - - // todo: subBlockRecords & subBlockTransactions should be cleared a bit later, as other peers may still ask for them - inputBlockRecords.clear() - inputBlockTransactions.clear() + prune() } // sub-blocks related logic - def applyInputBlock(sbi: InputBlockInfo): Unit = { + def applyInputBlock(ib: InputBlockInfo): Unit = { // new ordering block arrived ( should be processed outside ? ) - if (sbi.header.height > _bestInputBlock.map(_.header.height).getOrElse(-1)) { + if (ib.header.height > _bestInputBlock.map(_.header.height).getOrElse(-1)) { resetState() } - inputBlockRecords.put(sbi.header.id, sbi) + inputBlockRecords.put(ib.header.id, ib) // todo: currently only one chain of subblocks considered, // todo: in fact there could be multiple trees here (one subblocks tree per header) + // todo: split best input header / block _bestInputBlock match { - case None => _bestInputBlock = Some(sbi) - case Some(maybeParent) if (sbi.prevInputBlockId.map(bytesToId).contains(maybeParent.header.id)) => - _bestInputBlock = Some(sbi) + case None => + log.debug(s"Applying best input block #: ${ib.header.id}, no parent") + _bestInputBlock = Some(ib) + case Some(maybeParent) if (ib.prevInputBlockId.map(bytesToId).contains(maybeParent.header.id)) => + log.debug(s"Applying best input block #: ${ib.header.id}, parent is $maybeParent") + _bestInputBlock = Some(ib) case _ => // todo: record it - log.debug(s"Applying non-best inpu block #: ${sbi.header.id}") + log.debug(s"Applying non-best input block #: ${ib.header.id}") } } - def applySubBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { + def applyInputBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { inputBlockTransactions.put(sbId, transactions) } - def getSubBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { + def getInputBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { inputBlockTransactions.get(sbId) } - def bestSubblock(): Option[InputBlockInfo] = { + def bestInputBlock(): Option[InputBlockInfo] = { _bestInputBlock } From 8e9d526def8857395570b1e3b9954e6e8ca7090b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 24 Dec 2024 15:09:17 +0300 Subject: [PATCH 057/109] forming prevInputBlockId --- .../ergoplatform/mining/CandidateGenerator.scala | 12 ++++++++---- .../modifierprocessors/InputBlocksProcessor.scala | 14 +++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 1b82fa0837..85edd15cc4 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -18,6 +18,7 @@ import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.Eliminate import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.{LocallyGeneratedInputBlock, LocallyGeneratedOrderingBlock} import org.ergoplatform.nodeView.history.ErgoHistoryUtils.Height +import org.ergoplatform.nodeView.history.storage.modifierprocessors.InputBlocksProcessor import org.ergoplatform.nodeView.history.{ErgoHistoryReader, ErgoHistoryUtils} import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.nodeView.state.{ErgoState, ErgoStateContext, UtxoStateReader} @@ -29,7 +30,7 @@ import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, Input import scorex.crypto.authds.merkle.BatchMerkleProof import scorex.crypto.hash.Digest32 import scorex.util.encode.Base16 -import scorex.util.{ModifierId, ScorexLogging} +import scorex.util.{ModifierId, ScorexLogging, idToBytes} import sigma.data.{Digest32Coll, ProveDlog} import sigma.crypto.CryptoFacade import sigma.eval.Extensions.EvalIterableOps @@ -207,7 +208,8 @@ class CandidateGenerator( } case _: InputSolutionFound => log.info("Input-block mined!") - val (sbi, sbt) = completeInputBlock(state.cache.get.candidateBlock, solution) + + val (sbi, sbt) = completeInputBlock(state.hr, state.cache.get.candidateBlock, solution) sendInputToNodeView(sbi, sbt) StatusReply.error( @@ -921,13 +923,15 @@ object CandidateGenerator extends ScorexLogging { new ErgoFullBlock(header, blockTransactions, extension, Some(adProofs)) } - def completeInputBlock(candidate: CandidateBlock, solution: AutolykosSolution): (InputBlockInfo, InputBlockTransactionsData) = { + def completeInputBlock(inputBlockProcessor: InputBlocksProcessor, + candidate: CandidateBlock, + solution: AutolykosSolution): (InputBlockInfo, InputBlockTransactionsData) = { // todo: check links? // todo: update candidate generator state // todo: form and send real data instead of null - val prevSubBlockId: Option[Array[Byte]] = null + val prevSubBlockId: Option[Array[Byte]] = inputBlockProcessor.bestInputBlock().map(_.header.id).map(idToBytes) val subblockTransactionsDigest: Digest32 = null val merkleProof: BatchMerkleProof[Digest32] = null diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 3816fd884f..f8226f2fbe 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -2,6 +2,7 @@ package org.ergoplatform.nodeView.history.storage.modifierprocessors import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.history.ErgoHistoryReader import org.ergoplatform.subblocks.InputBlockInfo import scorex.util.{ModifierId, ScorexLogging, bytesToId} @@ -14,6 +15,11 @@ import scala.collection.mutable */ trait InputBlocksProcessor extends ScorexLogging { + /** + * @return interface to read objects from history database + */ + def historyReader: ErgoHistoryReader + /** * Pointer to a best input-block known */ @@ -85,7 +91,13 @@ trait InputBlocksProcessor extends ScorexLogging { } def bestInputBlock(): Option[InputBlockInfo] = { - _bestInputBlock + _bestInputBlock.flatMap{bib => + if(bib.header.height == historyReader.headersHeight) { // check header id? + Some(bib) + } else { + None + } + } } } From 139d770c1b46423e083e23c01542c80b46fe9572 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 24 Dec 2024 16:11:34 +0300 Subject: [PATCH 058/109] improving linking logic and code --- .../ergoplatform/mining/CandidateGenerator.scala | 6 +++--- .../nodeView/ErgoNodeViewHolder.scala | 2 +- .../modifierprocessors/InputBlocksProcessor.scala | 15 +++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 85edd15cc4..b2cadbfb4d 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -931,14 +931,14 @@ object CandidateGenerator extends ScorexLogging { // todo: update candidate generator state // todo: form and send real data instead of null - val prevSubBlockId: Option[Array[Byte]] = inputBlockProcessor.bestInputBlock().map(_.header.id).map(idToBytes) - val subblockTransactionsDigest: Digest32 = null + val prevInputBlockId: Option[Array[Byte]] = inputBlockProcessor.bestInputBlock().map(_.header.id).map(idToBytes) + val inputBlockTransactionsDigest: Digest32 = null val merkleProof: BatchMerkleProof[Digest32] = null val header = deriveUnprovenHeader(candidate).toHeader(solution, None) val txs = candidate.transactions - val sbi: InputBlockInfo = InputBlockInfo(InputBlockInfo.initialMessageVersion, header, prevSubBlockId, subblockTransactionsDigest, merkleProof) + val sbi: InputBlockInfo = InputBlockInfo(InputBlockInfo.initialMessageVersion, header, prevInputBlockId, inputBlockTransactionsDigest, merkleProof) val sbt : InputBlockTransactionsData = InputBlockTransactionsData(sbi.header.id, txs) (sbi, sbt) diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index f77cdabfc3..56925abfc6 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -303,7 +303,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti } } - // subblocks related logic + // input blocks related logic case ProcessInputBlock(sbi) => history().applyInputBlock(sbi) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index f8226f2fbe..97d48d077b 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -66,19 +66,21 @@ trait InputBlocksProcessor extends ScorexLogging { inputBlockRecords.put(ib.header.id, ib) + val ibParent = ib.prevInputBlockId + // todo: currently only one chain of subblocks considered, // todo: in fact there could be multiple trees here (one subblocks tree per header) // todo: split best input header / block _bestInputBlock match { case None => - log.debug(s"Applying best input block #: ${ib.header.id}, no parent") + log.info(s"Applying best input block #: ${ib.header.id}, no parent") _bestInputBlock = Some(ib) - case Some(maybeParent) if (ib.prevInputBlockId.map(bytesToId).contains(maybeParent.header.id)) => - log.debug(s"Applying best input block #: ${ib.header.id}, parent is $maybeParent") + case Some(maybeParent) if (ibParent.map(bytesToId).contains(maybeParent.header.id)) => + log.info(s"Applying best input block #: ${ib.header.id}, parent is ${maybeParent.header.id}") _bestInputBlock = Some(ib) case _ => - // todo: record it - log.debug(s"Applying non-best input block #: ${ib.header.id}") + // todo: switch from one input block chain to another + log.info(s"Applying non-best input block #: ${ib.header.id}, parent #: ${ibParent}") } } @@ -92,7 +94,8 @@ trait InputBlocksProcessor extends ScorexLogging { def bestInputBlock(): Option[InputBlockInfo] = { _bestInputBlock.flatMap{bib => - if(bib.header.height == historyReader.headersHeight) { // check header id? + // todo: check header id? best input block can be child of non-best ordering header + if(bib.header.height == historyReader.headersHeight + 1) { Some(bib) } else { None From afb4ef416cae29b60cc32229bf88727260ab6314 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 28 Dec 2024 23:43:08 +0300 Subject: [PATCH 059/109] refs, motivation rework --- papers/subblocks/subblocks.md | 23 ++++++++++++++----- .../InputBlocksProcessor.scala | 7 +++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index 7f12ce65cf..4b6f32741b 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -1,5 +1,5 @@ -Sub-Blocks and Improved Confirmed Transactions Propagation -========================== +Input-Blocks for Faster Transactions Propagation and Confirmation +================================================================= * Author: kushti * Status: Proposed @@ -14,16 +14,18 @@ Currently, a block is generated every two minutes on average, and confirmed tran other block sections. This is not efficient at all. Most of new block's transactions are already available in a node's mempool, and -bottlenecking network bandwidth after two minutes of (more or less) idle state is also downgrading network performance. +bottlenecking network bandwidth after two minutes of (more or less) idle state is downgrading network performance (for +more, see motivation in [1]). Also, while average block delay in Ergo is 2 minutes, variance is high, and often a user may wait 10 minutes for first confirmation. Proposals to lower variance are introducing experimental and controversial changes in consensus protocol. Changing block delay via hardfork would have a lot of harsh consequences (e.g. many contracts relying on current block -delay would be broken). Thus it makes sense to consider weaker notions of confirmation which still could be useful for +delay would be broken), and security of consensus after reducing block delay under bounded processing capacity could be +compromised [2]. Thus it makes sense to consider weaker notions of confirmation which still could be useful for a variety of applications. -Sub-Blocks ----------- +Input-Blocks +------------ A valid block is sequence of (semantically valid) header fields (and corresponding valid block sections, such as block transactions), including special field to iterate over, called nonce, such as *H(b) < T*, where *H()* is Autolykos Proof-of-Work @@ -121,3 +123,12 @@ with weaker security guarantees. Security Considerations and Assumptions --------------------------------------- + + +References +---------- + +1. Eyal, Ittay, et al. "{Bitcoin-NG}: A scalable blockchain protocol." 13th USENIX symposium on networked systems design and implementation (NSDI 16). 2016. + https://www.usenix.org/system/files/conference/nsdi16/nsdi16-paper-eyal.pdf +2. Kiffer, Lucianna, et al. "Nakamoto Consensus under Bounded Processing Capacity." Proceedings of the 2024 on ACM SIGSAC Conference on Computer and Communications Security. 2024. + https://iacr.steepath.eu/2023/381-NakamotoConsensusunderBoundedProcessingCapacity.pdf diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 97d48d077b..d2a84efb3c 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -37,15 +37,16 @@ trait InputBlocksProcessor extends ScorexLogging { val BlocksThreshold = 2 // we remove input-blocks data after 2 ordering blocks val bestHeight = bestInputBlockHeight.getOrElse(0) - val idsToRemove = inputBlockRecords.flatMap{case (id, ibi) => + val idsToRemove = inputBlockRecords.flatMap { case (id, ibi) => val res = (bestHeight - ibi.header.height) > BlocksThreshold - if(res){ + if (res) { Some(id) } else { None } } - idsToRemove.foreach{ id => + idsToRemove.foreach { id => + log.info(s"Pruning input block # $id") // todo: .debug inputBlockRecords.remove(id) inputBlockTransactions.remove(id) } From 8ed3749f4ae1ac0f6a5f51527b793a0ea30912d3 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 30 Dec 2024 17:59:41 +0300 Subject: [PATCH 060/109] EIP rework #1 --- papers/subblocks/subblocks.md | 49 +++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index 4b6f32741b..3502a5f870 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -24,13 +24,17 @@ delay would be broken), and security of consensus after reducing block delay und compromised [2]. Thus it makes sense to consider weaker notions of confirmation which still could be useful for a variety of applications. -Input-Blocks ------------- +Input Blocks and Ordering Blocks +-------------------------------- -A valid block is sequence of (semantically valid) header fields (and corresponding valid block sections, such as block +Following ideas in PRISM [3], parallel Proof-of-Work [4], and Tailstorm [5], we introduce two kinds of blocks in the Ergo + via non-breaking consensus protocol update. + +For starters, lets revisit blocks in current Ergo protocol, which is classic Proof-of-Work protocol formalized in [6]. +A valid block is a set of (semantically valid) header fields (and corresponding valid block sections, such as block transactions), including special field to iterate over, called nonce, such as *H(b) < T*, where *H()* is Autolykos Proof-of-Work -function, *b* are block bytes (including nonce), and *T* is a Proof-of-Work *target* value. A value which is reverse -to target is called difficulty *D*: *D = 2^256 / T* (in fact, slightly less value than 2^256 is taken, namely, order of +function, *b* are block header bytes (including nonce), and *T* is a Proof-of-Work *target* value. A value which is reverse +to target is called difficulty *D*: *D = 2^256 / T* (in fact, slightly less value than 2^256 is being used, namely, order of secp256k1 curve group, this is inherited from initial Autolykos 1 Proof-of-Work algorithm). *D* (and so *T*) is being readjusted regularly via a deterministic procedure (called difficulty readjustment algorithm) to have blocks coming every two minutes on average. @@ -39,13 +43,28 @@ a block which is more difficult to find than an ordinary, for example, for a (le *H(S) < T/2*, and in general, we can call n-level superblock a block *S* for which *H(S) < T/2^n*. Please note that a superblock is also a valid block (every superblock is passing block PoW test). -Similarly, we can go in opposite direction and use *subblocks*, so blocks with lower difficulty. We can set *t = T/64* -and define superblock *s* as *H(s) < t*, then miner can generate on average 64 subblocks (including normal block itself) -per block generation period. Please note that, unlike superblocks, subblocks are not blocks, but a block is passing -subblock check. +We propose to name full blocks in Ergo as *ordering blocks* from now, and use input-blocks (or sub-blocks) to carry most +of transactions. For starters, we set *t = T/64* (the divisor will be revisited later) and define input-block *ib* generation +condition as *H(ib) < t*, then a miner can generate on average 63 input blocks plus an ordering block +per orderring block generation period. Please note that, unlike superblocks, input blocks are not passing ordering-block PoW check, +but an ordering block is passing input block check. + +Thus we have now blockchain be like: + +(ordering) block - input block - input block - input block - (ordering) block - input block - input block - (ordering) block + +Next, we define how transactions are spread among input-blocks, and what additional data structures are needed. + +Transactions Handling +--------------------- + +Transactions are broken into two classes, for first one result of transaction validation can't change from one input +block to other , for the second, validation result can vary (this is true for transactions relying on block timestamp, +miner pubkey and other fields from block header, a clear example here is ERG emission contract). + +Transactions of the first class (about 99% of all transactions normally) can be included in input blocks only. +Transactions of the second class can be included in both kinds of blocks. -Subblocks are similar to block shares already used in pooled mining. Rather, this proposal is considering to use -sub-blocks for improving transactions propagation and providing a framework for weaker confirmations. Sub-Blocks And Transactions Propagation --------------------------------------- @@ -132,3 +151,11 @@ References https://www.usenix.org/system/files/conference/nsdi16/nsdi16-paper-eyal.pdf 2. Kiffer, Lucianna, et al. "Nakamoto Consensus under Bounded Processing Capacity." Proceedings of the 2024 on ACM SIGSAC Conference on Computer and Communications Security. 2024. https://iacr.steepath.eu/2023/381-NakamotoConsensusunderBoundedProcessingCapacity.pdf +3. Bagaria, Vivek, et al. "Prism: Deconstructing the blockchain to approach physical limits." Proceedings of the 2019 ACM SIGSAC Conference on Computer and Communications Security. 2019. + https://dl.acm.org/doi/pdf/10.1145/3319535.3363213 +4. Garay, Juan, Aggelos Kiayias, and Yu Shen. "Proof-of-work-based consensus in expected-constant time." Annual International Conference on the Theory and Applications of Cryptographic Techniques. Cham: Springer Nature Switzerland, 2024. + https://eprint.iacr.org/2023/1663.pdf +5. Keller, Patrik, et al. "Tailstorm: A secure and fair blockchain for cash transactions." arXiv preprint arXiv:2306.12206 (2023). + https://arxiv.org/pdf/2306.12206 +6. Garay, Juan, Aggelos Kiayias, and Nikos Leonardos. "The bitcoin backbone protocol: Analysis and applications." Journal of the ACM 71.4 (2024): 1-49. + https://dl.acm.org/doi/pdf/10.1145/3653445 From 70c57c204bce57dd37d5cce511dc8755daae961d Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 1 Jan 2025 03:32:54 +0300 Subject: [PATCH 061/109] Transactions Handling --- papers/subblocks/subblocks.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index 3502a5f870..892ef60e02 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -59,12 +59,27 @@ Transactions Handling --------------------- Transactions are broken into two classes, for first one result of transaction validation can't change from one input -block to other , for the second, validation result can vary (this is true for transactions relying on block timestamp, -miner pubkey and other fields from block header, a clear example here is ERG emission contract). +block to other , for the second, validation result can vary from one block candidate to another (this is true for transactions relying on block timestamp, +miner pubkey and other fields from block header, a clear example here is ERG emission contract, which is relying on miner pubkey). Transactions of the first class (about 99% of all transactions normally) can be included in input blocks only. Transactions of the second class can be included in both kinds of blocks. +As a miner does not know in advance which kind of block (input/ordering) will be generated, he is preparing for both +options by: + +* setting Merkle tree root of the block header to transactions seen in the last input block +and before that, since the last ordering block, plus all the second-class transactions miner has since the last ordering block. + +* setting 3 new fields in extension field of a block: + - setting a new field to new transactions included + - setting a new field to removed second-class transactions (first-class cant be removed) + - setting a new field to reference to a last seen input block (or Merkle tree of input blocks seen since last ordering block maybe) + +Miners are getting tx fees from first-class transactions and storage rent from input (sub) blocks, emission reward and tx fees +from second-class transactions from (ordering) blocks. +For tx fees to be collectable in input blocks, fee script should be changed to "true" just (todo: EIP). + Sub-Blocks And Transactions Propagation --------------------------------------- @@ -143,6 +158,14 @@ Security Considerations and Assumptions --------------------------------------- +Protocol Update +--------------- + +And only mining nodes update would be needed, while older nodes can receive ordinary block transactions message after every ordering block. + +And all the new rules will be made soft-forkable. + + References ---------- From d16aa2360f2759f00207f1f33233cf54511b298b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 3 Jan 2025 18:07:33 +0300 Subject: [PATCH 062/109] Transactions Handling --- papers/subblocks/subblocks.md | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index 892ef60e02..ad3065e34e 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -60,7 +60,8 @@ Transactions Handling Transactions are broken into two classes, for first one result of transaction validation can't change from one input block to other , for the second, validation result can vary from one block candidate to another (this is true for transactions relying on block timestamp, -miner pubkey and other fields from block header, a clear example here is ERG emission contract, which is relying on miner pubkey). +miner pubkey and other fields changing from one block header candidate to another, a clear example here is ERG emission contract, which is relying on miner pubkey. +See next section for more details). Transactions of the first class (about 99% of all transactions normally) can be included in input blocks only. Transactions of the second class can be included in both kinds of blocks. @@ -68,20 +69,39 @@ Transactions of the second class can be included in both kinds of blocks. As a miner does not know in advance which kind of block (input/ordering) will be generated, he is preparing for both options by: -* setting Merkle tree root of the block header to transactions seen in the last input block -and before that, since the last ordering block, plus all the second-class transactions miner has since the last ordering block. +* setting Merkle tree root of the block header to transactions seen in all the input blocks since the last ordering +block, plus all the second-class transactions miner has since the last ordering block. * setting 3 new fields in extension field of a block: - - setting a new field to new transactions included + - setting a new field to new transactions since last input-block included - setting a new field to removed second-class transactions (first-class cant be removed) - - setting a new field to reference to a last seen input block (or Merkle tree of input blocks seen since last ordering block maybe) + - setting a new field to reference to a last seen input block Miners are getting tx fees from first-class transactions and storage rent from input (sub) blocks, emission reward and tx fees from second-class transactions from (ordering) blocks. For tx fees to be collectable in input blocks, fee script should be changed to "true" just (todo: EIP). +Transaction Classes And Blocks Processing +----------------------------------------- -Sub-Blocks And Transactions Propagation +With overall picture provided in the previous section, we are going to define details of transactions and inputs- and +ordering-blocks here. + +First of all, lets define formally transactions classes. We define miner-affected transactions as transactions which +validity can be affected by a miner and block candidate the miner is forming, as their input scripts are using +following context fields: + +``` +def preHeader: PreHeader // timestamp, votes, minerPk can be changed from candidate to candidate + +def minerPubKey: Coll[Byte] +``` + +An example of such a transaction is ERG emission transaction. As a miner does not know which kind of block +(input/ordering) will be generated, he is including all the transactions into a block candidate. But then, if during +validation it turns out that an input-block is subject to validation, then miner-affected transactions are skipped. + +Input-Blocks And Transactions Propagation --------------------------------------- Let's consider that new block is just generated. Miners A and B (among others) are working on a new block. Users are From 8d8afa5b76ab298d81548f38f3e59a18fa8574e7 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 4 Jan 2025 02:00:14 +0300 Subject: [PATCH 063/109] propagation wip1 --- papers/subblocks/subblocks.md | 49 ++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index ad3065e34e..17fbd4a3f5 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -73,13 +73,13 @@ options by: block, plus all the second-class transactions miner has since the last ordering block. * setting 3 new fields in extension field of a block: - - setting a new field to new transactions since last input-block included - - setting a new field to removed second-class transactions (first-class cant be removed) + - setting a new field to a digest (Merkle tree root) of new first-class transactions since last input-block + - setting a new field to a digest (Merkle tree root) first class transactions since ordering block - setting a new field to reference to a last seen input block Miners are getting tx fees from first-class transactions and storage rent from input (sub) blocks, emission reward and tx fees from second-class transactions from (ordering) blocks. -For tx fees to be collectable in input blocks, fee script should be changed to "true" just (todo: EIP). +For tx fees to be collectable in input blocks, fee script should be changed to "true" just (todo: EIP). Transaction Classes And Blocks Processing ----------------------------------------- @@ -99,25 +99,32 @@ def minerPubKey: Coll[Byte] An example of such a transaction is ERG emission transaction. As a miner does not know which kind of block (input/ordering) will be generated, he is including all the transactions into a block candidate. But then, if during -validation it turns out that an input-block is subject to validation, then miner-affected transactions are skipped. +validation it turns out that an input-block is subject to validation, then miner-affected transactions are to be skipped. -Input-Blocks And Transactions Propagation ---------------------------------------- +Input and Ordering Blocks Propagation +------------------------------------- + +Here we consider how input and ordering blocks generated and their transactions are propagated over the p2p network, +for different clients (stateful/stateless). + +When a miner is generating an input block, it is announcing it by spreading header along with id of a previous input +block (parent). A peer, by receiving an announcement, is asking for input block data introspection message, which +contains proof of parent and both transaction Merkle trees against extension digest in the header, along with +first-class transaction 6-byte weak ids (similar to weak ids in Compact Blocks in Bitcoin). Receiver checks transaction + ids and downloads only first-class transactions to check. + +When a miner is generating an ordering block, it is announcing header similarly to input-block announcement. However, +in this case -Let's consider that new block is just generated. Miners A and B (among others) are working on a new block. Users are -submitting new unconfirmed transactions at the same time to the p2p network, and eventually they are reaching miners -(including A and B, but at a given time a transaction could be in one of the mempools just, not necessarily both, it -could also be somewhere else and not known to both A and B). +TODO: stateless clients. + +Incentivization +--------------- -Then, for example, miner A is generating a sub-block committing to new transactions after last block. It sends sub-block -header as well as weak transaction ids (6 bytes hashes) of transactions included into this sub-block but not previous -sub-blocks to peers. Peers then are asking for transactions they do not know only, and if previous sub-block is not -known, they are downloading it along with its transactions delta, and go further recursively if needed. +No incentives to generate and propagate sub-blocks are planned for the Ergo core protocols at the moment. At the same +time, incentives can be provided on the sub-block based merge-mined sidechains, or via application-specific agreements +(where applications may pay to miners for faster confirmations). -Thus pulse of sub-blocks will allow to exchange transactions quickly. And when a new sub-block is also a block (passing -normal difficulty check), not many transactions to download, normally. Thus instead of exchanging all the full-block -transactions when a new block comes, peers will exchange relatively small transaction deltas all the time. Full-block -transactions sections exchange still will be supported, to support downloading historical blocks, and also old clients. Sub-blocks Structure and Commitment to Sub-Blocks ------------------------------------------------- @@ -159,12 +166,6 @@ For rewarding miners submitting sub-blocks to Ergo network (sidechain block gene may be consist of main-chain sub-block and sidechain state along with membership proof. For enforcing linearity of transactions , sidechain consensus may enforce rollback to a sub-block before transaction reversal on proof of reversal being published. -Incentivization ---------------- - -No incentives to generate and propagate sub-blocks are planned for the Ergo core protocols at the moment. At the same -time, incentives can be provided on the sub-block based merge-mined sidechains, or via application-specific agreements -(where applications may pay to miners for faster confirmations). Weak Confirmations From 3752b3058225390a754f374953b6b4166db05800 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 4 Jan 2025 15:01:57 +0300 Subject: [PATCH 064/109] incentivization section --- papers/subblocks/subblocks.md | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index 17fbd4a3f5..af6c436c44 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -76,10 +76,6 @@ block, plus all the second-class transactions miner has since the last ordering - setting a new field to a digest (Merkle tree root) of new first-class transactions since last input-block - setting a new field to a digest (Merkle tree root) first class transactions since ordering block - setting a new field to reference to a last seen input block - -Miners are getting tx fees from first-class transactions and storage rent from input (sub) blocks, emission reward and tx fees -from second-class transactions from (ordering) blocks. -For tx fees to be collectable in input blocks, fee script should be changed to "true" just (todo: EIP). Transaction Classes And Blocks Processing ----------------------------------------- @@ -121,34 +117,9 @@ TODO: stateless clients. Incentivization --------------- -No incentives to generate and propagate sub-blocks are planned for the Ergo core protocols at the moment. At the same -time, incentives can be provided on the sub-block based merge-mined sidechains, or via application-specific agreements -(where applications may pay to miners for faster confirmations). - - -Sub-blocks Structure and Commitment to Sub-Blocks -------------------------------------------------- - -Here we consider what kind of footprint sub-blocks would have in consensus-enforced data structures (i.e. on-chain). -Proper balance here is critical and hard to achieve. Strict consensus-enforced commitments (when all the -sub-blocks committed on-chain) require from all the miners to have all the sub-blocks in order to check them. But, -at the same time, consensus-enforced commitments to properly ordered sub-blocks would allow for protocols and -applications using sub-blocks data. - -We have chosen weak commitments. That is, a miner may (and incentivized to) to commit to longest sub-blocks chain -since previous full-block, but that there are no any requirements about that in Ergo consensus rules. - -New extension key space starting with 0x03 will be used for sub-blocks related data, with one key used per this EIP: - -0x03 0x00 - digest of a Merkle tree of longest sub-blocks chain starting with previous block (but not including it). - -So first sub-block having full-block as a parent will have empty tree, next one will have only first, and next -full-block will commit to all the sub-blocks since previous full-block. - -Note that sub-blocks (like blocks) are forming direct acyclic graph (DAG), but only longest sub-blocks chain is -committed. - -At the same time, no any new checks are planned for the Ergo protocol. Checks are possible for sidechains. +Miners are getting tx fees from first-class transactions and storage rent from input (sub) blocks, emission reward and tx fees +from second-class transactions from (ordering) blocks. +For tx fees to be collectable in input blocks, fee script should be changed to "true" just (todo: EIP). Sub-Block Based Sidechains From e15dcd0b4ca0a72d32d97228f010d813540de39d Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 4 Jan 2025 15:14:52 +0300 Subject: [PATCH 065/109] EIP alpha version --- papers/subblocks/subblocks.md | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index af6c436c44..b9e9dd2ad8 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -122,40 +122,18 @@ from second-class transactions from (ordering) blocks. For tx fees to be collectable in input blocks, fee script should be changed to "true" just (todo: EIP). -Sub-Block Based Sidechains --------------------------- - -As L1 incentivization for propagating and committing on-chain to sub-blocks are missed, we consider sub-block based -merge-mined sidechains as possible option to incentivize miners to participate in the sub-blocks sub-protocol. They -also can be used to enforce linearity (so that transactions added in a previous sub-block can't be reversed). - -A merged-mined sidechain is using sub-blocks as well as blocks to update its state which can be committed via main-chain -transactions even. That is, in every sub-blocks side-chain state (sidechain UTXO set digest etc) can be written in a box -with sidechain NFT, and then every sub-block the box may be updated. - -For rewarding miners submitting sub-blocks to Ergo network (sidechain block generators are listening to), a sidechain block -may be consist of main-chain sub-block and sidechain state along with membership proof. For enforcing linearity of transactions -, sidechain consensus may enforce rollback to a sub-block before transaction reversal on proof of reversal being published. - - - -Weak Confirmations ------------------- - -With linearity of transactions history in sub-blocks chain, sub-blocks may be used for getting faster confirmations -with weaker security guarantees. - - Security Considerations and Assumptions --------------------------------------- +TODO: Protocol Update --------------- -And only mining nodes update would be needed, while older nodes can receive ordinary block transactions message after every ordering block. +Щnly mining nodes update would be needed, while older nodes can receive ordinary block transactions message after every ordering block. -And all the new rules will be made soft-forkable. +And all the new rules will be made soft-forkable, so it will be possible to change them with soft-fork (mining nodes upgrade after +90+% hashrate approval) only. From 7aae74ab6e66d826c025726ab16d33b118d99156 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 6 Jan 2025 19:56:51 +0300 Subject: [PATCH 066/109] update notes --- papers/subblocks/subblocks.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index b9e9dd2ad8..997a2e4508 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -130,7 +130,10 @@ TODO: Protocol Update --------------- -Щnly mining nodes update would be needed, while older nodes can receive ordinary block transactions message after every ordering block. +Initially, there will be no requirement to have new fields in the extension section. The new requirements will be introduced +when most of hashrate (90+%) would be updated and generating input blocks in the network. + +Only mining nodes update would be needed, while older nodes can receive ordinary block transactions message after every ordering block. And all the new rules will be made soft-forkable, so it will be possible to change them with soft-fork (mining nodes upgrade after 90+% hashrate approval) only. From 5d059f72f5348190b88e2084d325de7198f5b0ae Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 8 Jan 2025 01:07:06 +0300 Subject: [PATCH 067/109] transactions handling section updatte --- papers/subblocks/subblocks.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index 997a2e4508..a3abfbb7ed 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -69,13 +69,18 @@ Transactions of the second class can be included in both kinds of blocks. As a miner does not know in advance which kind of block (input/ordering) will be generated, he is preparing for both options by: -* setting Merkle tree root of the block header to transactions seen in all the input blocks since the last ordering -block, plus all the second-class transactions miner has since the last ordering block. +* setting Merkle tree root of the block header to transactions seen in all the previous input blocks since the last ordering +block, plus all the second-class transactions miner has since the last ordering block (including since last input block). * setting 3 new fields in extension field of a block: - setting a new field to a digest (Merkle tree root) of new first-class transactions since last input-block - - setting a new field to a digest (Merkle tree root) first class transactions since ordering block - - setting a new field to reference to a last seen input block + - setting a new field to a digest (Merkle tree root) first class transactions since ordering block till last input-block + - setting a new field to reference to a last seen input block + +With this structure we may have old clients still processing blocks, while new clients having better bandwidth utilization +and higher transactions throughput. + +Next, we define how new clients will process input and ordering blocks. Transaction Classes And Blocks Processing ----------------------------------------- From 7dfd05ea351eea1a785b44a9903bc6d7c1d36217 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 8 Jan 2025 12:58:07 +0300 Subject: [PATCH 068/109] transactions handling update --- papers/subblocks/subblocks.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md index a3abfbb7ed..3f6c475d37 100644 --- a/papers/subblocks/subblocks.md +++ b/papers/subblocks/subblocks.md @@ -69,15 +69,22 @@ Transactions of the second class can be included in both kinds of blocks. As a miner does not know in advance which kind of block (input/ordering) will be generated, he is preparing for both options by: -* setting Merkle tree root of the block header to transactions seen in all the previous input blocks since the last ordering +* setting transactions Merkle tree root of the block header to transactions seen in all the previous input blocks since the last ordering block, plus all the second-class transactions miner has since the last ordering block (including since last input block). * setting 3 new fields in extension field of a block: - - setting a new field to a digest (Merkle tree root) of new first-class transactions since last input-block - - setting a new field to a digest (Merkle tree root) first class transactions since ordering block till last input-block - - setting a new field to reference to a last seen input block - -With this structure we may have old clients still processing blocks, while new clients having better bandwidth utilization + - setting a new field E1 to a digest (Merkle tree root) of new first-class transactions since last input-block + - setting a new field E2 to a digest (Merkle tree root) first class transactions since ordering block till last input-block + - setting a new field E3 to reference to a last seen input block + +Before input/ordering blocks split, transactions Merkle tree root contained commitment to block's transactions. Similarly, +this field for an ordering block contains commitments for transactions. For input blocks, incremental updates should be +checked for this field, by checking that E2 contains all the first class transactions from the header's transactions +Merkle tree root, and then that E2 of an input blocks contains all the transactions from E2 of previous input block. +Thus double-spending first-class transactions from input blocks is not possible. + +Also, with this structure we may have old clients still processing blocks, by downloading full block transactions +corresponding to block header's transactions commitment, while new clients having better bandwidth utilization and higher transactions throughput. Next, we define how new clients will process input and ordering blocks. @@ -106,16 +113,15 @@ Input and Ordering Blocks Propagation ------------------------------------- Here we consider how input and ordering blocks generated and their transactions are propagated over the p2p network, -for different clients (stateful/stateless). +for different clients (stateful/stateless). -When a miner is generating an input block, it is announcing it by spreading header along with id of a previous input +When a miner has generated an input block, it is announcing it by spreading header along with id of a previous input block (parent). A peer, by receiving an announcement, is asking for input block data introspection message, which contains proof of parent and both transaction Merkle trees against extension digest in the header, along with first-class transaction 6-byte weak ids (similar to weak ids in Compact Blocks in Bitcoin). Receiver checks transaction ids and downloads only first-class transactions to check. -When a miner is generating an ordering block, it is announcing header similarly to input-block announcement. However, -in this case +When a miner is generating an ordering block, it is announcing header similarly to input-block announcement. TODO: stateless clients. From c5814df6716b9bbc4d56c2270af5629e22d64403 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 10 Jan 2025 00:16:54 +0300 Subject: [PATCH 069/109] input-block related extension fields --- .../history/extension/Extension.scala | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala b/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala index 4f48ecc771..bfb7af75a4 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala @@ -72,19 +72,35 @@ object Extension extends ApiCodecs { val ValidationRulesPrefix: Byte = 0x02 /** - * Prefix for keys related to sub-blocks related data. + * Prefix for keys related to input-blocks related data. */ - val SubBlocksDataPrefix: Byte = 0x03 + val InputBlocksDataPrefix: Byte = 0x03 - val PrevSubBlockIdKey: Array[Byte] = Array(SubBlocksDataPrefix, 0x00) + /** + * Digest (Merkle tree root) of new first-class transactions since last input-block + */ + val InputBlockTransactionsDigestKey: Array[Byte] = Array(InputBlocksDataPrefix, 0x00) - val SubBlockTransactionsDigestKey: Array[Byte] = Array(SubBlocksDataPrefix, 0x01) + /** + * Digest (Merkle tree root) first class transactions since ordering block till last input-block + */ + val PreviousInputBlockTransactionsDigestKey: Array[Byte] = Array(InputBlocksDataPrefix, 0x01) /** - * Prefix for keys related to sidechains data. + * Reference to last seen input block + */ + val PrevSubBlockIdKey: Array[Byte] = Array(InputBlocksDataPrefix, 0x02) + + + /** + * Prefix for keys related to sidechains data. Not used for now, reserved for future. */ val SidechainsDataPrefix: Byte = 0x04 + /** + * Prefix for keys related to rollup related blobs. Not used for now, reserved for future. + */ + val RollupBlobsDataPrefix: Byte = 0x05 /** From 251d09739d0c195b94d0e5bf8163ffbf6b32a908 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 10 Jan 2025 23:20:07 +0300 Subject: [PATCH 070/109] generateCandidate comments --- .../org/ergoplatform/mining/CandidateGenerator.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index b2cadbfb4d..158b84a27e 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -312,6 +312,7 @@ object CandidateGenerator extends ScorexLogging { tx.inputs.forall(inp => s.boxById(inp.boxId).isDefined) /** + * @param txsToInclude - user-provided transactions, to be included into a block (prioritized over mempool's) * @return None if chain is not synced or Some of attempt to create candidate */ def generateCandidate( @@ -320,22 +321,23 @@ object CandidateGenerator extends ScorexLogging { m: ErgoMemPoolReader, pk: ProveDlog, txsToInclude: Seq[ErgoTransaction], - ergoSettings: ErgoSettings - ): Option[Try[(Candidate, EliminateTransactions)]] = { - //mandatory transactions to include into next block taken from the previous candidate + ergoSettings: ErgoSettings): Option[Try[(Candidate, EliminateTransactions)]] = { + + // prioritized transactions to include + // filter out transactions which inputs spent already lazy val unspentTxsToInclude = txsToInclude.filter { tx => inputsNotSpent(tx, s) } val stateContext = s.stateContext - //only transactions valid from against the current utxo state we take from the mem pool + // mempool transactions to include into a block lazy val poolTransactions = m.getAllPrioritized lazy val emissionTxOpt = CandidateGenerator.collectEmission(s, pk, stateContext) - def chainSynced = + def chainSynced: Boolean = h.bestFullBlockOpt.map(_.id) == stateContext.lastHeaderOpt.map(_.id) def hasAnyMemPoolOrMinerTx = From 7fd69ec1ad058dfbb81f86ef2d070160544dfa11 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 10 Jan 2025 23:40:15 +0300 Subject: [PATCH 071/109] createCandidate refatoring --- .../mining/CandidateGenerator.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 158b84a27e..1c38f1f024 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -436,29 +436,29 @@ object CandidateGenerator extends ScorexLogging { ergoSettings: ErgoSettings ): Try[(Candidate, EliminateTransactions)] = Try { + val popowAlgos = new NipopowAlgos(ergoSettings.chainSettings) + val stateContext = state.stateContext // Extract best header and extension of a best block for assembling a new block val bestHeaderOpt: Option[Header] = history.bestFullBlockOpt.map(_.header) val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) - val lastSubblockOpt: Option[InputBlockInfo] = history.bestInputBlock() + val lastInputBlockOpt: Option[InputBlockInfo] = history.bestInputBlock() // there was sub-block generated before for this block - val continueSubblock = lastSubblockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.header.parentId)) + val continueInputBlock = lastInputBlockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.header.parentId)) // Make progress in time since last block. // If no progress is made, then, by consensus rules, the block will be rejected. - // todo: review w. subblocks val timestamp = Math.max(System.currentTimeMillis(), bestHeaderOpt.map(_.timestamp + 1).getOrElse(0L)) - val stateContext = state.stateContext - // Calculate required difficulty for the new block, the same diff for subblock - val nBits: Long = if(continueSubblock) { - lastSubblockOpt.get.header.nBits // .get is ok as lastSubblockOpt.exists in continueSubblock checks emptiness + val nBits: Long = if (continueInputBlock) { + // just take nbits from previous input block + lastInputBlockOpt.get.header.nBits // .get is ok as lastSubblockOpt.exists in continueSubblock checks emptiness } else { bestHeaderOpt .map(parent => history.requiredDifficultyAfter(parent)) @@ -471,6 +471,7 @@ object CandidateGenerator extends ScorexLogging { // Obtain NiPoPoW interlinks vector to pack it into the extension section val updInterlinks = popowAlgos.updateInterlinks(bestHeaderOpt, bestExtensionOpt) val interlinksExtension = popowAlgos.interlinksToExtension(updInterlinks) + val votingSettings = ergoSettings.chainSettings.voting val (extensionCandidate, votes: Array[Byte], version: Byte) = bestHeaderOpt .map { header => @@ -521,7 +522,7 @@ object CandidateGenerator extends ScorexLogging { val emissionTxs = emissionTxOpt.toSeq - // todo: remove in 5.0 + // todo: could be removed after 5.0, but we still slowly decreasing it for starters // we allow for some gap, to avoid possible problems when different interpreter version can estimate cost // differently due to bugs in AOT costing val safeGap = if (state.stateContext.currentParameters.maxBlockCost < 1000000) { From b77765c5a51eda3dc841dbbd0bbdc01c20fc5645 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 10 Jan 2025 23:56:38 +0300 Subject: [PATCH 072/109] new extension fields stub --- .../modifiers/history/extension/Extension.scala | 2 +- .../org/ergoplatform/mining/CandidateGenerator.scala | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala b/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala index bfb7af75a4..4a359189aa 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/extension/Extension.scala @@ -89,7 +89,7 @@ object Extension extends ApiCodecs { /** * Reference to last seen input block */ - val PrevSubBlockIdKey: Array[Byte] = Array(InputBlocksDataPrefix, 0x02) + val PrevInputBlockIdKey: Array[Byte] = Array(InputBlocksDataPrefix, 0x02) /** diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 1c38f1f024..6f363b4f46 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -8,7 +8,8 @@ import org.ergoplatform.mining.AutolykosPowScheme.derivedHeaderFields import org.ergoplatform.mining.difficulty.DifficultySerializer import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.modifiers.history._ -import org.ergoplatform.modifiers.history.extension.Extension +import org.ergoplatform.modifiers.history.extension.Extension.{InputBlockTransactionsDigestKey, PrevInputBlockIdKey, PreviousInputBlockTransactionsDigestKey} +import org.ergoplatform.modifiers.history.extension.{Extension, ExtensionCandidate} import org.ergoplatform.modifiers.history.header.{Header, HeaderWithoutPow} import org.ergoplatform.modifiers.history.popow.NipopowAlgos import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} @@ -473,7 +474,7 @@ object CandidateGenerator extends ScorexLogging { val interlinksExtension = popowAlgos.interlinksToExtension(updInterlinks) val votingSettings = ergoSettings.chainSettings.voting - val (extensionCandidate, votes: Array[Byte], version: Byte) = bestHeaderOpt + val (preExtensionCandidate, votes: Array[Byte], version: Byte) = bestHeaderOpt .map { header => val newHeight = header.height + 1 val currentParams = stateContext.currentParameters @@ -511,6 +512,13 @@ object CandidateGenerator extends ScorexLogging { (interlinksExtension, Array(0: Byte, 0: Byte, 0: Byte), Header.InitialVersion) ) + val inputBlockTransactionsDigest = (InputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + val previousInputBlockTransactions = (PreviousInputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + val prevInputBlockId = (PrevInputBlockIdKey, Array.emptyByteArray) // todo: real bytes + val inputBlockFields = ExtensionCandidate(Seq(inputBlockTransactionsDigest, previousInputBlockTransactions, prevInputBlockId)) + + val extensionCandidate = preExtensionCandidate ++ inputBlockFields + val upcomingContext = state.stateContext.upcoming( minerPk.value, timestamp, From 7dcec208c4972ec3de6003bf7ebaf10c6063a090 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 13 Jan 2025 22:35:38 +0300 Subject: [PATCH 073/109] forming prev input block id --- .../modifiers/history/header/Header.scala | 2 +- .../ergoplatform/mining/CandidateGenerator.scala | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/header/Header.scala b/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/header/Header.scala index 6aad02eade..d3672de681 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/header/Header.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/modifiers/history/header/Header.scala @@ -59,7 +59,7 @@ case class Header(override val version: Header.Version, override val sizeOpt: Option[Int] = None) extends HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, nBits, height, extensionRoot, votes, unparsedBytes) with PreHeader with BlockSection { - override def serializedId: Array[Header.Version] = Algos.hash(bytes) + override def serializedId: Array[Byte] = Algos.hash(bytes) override type M = Header diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 6f363b4f46..fa18a828bd 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -451,6 +451,13 @@ object CandidateGenerator extends ScorexLogging { // there was sub-block generated before for this block val continueInputBlock = lastInputBlockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.header.parentId)) + // todo: recheck + val parentInputBlockIdOpt = if (continueInputBlock) { + lastInputBlockOpt.map(_.header.serializedId) + } else { + None + } + // Make progress in time since last block. // If no progress is made, then, by consensus rules, the block will be rejected. val timestamp = @@ -512,10 +519,14 @@ object CandidateGenerator extends ScorexLogging { (interlinksExtension, Array(0: Byte, 0: Byte, 0: Byte), Header.InitialVersion) ) - val inputBlockTransactionsDigest = (InputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + val inputBlockTransactionsDigest = parentInputBlockIdOpt.map { prevInputBlockId => + (InputBlockTransactionsDigestKey, prevInputBlockId) + }.toSeq val previousInputBlockTransactions = (PreviousInputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes val prevInputBlockId = (PrevInputBlockIdKey, Array.emptyByteArray) // todo: real bytes - val inputBlockFields = ExtensionCandidate(Seq(inputBlockTransactionsDigest, previousInputBlockTransactions, prevInputBlockId)) + val inputBlockFields = ExtensionCandidate( + inputBlockTransactionsDigest ++ Seq(previousInputBlockTransactions, prevInputBlockId) + ) val extensionCandidate = preExtensionCandidate ++ inputBlockFields From e34a18169a5e3435b340df568cc4837ad27fd18e Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 13 Jan 2025 22:57:19 +0300 Subject: [PATCH 074/109] comments for extension fields --- .../ergoplatform/mining/CandidateGenerator.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index fa18a828bd..cc66c35110 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -519,13 +519,19 @@ object CandidateGenerator extends ScorexLogging { (interlinksExtension, Array(0: Byte, 0: Byte, 0: Byte), Header.InitialVersion) ) - val inputBlockTransactionsDigest = parentInputBlockIdOpt.map { prevInputBlockId => - (InputBlockTransactionsDigestKey, prevInputBlockId) + // digest (Merkle tree root) of new first-class transactions since last input-block + val inputBlockTransactionsDigest = (InputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + + // digest (Merkle tree root) of new first-class transactions since last input-block + val previousInputBlocksTransactions = (PreviousInputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + + // reference to a last seen input block + val prevInputBlockId = parentInputBlockIdOpt.map { prevInputBlockId => + (PrevInputBlockIdKey, prevInputBlockId) }.toSeq - val previousInputBlockTransactions = (PreviousInputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes - val prevInputBlockId = (PrevInputBlockIdKey, Array.emptyByteArray) // todo: real bytes + val inputBlockFields = ExtensionCandidate( - inputBlockTransactionsDigest ++ Seq(previousInputBlockTransactions, prevInputBlockId) + prevInputBlockId ++ Seq(inputBlockTransactionsDigest, previousInputBlocksTransactions) ) val extensionCandidate = preExtensionCandidate ++ inputBlockFields From 5773c173e9f3f3796bbf4419058e74f067a0cefb Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 14 Jan 2025 21:50:02 +0300 Subject: [PATCH 075/109] reordering code before reworking forming transactions --- .../mining/CandidateGenerator.scala | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index cc66c35110..cef617aeff 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -460,8 +460,7 @@ object CandidateGenerator extends ScorexLogging { // Make progress in time since last block. // If no progress is made, then, by consensus rules, the block will be rejected. - val timestamp = - Math.max(System.currentTimeMillis(), bestHeaderOpt.map(_.timestamp + 1).getOrElse(0L)) + val timestamp = Math.max(System.currentTimeMillis(), bestHeaderOpt.map(_.timestamp + 1).getOrElse(0L)) // Calculate required difficulty for the new block, the same diff for subblock val nBits: Long = if (continueInputBlock) { @@ -480,9 +479,15 @@ object CandidateGenerator extends ScorexLogging { val updInterlinks = popowAlgos.updateInterlinks(bestHeaderOpt, bestExtensionOpt) val interlinksExtension = popowAlgos.interlinksToExtension(updInterlinks) - val votingSettings = ergoSettings.chainSettings.voting + // todo: cache votes and version for a header, do not recalculate it each block + /* + * Calculate extension candidate without input-block specific fields, votes, and block version + */ + val (preExtensionCandidate, votes: Array[Byte], version: Byte) = bestHeaderOpt .map { header => + val votingSettings = ergoSettings.chainSettings.voting + val newHeight = header.height + 1 val currentParams = stateContext.currentParameters val voteForSoftFork = forkOrdered(ergoSettings, currentParams, header) @@ -519,33 +524,9 @@ object CandidateGenerator extends ScorexLogging { (interlinksExtension, Array(0: Byte, 0: Byte, 0: Byte), Header.InitialVersion) ) - // digest (Merkle tree root) of new first-class transactions since last input-block - val inputBlockTransactionsDigest = (InputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes - - // digest (Merkle tree root) of new first-class transactions since last input-block - val previousInputBlocksTransactions = (PreviousInputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes - - // reference to a last seen input block - val prevInputBlockId = parentInputBlockIdOpt.map { prevInputBlockId => - (PrevInputBlockIdKey, prevInputBlockId) - }.toSeq - - val inputBlockFields = ExtensionCandidate( - prevInputBlockId ++ Seq(inputBlockTransactionsDigest, previousInputBlocksTransactions) - ) - - val extensionCandidate = preExtensionCandidate ++ inputBlockFields - - val upcomingContext = state.stateContext.upcoming( - minerPk.value, - timestamp, - nBits, - votes, - proposedUpdate, - version - ) - - val emissionTxs = emissionTxOpt.toSeq + /* + * Forming transactions to get included + */ // todo: could be removed after 5.0, but we still slowly decreasing it for starters // we allow for some gap, to avoid possible problems when different interpreter version can estimate cost @@ -558,23 +539,55 @@ object CandidateGenerator extends ScorexLogging { 500000 } + val upcomingContext = state.stateContext.upcoming( + minerPk.value, + timestamp, + nBits, + votes, + proposedUpdate, + version + ) + + val transactionCandidates = emissionTxOpt.toSeq ++ prioritizedTransactions ++ poolTxs.map(_.transaction) + val (txs, toEliminate) = collectTxs( minerPk, state.stateContext.currentParameters.maxBlockCost - safeGap, state.stateContext.currentParameters.maxBlockSize, state, upcomingContext, - emissionTxs ++ prioritizedTransactions ++ poolTxs.map(_.transaction) + transactionCandidates ) val eliminateTransactions = EliminateTransactions(toEliminate) if (txs.isEmpty) { throw new IllegalArgumentException( - s"Proofs for 0 txs cannot be generated : emissionTxs: ${emissionTxs.size}, priorityTxs: ${prioritizedTransactions.size}, poolTxs: ${poolTxs.size}" + s"Proofs for 0 txs cannot be generated : emissionTx: ${emissionTxOpt.isDefined}, priorityTxs: ${prioritizedTransactions.size}, poolTxs: ${poolTxs.size}" ) } + /* + * Put input block related fields into extension section of block candidate + */ + + // digest (Merkle tree root) of new first-class transactions since last input-block + val inputBlockTransactionsDigest = (InputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + + // digest (Merkle tree root) of new first-class transactions since last input-block + val previousInputBlocksTransactions = (PreviousInputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + + // reference to a last seen input block + val prevInputBlockId = parentInputBlockIdOpt.map { prevInputBlockId => + (PrevInputBlockIdKey, prevInputBlockId) + }.toSeq + + val inputBlockFields = ExtensionCandidate( + prevInputBlockId ++ Seq(inputBlockTransactionsDigest, previousInputBlocksTransactions) + ) + + val extensionCandidate = preExtensionCandidate ++ inputBlockFields + def deriveWorkMessage(block: CandidateBlock) = { ergoSettings.chainSettings.powScheme.deriveExternalCandidate( block, From 41f2d595d79c660067a4409b3fd56154f0d84ed7 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 14 Jan 2025 22:14:13 +0300 Subject: [PATCH 076/109] initial input/ordering txs split --- .../org/ergoplatform/mining/CandidateGenerator.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index cef617aeff..c3c147cca2 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -548,7 +548,13 @@ object CandidateGenerator extends ScorexLogging { version ) - val transactionCandidates = emissionTxOpt.toSeq ++ prioritizedTransactions ++ poolTxs.map(_.transaction) + // returns txs which may and may not be included into input-block + def filterInputBlockTransactions(candidates: Seq[ErgoTransaction]): (Seq[ErgoTransaction], Seq[ErgoTransaction]) = { + (candidates, Seq.empty) // todo: real implemenation + } + + val (inputBlockTransactionCandidates, txsNotIncludedIntoInput) = filterInputBlockTransactions(prioritizedTransactions ++ poolTxs.map(_.transaction)) + val orderingBlocktransactionCandidates = emissionTxOpt.toSeq ++ inputBlockTransactionCandidates ++ txsNotIncludedIntoInput val (txs, toEliminate) = collectTxs( minerPk, @@ -556,7 +562,7 @@ object CandidateGenerator extends ScorexLogging { state.stateContext.currentParameters.maxBlockSize, state, upcomingContext, - transactionCandidates + orderingBlocktransactionCandidates ) val eliminateTransactions = EliminateTransactions(toEliminate) From 22458083c6bbe108775cda393f6248f1f562791b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 16 Jan 2025 14:26:53 +0300 Subject: [PATCH 077/109] comments fix --- .../subblocks-forum.md => inputblocks/inputblocks-forum.md} | 0 papers/{subblocks/subblocks.md => inputblocks/inputblocks.md} | 0 src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename papers/{subblocks/subblocks-forum.md => inputblocks/inputblocks-forum.md} (100%) rename papers/{subblocks/subblocks.md => inputblocks/inputblocks.md} (100%) diff --git a/papers/subblocks/subblocks-forum.md b/papers/inputblocks/inputblocks-forum.md similarity index 100% rename from papers/subblocks/subblocks-forum.md rename to papers/inputblocks/inputblocks-forum.md diff --git a/papers/subblocks/subblocks.md b/papers/inputblocks/inputblocks.md similarity index 100% rename from papers/subblocks/subblocks.md rename to papers/inputblocks/inputblocks.md diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index c3c147cca2..e696e96fd7 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -580,7 +580,7 @@ object CandidateGenerator extends ScorexLogging { // digest (Merkle tree root) of new first-class transactions since last input-block val inputBlockTransactionsDigest = (InputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes - // digest (Merkle tree root) of new first-class transactions since last input-block + // digest (Merkle tree root) first class transactions since ordering block till last input-block val previousInputBlocksTransactions = (PreviousInputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes // reference to a last seen input block From a4d892a5500d334541af2da63ec756feefc81472 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 16 Jan 2025 15:08:10 +0300 Subject: [PATCH 078/109] bestBlocks --- .../mining/CandidateGenerator.scala | 19 +++++-------------- .../InputBlocksProcessor.scala | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index e696e96fd7..76e20e54a1 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -442,30 +442,21 @@ object CandidateGenerator extends ScorexLogging { val stateContext = state.stateContext // Extract best header and extension of a best block for assembling a new block - val bestHeaderOpt: Option[Header] = history.bestFullBlockOpt.map(_.header) + val (bestHeaderOpt, bestInputBlock) = history.bestBlocks val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) - val lastInputBlockOpt: Option[InputBlockInfo] = history.bestInputBlock() - - // there was sub-block generated before for this block - val continueInputBlock = lastInputBlockOpt.exists(sbi => bestHeaderOpt.map(_.id).contains(sbi.header.parentId)) - - // todo: recheck - val parentInputBlockIdOpt = if (continueInputBlock) { - lastInputBlockOpt.map(_.header.serializedId) - } else { - None - } + // todo: put previous input block id, not header id + val parentInputBlockIdOpt = bestInputBlock.map(_.header.serializedId) // Make progress in time since last block. // If no progress is made, then, by consensus rules, the block will be rejected. val timestamp = Math.max(System.currentTimeMillis(), bestHeaderOpt.map(_.timestamp + 1).getOrElse(0L)) // Calculate required difficulty for the new block, the same diff for subblock - val nBits: Long = if (continueInputBlock) { + val nBits: Long = if (bestInputBlock.isDefined) { // just take nbits from previous input block - lastInputBlockOpt.get.header.nBits // .get is ok as lastSubblockOpt.exists in continueSubblock checks emptiness + bestInputBlock.get.header.nBits // .get is ok as lastSubblockOpt.exists in continueSubblock checks emptiness } else { bestHeaderOpt .map(parent => history.requiredDifficultyAfter(parent)) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index d2a84efb3c..81f006a451 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -1,6 +1,7 @@ package org.ergoplatform.nodeView.history.storage.modifierprocessors import org.ergoplatform.ErgoLikeContext.Height +import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.history.ErgoHistoryReader import org.ergoplatform.subblocks.InputBlockInfo @@ -31,6 +32,19 @@ trait InputBlocksProcessor extends ScorexLogging { // input block id -> input block transactions index val inputBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() + /** + * @return best ordering and input blocks + */ + def bestBlocks: (Option[Header], Option[InputBlockInfo]) = { + val bestOrdering = historyReader.bestFullBlockOpt.map(_.header) + val bestInputForOrdering = if(_bestInputBlock.exists(sbi => bestOrdering.map(_.id).contains(sbi.header.parentId))) { + _bestInputBlock + } else { + None + } + bestOrdering -> bestInputForOrdering + } + private def bestInputBlockHeight: Option[Height] = _bestInputBlock.map(_.header.height) private def prune() = { From 4633a5ef5ee7cadbd8be409a8667eca1ab2a0fcf Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 16 Jan 2025 16:24:54 +0300 Subject: [PATCH 079/109] parentInputBlockIdOpt fix --- .../scala/org/ergoplatform/mining/CandidateGenerator.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 76e20e54a1..3eda23018b 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -446,8 +446,7 @@ object CandidateGenerator extends ScorexLogging { val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) - // todo: put previous input block id, not header id - val parentInputBlockIdOpt = bestInputBlock.map(_.header.serializedId) + val parentInputBlockIdOpt = bestInputBlock.flatMap(bestInput => bestInput.prevInputBlockId) // Make progress in time since last block. // If no progress is made, then, by consensus rules, the block will be rejected. From cebf363bb7212381fa2db319a1c7b31c38a4533f Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 16 Jan 2025 22:47:48 +0300 Subject: [PATCH 080/109] transactionsCache --- .../network/ErgoNodeViewSynchronizer.scala | 1 + .../InputBlocksProcessor.scala | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index 842a1a2ecc..198aa4141f 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -1098,6 +1098,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } } + // todo: send transactions? or transaction ids? or switch from one option to another depending on message size ? def processInputBlockTransactionsRequest(subBlockId: ModifierId, hr: ErgoHistoryReader, remote: ConnectedPeer): Unit = { hr.getInputBlockTransactions(subBlockId) match { case Some(transactions) => diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 81f006a451..b822585cf7 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -29,8 +29,15 @@ trait InputBlocksProcessor extends ScorexLogging { // input block id -> input block index val inputBlockRecords = mutable.Map[ModifierId, InputBlockInfo]() - // input block id -> input block transactions index - val inputBlockTransactions = mutable.Map[ModifierId, Seq[ErgoTransaction]]() + // input block id -> input block transaction ids index + val inputBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() + + // txid -> transaction + val transactionsCache = mutable.Map[ModifierId, ErgoTransaction]() + + // todo: record incremental transaction sets for ordering blocks (and prune them) + // block header (ordering block) -> transaction ids + val orderingBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() /** * @return best ordering and input blocks @@ -100,11 +107,19 @@ trait InputBlocksProcessor extends ScorexLogging { } def applyInputBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { - inputBlockTransactions.put(sbId, transactions) + val transactionIds = transactions.map(_.id) + inputBlockTransactions.put(sbId, transactionIds) + transactions.foreach {tx => + transactionsCache.put(tx.id, tx) + } } def getInputBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { - inputBlockTransactions.get(sbId) + // todo: cache input block transactions to avoid recalculating it on every p2p request + // todo: optimize the code below + inputBlockTransactions.get(sbId).map{ids => + ids.flatMap(transactionsCache.get) + } } def bestInputBlock(): Option[InputBlockInfo] = { From 8a4b690a44e27efa7af7b56f99d1113704355136 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 17 Jan 2025 15:25:50 +0300 Subject: [PATCH 081/109] input block id, prev link fix, accumulating ordering block transactions --- .../org/ergoplatform/subblocks/InputBlockInfo.scala | 12 +++++++++++- .../org/ergoplatform/mining/CandidateGenerator.scala | 2 +- .../modifierprocessors/InputBlocksProcessor.scala | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala b/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala index 727785ac3a..b255aaa5b1 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala @@ -1,12 +1,15 @@ package org.ergoplatform.subblocks +import org.ergoplatform.core.bytesToId import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer} import org.ergoplatform.serialization.ErgoSerializer -import org.ergoplatform.settings.Constants +import org.ergoplatform.settings.{Algos, Constants} +import org.ergoplatform.subblocks.InputBlockInfo.FakePrevInputBlockId import scorex.crypto.authds.merkle.BatchMerkleProof import scorex.crypto.authds.merkle.serialization.BatchMerkleProofSerializer import scorex.crypto.hash.{Blake2b, Blake2b256, CryptographicHash, Digest32} import scorex.util.Extensions.IntOps +import scorex.util.ModifierId import scorex.util.serialization.{Reader, Writer} /** @@ -21,6 +24,7 @@ import scorex.util.serialization.{Reader, Writer} * (as they are coming from extension section, and committed in `subBlock` header via extension * digest) */ +// todo: introduce id case class InputBlockInfo(version: Byte, header: Header, prevInputBlockId: Option[Array[Byte]], @@ -28,6 +32,10 @@ case class InputBlockInfo(version: Byte, merkleProof: BatchMerkleProof[Digest32] // Merkle proof for both prevSubBlockId & subblockTransactionsDigest ) { + // todo: enough for unique id, but for protocols maybe its worth to authenticate transactions digest as well? + lazy val serializedId: Digest32 = Algos.hash(header.serializedId ++ prevInputBlockId.getOrElse(FakePrevInputBlockId)) + lazy val id: ModifierId = bytesToId(serializedId) + def valid(): Boolean = { // todo: implement data validity checks false @@ -38,6 +46,8 @@ case class InputBlockInfo(version: Byte, object InputBlockInfo { + private val FakePrevInputBlockId: Array[Byte] = Array.fill(32)(0.toByte) + val initialMessageVersion = 1.toByte private val bmp = new BatchMerkleProofSerializer[Digest32, CryptographicHash[Digest32]]()(Blake2b256) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 3eda23018b..86cdab843b 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -446,7 +446,7 @@ object CandidateGenerator extends ScorexLogging { val bestExtensionOpt: Option[Extension] = bestHeaderOpt .flatMap(h => history.typedModifierById[Extension](h.extensionId)) - val parentInputBlockIdOpt = bestInputBlock.flatMap(bestInput => bestInput.prevInputBlockId) + val parentInputBlockIdOpt = bestInputBlock.map(bestInput => bestInput.serializedId) // Make progress in time since last block. // If no progress is made, then, by consensus rules, the block will be rejected. diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index b822585cf7..d74488442e 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -34,7 +34,7 @@ trait InputBlocksProcessor extends ScorexLogging { // txid -> transaction val transactionsCache = mutable.Map[ModifierId, ErgoTransaction]() - + // todo: record incremental transaction sets for ordering blocks (and prune them) // block header (ordering block) -> transaction ids val orderingBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() @@ -109,6 +109,11 @@ trait InputBlocksProcessor extends ScorexLogging { def applyInputBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { val transactionIds = transactions.map(_.id) inputBlockTransactions.put(sbId, transactionIds) + if (sbId == _bestInputBlock.map(_.id).getOrElse("")) { + val orderingBlockId = _bestInputBlock.get.header.id + val curr = orderingBlockTransactions.getOrElse(orderingBlockId, Seq.empty) + orderingBlockTransactions.put(orderingBlockId, curr ++ transactionIds) + } transactions.foreach {tx => transactionsCache.put(tx.id, tx) } From f687ca4752a48cda92c229cea0ea6b591f52469f Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 20 Jan 2025 13:34:56 +0300 Subject: [PATCH 082/109] using previous ordering block transactions in orderingBlocktransactionCandidates --- .../mining/CandidateGenerator.scala | 3 ++- .../InputBlocksProcessor.scala | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 86cdab843b..f94cc3f9bc 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -543,8 +543,9 @@ object CandidateGenerator extends ScorexLogging { (candidates, Seq.empty) // todo: real implemenation } + val previousOrderingBlockTransactions = bestInputBlock.map(_.header).map(_.id).flatMap(history.getOrderingBlockTransactions).getOrElse(Seq.empty) val (inputBlockTransactionCandidates, txsNotIncludedIntoInput) = filterInputBlockTransactions(prioritizedTransactions ++ poolTxs.map(_.transaction)) - val orderingBlocktransactionCandidates = emissionTxOpt.toSeq ++ inputBlockTransactionCandidates ++ txsNotIncludedIntoInput + val orderingBlocktransactionCandidates = emissionTxOpt.toSeq ++ previousOrderingBlockTransactions ++ inputBlockTransactionCandidates ++ txsNotIncludedIntoInput val (txs, toEliminate) = collectTxs( minerPk, diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index d74488442e..0a93030821 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -12,7 +12,7 @@ import scala.collection.mutable /** * Storing and processing input-blocks related data * Desiderata: - * * store input blocks for short time only + * * store input blocks for short time only */ trait InputBlocksProcessor extends ScorexLogging { @@ -35,7 +35,7 @@ trait InputBlocksProcessor extends ScorexLogging { // txid -> transaction val transactionsCache = mutable.Map[ModifierId, ErgoTransaction]() - // todo: record incremental transaction sets for ordering blocks (and prune them) + // transactions generated AFTER an ordering block // block header (ordering block) -> transaction ids val orderingBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() @@ -44,7 +44,7 @@ trait InputBlocksProcessor extends ScorexLogging { */ def bestBlocks: (Option[Header], Option[InputBlockInfo]) = { val bestOrdering = historyReader.bestFullBlockOpt.map(_.header) - val bestInputForOrdering = if(_bestInputBlock.exists(sbi => bestOrdering.map(_.id).contains(sbi.header.parentId))) { + val bestInputForOrdering = if (_bestInputBlock.exists(sbi => bestOrdering.map(_.id).contains(sbi.header.parentId))) { _bestInputBlock } else { None @@ -114,7 +114,7 @@ trait InputBlocksProcessor extends ScorexLogging { val curr = orderingBlockTransactions.getOrElse(orderingBlockId, Seq.empty) orderingBlockTransactions.put(orderingBlockId, curr ++ transactionIds) } - transactions.foreach {tx => + transactions.foreach { tx => transactionsCache.put(tx.id, tx) } } @@ -122,15 +122,23 @@ trait InputBlocksProcessor extends ScorexLogging { def getInputBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { // todo: cache input block transactions to avoid recalculating it on every p2p request // todo: optimize the code below - inputBlockTransactions.get(sbId).map{ids => + inputBlockTransactions.get(sbId).map { ids => + ids.flatMap(transactionsCache.get) + } + } + + def getOrderingBlockTransactions(id: ModifierId): Option[Seq[ErgoTransaction]] = { + // todo: cache input block transactions to avoid recalculating it on every input block regeneration? + // todo: optimize the code below + orderingBlockTransactions.get(id).map { ids => ids.flatMap(transactionsCache.get) } } def bestInputBlock(): Option[InputBlockInfo] = { - _bestInputBlock.flatMap{bib => + _bestInputBlock.flatMap { bib => // todo: check header id? best input block can be child of non-best ordering header - if(bib.header.height == historyReader.headersHeight + 1) { + if (bib.header.height == historyReader.headersHeight + 1) { Some(bib) } else { None From d0f7ca6774dcf8bcda3e1e66fd7484c4c2e21313 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 20 Jan 2025 13:47:37 +0300 Subject: [PATCH 083/109] fixing subblocksPerBlock in checkNonces --- .../src/main/scala/org/ergoplatform/SubBlockAlgos.scala | 4 ++-- .../scala/org/ergoplatform/mining/AutolykosPowScheme.scala | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index ef63904bef..a8abb468d3 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -54,7 +54,7 @@ object SubBlockAlgos { sealed trait BlockKind case object InputBlock extends BlockKind - case object FinalizingBlock extends BlockKind + case object OrderingBlock extends BlockKind case object InvalidPoWBlock extends BlockKind def blockKind(header: Header): BlockKind = { @@ -66,7 +66,7 @@ object SubBlockAlgos { if (hit < subTarget) { InputBlock } else if (hit >= subTarget && hit < fullTarget) { - FinalizingBlock + OrderingBlock } else { InvalidPoWBlock } diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala index 97f2953221..d07afb157b 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala @@ -13,6 +13,7 @@ import org.ergoplatform.modifiers.history.header.{Header, HeaderSerializer, Head import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.history.ErgoHistoryUtils.GenesisHeight import org.ergoplatform.nodeView.mempool.TransactionMembershipProof +import org.ergoplatform.settings.Parameters import scorex.crypto.authds.{ADDigest, SerializedAdProof} import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.{ModifierId, ScorexLogging} @@ -386,7 +387,7 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { startNonce: Long, endNonce: Long): BlockSolutionSearchResult = { - val subblocksPerBlock = 10 // todo : make configurable + val subblocksPerBlock = Parameters.SubsPerBlockDefault // todo : make adjustable log.debug(s"Going to check nonces from $startNonce to $endNonce") val p1 = groupElemToBytes(genPk(sk)) @@ -412,9 +413,10 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { toBigInt(hash(indexes.map(i => genElement(version, m, p1, p2, Ints.toByteArray(i), h)).sum.toByteArray)) } if (d <= b) { - log.debug(s"Solution found at $i") + log.debug(s"Ordering block solution found at $i") OrderingSolutionFound(AutolykosSolution(genPk(sk), genPk(x), nonce, d)) } else if (d <= b * subblocksPerBlock) { + log.debug(s"Input block solution found at $i") InputSolutionFound(AutolykosSolution(genPk(sk), genPk(x), nonce, d)) } else { loop(i + 1) From 3288e61bc5c31db97d183e8beaafc9b918bb39f8 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 20 Jan 2025 14:14:23 +0300 Subject: [PATCH 084/109] checkInputBlockPoW & checkOrderingBlockPoW in AutolykosPowScheme --- .../scala/org/ergoplatform/SubBlockAlgos.scala | 8 -------- .../ergoplatform/mining/AutolykosPowScheme.scala | 15 ++++++++++++--- .../ergoplatform/mining/CandidateGenerator.scala | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala index a8abb468d3..4bcc0e5f8d 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -72,14 +72,6 @@ object SubBlockAlgos { } } - def checkInputBlockPoW(header: Header): Boolean = { - val hit = powScheme.hitForVersion2(header) // todo: cache hit in header - - val orderingTarget = powScheme.getB(header.nBits) - val inputTarget = orderingTarget * subsPerBlock - hit < inputTarget - } - // messages: // // sub block signal: diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala index d07afb157b..cf48a23fc7 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala @@ -3,6 +3,7 @@ package org.ergoplatform.mining import com.google.common.primitives.{Bytes, Ints, Longs} import org.bouncycastle.util.BigIntegers import org.ergoplatform.ErgoLikeContext.Height +import org.ergoplatform.SubBlockAlgos.{subsPerBlock} import org.ergoplatform.{BlockSolutionSearchResult, InputBlockFound, InputBlockHeaderFound, InputSolutionFound, NoSolutionFound, NothingFound, OrderingBlockFound, OrderingBlockHeaderFound, OrderingSolutionFound, ProveBlockResult} import org.ergoplatform.mining.difficulty.DifficultySerializer import org.ergoplatform.modifiers.ErgoFullBlock @@ -108,7 +109,7 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { // for version 1, we check equality of left and right sides of the equation require(checkPoWForVersion1(header), "Incorrect points") } else { - require(checkPoWForVersion2(header), "h(f) < b condition not met") + require(checkOrderingBlockPoW(header), "h(f) < b condition not met") } } @@ -118,13 +119,21 @@ class AutolykosPowScheme(val k: Int, val n: Int) extends ScorexLogging { * @param header - header to check PoW for * @return whether PoW is valid or not */ - def checkPoWForVersion2(header: Header): Boolean = { - val b = getB(header.nBits) + def checkOrderingBlockPoW(header: Header): Boolean = { // for version 2, we're calculating hit and compare it with target val hit = hitForVersion2(header) + + val b = getB(header.nBits) hit < b } + def checkInputBlockPoW(header: Header): Boolean = { + val hit = hitForVersion2(header) // todo: cache hit in header + + val orderingTarget = getB(header.nBits) + val inputTarget = orderingTarget * subsPerBlock // todo: use adjustable subsPerBlock + hit < inputTarget + } /** * Check PoW for Autolykos v1 header * diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index f94cc3f9bc..19399756a1 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -214,7 +214,7 @@ class CandidateGenerator( sendInputToNodeView(sbi, sbt) StatusReply.error( - new Exception(s"Input block found! PoW valid: ${SubBlockAlgos.checkInputBlockPoW(sbi.header)}") + new Exception(s"Input block found! PoW valid: ${SubBlockAlgos.powScheme.checkInputBlockPoW(sbi.header)}") ) } } From 879b50b0d0dcba1480565ba3712b802f14b12653 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 21 Jan 2025 18:22:31 +0300 Subject: [PATCH 085/109] passing link to previous input block, clearing miner cache on input block generation --- .../ergoplatform/mining/CandidateBlock.scala | 3 +- .../mining/CandidateGenerator.scala | 38 +++++++++++-------- .../nodeView/ErgoNodeViewHolder.scala | 3 ++ .../InputBlocksProcessor.scala | 8 ++-- .../history/extra/ChainGenerator.scala | 2 +- .../ergoplatform/tools/ChainGenerator.scala | 2 +- .../org/ergoplatform/tools/MinerBench.scala | 4 +- 7 files changed, 36 insertions(+), 24 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/CandidateBlock.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/CandidateBlock.scala index 55848369c4..7420ed23bc 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/CandidateBlock.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/CandidateBlock.scala @@ -16,7 +16,8 @@ case class CandidateBlock(parentOpt: Option[Header], transactions: Seq[ErgoTransaction], timestamp: Header.Timestamp, extension: ExtensionCandidate, - votes: Array[Byte]) { + votes: Array[Byte], + inputBlockFields: Seq[(Array[Byte], Array[Byte])]) { override def toString: String = s"CandidateBlock(${this.asJson})" diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 19399756a1..f3e8de8311 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -19,7 +19,6 @@ import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.Eliminate import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.{LocallyGeneratedInputBlock, LocallyGeneratedOrderingBlock} import org.ergoplatform.nodeView.history.ErgoHistoryUtils.Height -import org.ergoplatform.nodeView.history.storage.modifierprocessors.InputBlocksProcessor import org.ergoplatform.nodeView.history.{ErgoHistoryReader, ErgoHistoryUtils} import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.nodeView.state.{ErgoState, ErgoStateContext, UtxoStateReader} @@ -31,7 +30,7 @@ import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, Input import scorex.crypto.authds.merkle.BatchMerkleProof import scorex.crypto.hash.Digest32 import scorex.util.encode.Base16 -import scorex.util.{ModifierId, ScorexLogging, idToBytes} +import scorex.util.{ModifierId, ScorexLogging} import sigma.data.{Digest32Coll, ProveDlog} import sigma.crypto.CryptoFacade import sigma.eval.Extensions.EvalIterableOps @@ -208,11 +207,15 @@ class CandidateGenerator( ) } case _: InputSolutionFound => - log.info("Input-block mined!") + val (sbi, sbt) = completeInputBlock(state.cache.get.candidateBlock, solution) + + log.info(s"Input-block mined @ height ${sbi.header.height}!") - val (sbi, sbt) = completeInputBlock(state.hr, state.cache.get.candidateBlock, solution) sendInputToNodeView(sbi, sbt) + // todo: return success + log.warn(s"Removing candidate due to input block") + context.become(initialized(state.copy(cache = None))) StatusReply.error( new Exception(s"Input block found! PoW valid: ${SubBlockAlgos.powScheme.checkInputBlockPoW(sbi.header)}") ) @@ -579,11 +582,9 @@ object CandidateGenerator extends ScorexLogging { (PrevInputBlockIdKey, prevInputBlockId) }.toSeq - val inputBlockFields = ExtensionCandidate( - prevInputBlockId ++ Seq(inputBlockTransactionsDigest, previousInputBlocksTransactions) - ) + val inputBlockFields = prevInputBlockId ++ Seq(inputBlockTransactionsDigest, previousInputBlocksTransactions) - val extensionCandidate = preExtensionCandidate ++ inputBlockFields + val extensionCandidate = preExtensionCandidate ++ ExtensionCandidate(inputBlockFields) def deriveWorkMessage(block: CandidateBlock) = { ergoSettings.chainSettings.powScheme.deriveExternalCandidate( @@ -604,7 +605,8 @@ object CandidateGenerator extends ScorexLogging { txs, timestamp, extensionCandidate, - votes + votes, + inputBlockFields ) val ext = deriveWorkMessage(candidate) log.info( @@ -636,7 +638,8 @@ object CandidateGenerator extends ScorexLogging { fallbackTxs, timestamp, extensionCandidate, - votes + votes, + inputBlockFields = Seq.empty // todo: recheck, likely should be different ) Candidate( candidate, @@ -961,21 +964,24 @@ object CandidateGenerator extends ScorexLogging { new ErgoFullBlock(header, blockTransactions, extension, Some(adProofs)) } - def completeInputBlock(inputBlockProcessor: InputBlocksProcessor, - candidate: CandidateBlock, + def completeInputBlock(candidate: CandidateBlock, solution: AutolykosSolution): (InputBlockInfo, InputBlockTransactionsData) = { + val header = deriveUnprovenHeader(candidate).toHeader(solution, None) + val txs = candidate.transactions + // todo: check links? // todo: update candidate generator state // todo: form and send real data instead of null - val prevInputBlockId: Option[Array[Byte]] = inputBlockProcessor.bestInputBlock().map(_.header.id).map(idToBytes) + val prevInputBlockId: Option[Array[Byte]] = if(candidate.inputBlockFields.size < 3){ + None + } else { + Some(candidate.inputBlockFields.head._2) + } val inputBlockTransactionsDigest: Digest32 = null val merkleProof: BatchMerkleProof[Digest32] = null - val header = deriveUnprovenHeader(candidate).toHeader(solution, None) - val txs = candidate.transactions - val sbi: InputBlockInfo = InputBlockInfo(InputBlockInfo.initialMessageVersion, header, prevInputBlockId, inputBlockTransactionsDigest, merkleProof) val sbt : InputBlockTransactionsData = InputBlockTransactionsData(sbi.header.id, txs) diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 56925abfc6..ff1cc65ac7 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -304,6 +304,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti } // input blocks related logic + // process input block got from p2p network case ProcessInputBlock(sbi) => history().applyInputBlock(sbi) @@ -673,6 +674,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti case lm: LocallyGeneratedBlockSection => log.info(s"Got locally generated modifier ${lm.blockSection.encodedId} of type ${lm.blockSection.modifierTypeId}") pmodModify(lm.blockSection, local = true) + case LocallyGeneratedOrderingBlock(efb) => log.info(s"Got locally generated ordering block ${efb.id}") pmodModify(efb.header, local = true) @@ -684,6 +686,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti sectionsToApply.foreach { section => pmodModify(section, local = true) } + case LocallyGeneratedInputBlock(subblockInfo, subBlockTransactionsData) => log.info(s"Got locally generated input block ${subblockInfo.header.id}") history().applyInputBlock(subblockInfo) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 0a93030821..ff47d346bb 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -88,7 +88,7 @@ trait InputBlocksProcessor extends ScorexLogging { inputBlockRecords.put(ib.header.id, ib) - val ibParent = ib.prevInputBlockId + val ibParent = ib.prevInputBlockId.map(bytesToId) // todo: currently only one chain of subblocks considered, // todo: in fact there could be multiple trees here (one subblocks tree per header) @@ -97,12 +97,12 @@ trait InputBlocksProcessor extends ScorexLogging { case None => log.info(s"Applying best input block #: ${ib.header.id}, no parent") _bestInputBlock = Some(ib) - case Some(maybeParent) if (ibParent.map(bytesToId).contains(maybeParent.header.id)) => - log.info(s"Applying best input block #: ${ib.header.id}, parent is ${maybeParent.header.id}") + case Some(maybeParent) if (ibParent.contains(maybeParent.id)) => + log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") _bestInputBlock = Some(ib) case _ => // todo: switch from one input block chain to another - log.info(s"Applying non-best input block #: ${ib.header.id}, parent #: ${ibParent}") + log.info(s"Applying non-best input block #: ${ib.header.id}, parent #: $ibParent") } } diff --git a/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala b/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala index 94150ac444..68378d6a3c 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala @@ -185,7 +185,7 @@ object ChainGenerator extends ErgoTestHelpers with Matchers { val txs = emissionTxOpt.toSeq ++ txsFromPool state.proofsForTransactions(txs).map { case (adProof, adDigest) => - CandidateBlock(lastHeaderOpt, version, nBits, adDigest, adProof, txs, ts, extensionCandidate, votes) + CandidateBlock(lastHeaderOpt, version, nBits, adDigest, adProof, txs, ts, extensionCandidate, votes, Seq.empty) } }.flatten diff --git a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala index f21658c230..59d7d63380 100644 --- a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala @@ -199,7 +199,7 @@ object ChainGenerator extends App with ErgoTestHelpers with Matchers { val txs = emissionTxOpt.toSeq ++ txsFromPool state.proofsForTransactions(txs).map { case (adProof, adDigest) => - CandidateBlock(lastHeaderOpt, version, nBits, adDigest, adProof, txs, ts, extensionCandidate, votes) + CandidateBlock(lastHeaderOpt, version, nBits, adDigest, adProof, txs, ts, extensionCandidate, votes, Seq.empty) } }.flatten diff --git a/src/test/scala/org/ergoplatform/tools/MinerBench.scala b/src/test/scala/org/ergoplatform/tools/MinerBench.scala index b013dfdc5a..ea8a6e6158 100644 --- a/src/test/scala/org/ergoplatform/tools/MinerBench.scala +++ b/src/test/scala/org/ergoplatform/tools/MinerBench.scala @@ -76,7 +76,9 @@ object MinerBench extends App with ErgoTestHelpers { fb.blockTransactions.txs, System.currentTimeMillis(), ExtensionCandidate(Seq.empty), - Array()) + Array(), + Seq.empty + ) val newHeader = pow.proveCandidate(candidate, sk) .asInstanceOf[OrderingBlockFound] // todo: fix .fb From b6892993e4fb011f3ae8ec204ec5db514e62a3e2 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 22 Jan 2025 12:38:07 +0300 Subject: [PATCH 086/109] filtering out non-input transactions --- .../org/ergoplatform/mining/CandidateBlock.scala | 3 ++- .../org/ergoplatform/mining/CandidateGenerator.scala | 12 ++++++++---- .../modifierprocessors/InputBlocksProcessor.scala | 1 + .../nodeView/history/extra/ChainGenerator.scala | 2 +- .../org/ergoplatform/tools/ChainGenerator.scala | 2 +- .../scala/org/ergoplatform/tools/MinerBench.scala | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/mining/CandidateBlock.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/CandidateBlock.scala index 7420ed23bc..fc2c11dd0a 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/CandidateBlock.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/CandidateBlock.scala @@ -17,7 +17,8 @@ case class CandidateBlock(parentOpt: Option[Header], timestamp: Header.Timestamp, extension: ExtensionCandidate, votes: Array[Byte], - inputBlockFields: Seq[(Array[Byte], Array[Byte])]) { + inputBlockFields: Seq[(Array[Byte], Array[Byte])], + inputBlockTransactions: Seq[ErgoTransaction]) { override def toString: String = s"CandidateBlock(${this.asJson})" diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index f3e8de8311..6a386d901e 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -559,6 +559,8 @@ object CandidateGenerator extends ScorexLogging { orderingBlocktransactionCandidates ) + val inputBlockTransactions = inputBlockTransactionCandidates.filterNot(tx => toEliminate.contains(tx.id)) + val eliminateTransactions = EliminateTransactions(toEliminate) if (txs.isEmpty) { @@ -606,7 +608,8 @@ object CandidateGenerator extends ScorexLogging { timestamp, extensionCandidate, votes, - inputBlockFields + inputBlockFields, + inputBlockTransactions ) val ext = deriveWorkMessage(candidate) log.info( @@ -639,7 +642,8 @@ object CandidateGenerator extends ScorexLogging { timestamp, extensionCandidate, votes, - inputBlockFields = Seq.empty // todo: recheck, likely should be different + inputBlockFields = Seq.empty, // todo: recheck, likely should be not empty, + inputBlockTransactions = inputBlockTransactions ) Candidate( candidate, @@ -968,13 +972,13 @@ object CandidateGenerator extends ScorexLogging { solution: AutolykosSolution): (InputBlockInfo, InputBlockTransactionsData) = { val header = deriveUnprovenHeader(candidate).toHeader(solution, None) - val txs = candidate.transactions + val txs = candidate.inputBlockTransactions // todo: check links? // todo: update candidate generator state // todo: form and send real data instead of null - val prevInputBlockId: Option[Array[Byte]] = if(candidate.inputBlockFields.size < 3){ + val prevInputBlockId: Option[Array[Byte]] = if (candidate.inputBlockFields.size < 3) { None } else { Some(candidate.inputBlockFields.head._2) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index ff47d346bb..d704c77e3b 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -107,6 +107,7 @@ trait InputBlocksProcessor extends ScorexLogging { } def applyInputBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { + log.info(s"Applying input block transactions for ${sbId} , transactions: ${transactions.size}") val transactionIds = transactions.map(_.id) inputBlockTransactions.put(sbId, transactionIds) if (sbId == _bestInputBlock.map(_.id).getOrElse("")) { diff --git a/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala b/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala index 68378d6a3c..b95b68a8e4 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/extra/ChainGenerator.scala @@ -185,7 +185,7 @@ object ChainGenerator extends ErgoTestHelpers with Matchers { val txs = emissionTxOpt.toSeq ++ txsFromPool state.proofsForTransactions(txs).map { case (adProof, adDigest) => - CandidateBlock(lastHeaderOpt, version, nBits, adDigest, adProof, txs, ts, extensionCandidate, votes, Seq.empty) + CandidateBlock(lastHeaderOpt, version, nBits, adDigest, adProof, txs, ts, extensionCandidate, votes, Seq.empty, Seq.empty) } }.flatten diff --git a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala index 59d7d63380..195ddd2e01 100644 --- a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala @@ -199,7 +199,7 @@ object ChainGenerator extends App with ErgoTestHelpers with Matchers { val txs = emissionTxOpt.toSeq ++ txsFromPool state.proofsForTransactions(txs).map { case (adProof, adDigest) => - CandidateBlock(lastHeaderOpt, version, nBits, adDigest, adProof, txs, ts, extensionCandidate, votes, Seq.empty) + CandidateBlock(lastHeaderOpt, version, nBits, adDigest, adProof, txs, ts, extensionCandidate, votes, Seq.empty, Seq.empty) } }.flatten diff --git a/src/test/scala/org/ergoplatform/tools/MinerBench.scala b/src/test/scala/org/ergoplatform/tools/MinerBench.scala index ea8a6e6158..32eaf97b1b 100644 --- a/src/test/scala/org/ergoplatform/tools/MinerBench.scala +++ b/src/test/scala/org/ergoplatform/tools/MinerBench.scala @@ -59,7 +59,6 @@ object MinerBench extends App with ErgoTestHelpers { println(s"Calculation time of $Steps numberic hashes over ${data.length} bytes") println(s"Blake2b256: ${st2 - st} ms") println(s"Blake2b512: ${st4 - st3} ms") - } def validationBench() { @@ -77,6 +76,7 @@ object MinerBench extends App with ErgoTestHelpers { System.currentTimeMillis(), ExtensionCandidate(Seq.empty), Array(), + Seq.empty, Seq.empty ) val newHeader = pow.proveCandidate(candidate, sk) From c77d30bcac905262f3389271917c9e37f871a106 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 23 Jan 2025 11:26:33 +0300 Subject: [PATCH 087/109] filtering out already included before collectTxs, id arg fix in applyInputBlockTransactions --- .../scala/org/ergoplatform/mining/CandidateGenerator.scala | 5 ++++- .../scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 6a386d901e..8b74b13b59 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -548,6 +548,9 @@ object CandidateGenerator extends ScorexLogging { val previousOrderingBlockTransactions = bestInputBlock.map(_.header).map(_.id).flatMap(history.getOrderingBlockTransactions).getOrElse(Seq.empty) val (inputBlockTransactionCandidates, txsNotIncludedIntoInput) = filterInputBlockTransactions(prioritizedTransactions ++ poolTxs.map(_.transaction)) + + val previousOrderingBlockTransactionIds = previousOrderingBlockTransactions.map(_.id) + val filteredInputBlockTransactionCandidates = inputBlockTransactionCandidates.filterNot(tx => previousOrderingBlockTransactionIds.contains(tx.id)) val orderingBlocktransactionCandidates = emissionTxOpt.toSeq ++ previousOrderingBlockTransactions ++ inputBlockTransactionCandidates ++ txsNotIncludedIntoInput val (txs, toEliminate) = collectTxs( @@ -559,7 +562,7 @@ object CandidateGenerator extends ScorexLogging { orderingBlocktransactionCandidates ) - val inputBlockTransactions = inputBlockTransactionCandidates.filterNot(tx => toEliminate.contains(tx.id)) + val inputBlockTransactions = filteredInputBlockTransactionCandidates.filterNot(tx => toEliminate.contains(tx.id)) val eliminateTransactions = EliminateTransactions(toEliminate) diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index ff1cc65ac7..9aa2fab6b4 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -690,7 +690,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti case LocallyGeneratedInputBlock(subblockInfo, subBlockTransactionsData) => log.info(s"Got locally generated input block ${subblockInfo.header.id}") history().applyInputBlock(subblockInfo) - history().applyInputBlockTransactions(subblockInfo.header.id, subBlockTransactionsData.transactions) + history().applyInputBlockTransactions(subblockInfo.id, subBlockTransactionsData.transactions) // todo: finish processing } From cac0278042c671fc25fe1aadad48412ba7d2f330 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 23 Jan 2025 11:49:01 +0300 Subject: [PATCH 088/109] real Merkle tree digests --- .../ergoplatform/mining/CandidateGenerator.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 8b74b13b59..4b696d51ac 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -22,15 +22,16 @@ import org.ergoplatform.nodeView.history.ErgoHistoryUtils.Height import org.ergoplatform.nodeView.history.{ErgoHistoryReader, ErgoHistoryUtils} import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.nodeView.state.{ErgoState, ErgoStateContext, UtxoStateReader} -import org.ergoplatform.settings.{ErgoSettings, ErgoValidationSettingsUpdate, Parameters} +import org.ergoplatform.settings.{Algos, ErgoSettings, ErgoValidationSettingsUpdate, Parameters} import org.ergoplatform.sdk.wallet.Constants.MaxAssetsPerBox import org.ergoplatform.subblocks.InputBlockInfo import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input, InputSolutionFound, OrderingSolutionFound, SolutionFound, SubBlockAlgos} +import scorex.crypto.authds.LeafData import scorex.crypto.authds.merkle.BatchMerkleProof import scorex.crypto.hash.Digest32 import scorex.util.encode.Base16 -import scorex.util.{ModifierId, ScorexLogging} +import scorex.util.{ModifierId, ScorexLogging, idToBytes} import sigma.data.{Digest32Coll, ProveDlog} import sigma.crypto.CryptoFacade import sigma.eval.Extensions.EvalIterableOps @@ -549,7 +550,7 @@ object CandidateGenerator extends ScorexLogging { val previousOrderingBlockTransactions = bestInputBlock.map(_.header).map(_.id).flatMap(history.getOrderingBlockTransactions).getOrElse(Seq.empty) val (inputBlockTransactionCandidates, txsNotIncludedIntoInput) = filterInputBlockTransactions(prioritizedTransactions ++ poolTxs.map(_.transaction)) - val previousOrderingBlockTransactionIds = previousOrderingBlockTransactions.map(_.id) + val previousOrderingBlockTransactionIds = previousOrderingBlockTransactions.map(_.id) // todo: check only first-class txs there val filteredInputBlockTransactionCandidates = inputBlockTransactionCandidates.filterNot(tx => previousOrderingBlockTransactionIds.contains(tx.id)) val orderingBlocktransactionCandidates = emissionTxOpt.toSeq ++ previousOrderingBlockTransactions ++ inputBlockTransactionCandidates ++ txsNotIncludedIntoInput @@ -572,15 +573,18 @@ object CandidateGenerator extends ScorexLogging { ) } + val inputBlockTransactionsDigestValue = Algos.merkleTreeRoot(inputBlockTransactions.map(tx => LeafData @@ tx.serializedId)) + val previousInputBlocksTransactionsValue = Algos.merkleTreeRoot(previousOrderingBlockTransactionIds.map(id => LeafData @@ idToBytes(id))) + /* * Put input block related fields into extension section of block candidate */ // digest (Merkle tree root) of new first-class transactions since last input-block - val inputBlockTransactionsDigest = (InputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + val inputBlockTransactionsDigest = (InputBlockTransactionsDigestKey, inputBlockTransactionsDigestValue) // digest (Merkle tree root) first class transactions since ordering block till last input-block - val previousInputBlocksTransactions = (PreviousInputBlockTransactionsDigestKey, Array.emptyByteArray) // todo: real bytes + val previousInputBlocksTransactions = (PreviousInputBlockTransactionsDigestKey, previousInputBlocksTransactionsValue) // reference to a last seen input block val prevInputBlockId = parentInputBlockIdOpt.map { prevInputBlockId => From bca36db617249edf81fd43028d7a1f515bd251e0 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 24 Jan 2025 15:24:54 +0300 Subject: [PATCH 089/109] NewBestInputBlock signal, /info field --- .../ergoplatform/local/ErgoStatsCollector.scala | 15 ++++++++++++++- .../ErgoNodeViewSynchronizerMessages.scala | 4 +++- .../nodeView/ErgoNodeViewHolder.scala | 13 +++++++++++-- .../modifierprocessors/InputBlocksProcessor.scala | 11 +++++++++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/ergoplatform/local/ErgoStatsCollector.scala b/src/main/scala/org/ergoplatform/local/ErgoStatsCollector.scala index 39091b904a..3c93f70d20 100644 --- a/src/main/scala/org/ergoplatform/local/ErgoStatsCollector.scala +++ b/src/main/scala/org/ergoplatform/local/ErgoStatsCollector.scala @@ -17,7 +17,7 @@ import scorex.core.network.ConnectedPeer import scorex.core.network.NetworkController.ReceivableMessages.{GetConnectedPeers, GetPeersStatus} import org.ergoplatform.network.ErgoNodeViewSynchronizerMessages._ import org.ergoplatform.network.ErgoSyncTracker -import scorex.util.ScorexLogging +import scorex.util.{ModifierId, ScorexLogging} import org.ergoplatform.network.peer.PeersStatus import java.net.URL @@ -38,6 +38,7 @@ class ErgoStatsCollector(readersHolder: ActorRef, readersHolder ! GetReaders context.system.eventStream.subscribe(self, classOf[ChangedHistory]) + context.system.eventStream.subscribe(self, classOf[NewBestInputBlock]) context.system.eventStream.subscribe(self, classOf[ChangedState]) context.system.eventStream.subscribe(self, classOf[ChangedMempool]) context.system.eventStream.subscribe(self, classOf[FullBlockApplied]) @@ -60,6 +61,7 @@ class ErgoStatsCollector(readersHolder: ActorRef, None, None, None, + None, launchTime = System.currentTimeMillis(), lastIncomingMessageTime = System.currentTimeMillis(), None, @@ -116,11 +118,20 @@ class ErgoStatsCollector(readersHolder: ActorRef, nodeInfo = nodeInfo.copy(genesisBlockIdOpt = h.headerIdsAtHeight(GenesisHeight).headOption) } + // clearing best input block id on getting new full block + // todo: better to send signal NewBestInputBlock(None) on new best full block + if(nodeInfo.bestFullBlockOpt.map(_.id).getOrElse("") != h.bestFullBlockOpt.map(_.id).getOrElse("")){ + nodeInfo = nodeInfo.copy(bestInputBlockId = None) + } + nodeInfo = nodeInfo.copy(bestFullBlockOpt = h.bestFullBlockOpt, bestHeaderOpt = h.bestHeaderOpt, headersScore = h.bestHeaderOpt.flatMap(m => h.scoreOf(m.id)), fullBlocksScore = h.bestFullBlockOpt.flatMap(m => h.scoreOf(m.id)) ) + + case NewBestInputBlock(v) => + nodeInfo = nodeInfo.copy(bestInputBlockId = v) } private def onConnectedPeers: Receive = { @@ -187,6 +198,7 @@ object ErgoStatsCollector { stateVersion: Option[String], isMining: Boolean, bestHeaderOpt: Option[Header], + bestInputBlockId: Option[ModifierId], headersScore: Option[BigInt], bestFullBlockOpt: Option[ErgoFullBlock], fullBlocksScore: Option[BigInt], @@ -215,6 +227,7 @@ object ErgoStatsCollector { "bestHeaderId" -> ni.bestHeaderOpt.map(_.encodedId).asJson, "bestFullHeaderId" -> ni.bestFullBlockOpt.map(_.header.encodedId).asJson, "previousFullHeaderId" -> ni.bestFullBlockOpt.map(_.header.parentId).map(Algos.encode).asJson, + "bestInputBlock" -> ni.bestInputBlockId.asJson, "difficulty" -> ni.bestFullBlockOpt.map(_.header.requiredDifficulty).map(difficultyEncoder.apply).asJson, "headersScore" -> ni.headersScore.map(difficultyEncoder.apply).asJson, "fullBlocksScore" -> ni.fullBlocksScore.map(difficultyEncoder.apply).asJson, diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala index da5d301080..44afbb1351 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala @@ -41,7 +41,7 @@ object ErgoNodeViewSynchronizerMessages { trait NodeViewHolderEvent - trait NodeViewChange extends NodeViewHolderEvent + sealed trait NodeViewChange extends NodeViewHolderEvent case class ChangedHistory(reader: ErgoHistoryReader) extends NodeViewChange @@ -51,6 +51,8 @@ object ErgoNodeViewSynchronizerMessages { case class ChangedState(reader: ErgoStateReader) extends NodeViewChange + case class NewBestInputBlock(id: Option[ModifierId]) extends NodeViewChange + /** * Event which is published when rollback happened (on finding a better chain) * diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 9aa2fab6b4..b7358ce6b5 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -306,7 +306,12 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti // input blocks related logic // process input block got from p2p network case ProcessInputBlock(sbi) => - history().applyInputBlock(sbi) + val bestInputBlock = history().applyInputBlock(sbi) + // todo: publish after checking transactions + // todo: send NewBestInputBlock(None) on new full block + if (bestInputBlock) { + context.system.eventStream.publish(NewBestInputBlock(Some(sbi.id))) + } case ProcessInputBlockTransactions(std) => history().applyInputBlockTransactions(std.inputBlockID, std.transactions) @@ -689,7 +694,11 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti case LocallyGeneratedInputBlock(subblockInfo, subBlockTransactionsData) => log.info(s"Got locally generated input block ${subblockInfo.header.id}") - history().applyInputBlock(subblockInfo) + val bestInputBlock = history().applyInputBlock(subblockInfo) + // todo: publish after checking transactions + if (bestInputBlock) { + context.system.eventStream.publish(NewBestInputBlock(Some(subblockInfo.id))) + } history().applyInputBlockTransactions(subblockInfo.id, subBlockTransactionsData.transactions) // todo: finish processing } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index d704c77e3b..b00463d7a6 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -37,6 +37,7 @@ trait InputBlocksProcessor extends ScorexLogging { // transactions generated AFTER an ordering block // block header (ordering block) -> transaction ids + // so transaction ids do belong to transactions in input blocks since the block (header) val orderingBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() /** @@ -79,8 +80,11 @@ trait InputBlocksProcessor extends ScorexLogging { prune() } - // sub-blocks related logic - def applyInputBlock(ib: InputBlockInfo): Unit = { + /** + * Update input block related structures with a new input block got from a local miner or p2p network + * @return true if provided input block is a new best input block + */ + def applyInputBlock(ib: InputBlockInfo): Boolean = { // new ordering block arrived ( should be processed outside ? ) if (ib.header.height > _bestInputBlock.map(_.header.height).getOrElse(-1)) { resetState() @@ -97,12 +101,15 @@ trait InputBlocksProcessor extends ScorexLogging { case None => log.info(s"Applying best input block #: ${ib.header.id}, no parent") _bestInputBlock = Some(ib) + true case Some(maybeParent) if (ibParent.contains(maybeParent.id)) => log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") _bestInputBlock = Some(ib) + true case _ => // todo: switch from one input block chain to another log.info(s"Applying non-best input block #: ${ib.header.id}, parent #: $ibParent") + false } } From 4988a6e9572c081eb5f4786a4175ac7d66871012 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 24 Jan 2025 15:56:43 +0300 Subject: [PATCH 090/109] getInputBlock --- .../org/ergoplatform/subblocks/InputBlockInfo.scala | 2 +- .../modifierprocessors/InputBlocksProcessor.scala | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala b/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala index b255aaa5b1..3dffae43b1 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/subblocks/InputBlockInfo.scala @@ -24,7 +24,7 @@ import scorex.util.serialization.{Reader, Writer} * (as they are coming from extension section, and committed in `subBlock` header via extension * digest) */ -// todo: introduce id +// todo: include prev txs digest and Merkle proof case class InputBlockInfo(version: Byte, header: Header, prevInputBlockId: Option[Array[Byte]], diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index b00463d7a6..df9e28f5f8 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -127,6 +127,12 @@ trait InputBlocksProcessor extends ScorexLogging { } } + // Getters to serve client requests below + + def getInputBlock(sbId: ModifierId): Option[InputBlockInfo] = { + inputBlockRecords.get(sbId) + } + def getInputBlockTransactions(sbId: ModifierId): Option[Seq[ErgoTransaction]] = { // todo: cache input block transactions to avoid recalculating it on every p2p request // todo: optimize the code below @@ -135,6 +141,10 @@ trait InputBlocksProcessor extends ScorexLogging { } } + /** + * @param id ordering block (header) id + * @return transactions included in best input blocks chain since ordering block with identifier `id` + */ def getOrderingBlockTransactions(id: ModifierId): Option[Seq[ErgoTransaction]] = { // todo: cache input block transactions to avoid recalculating it on every input block regeneration? // todo: optimize the code below From 1aaa562c4bd8959d48739c2dbfaa9b499ad50b91 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 25 Jan 2025 10:20:09 +0300 Subject: [PATCH 091/109] improving comments --- .../storage/modifierprocessors/InputBlocksProcessor.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index df9e28f5f8..74fc78b21b 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -26,10 +26,12 @@ trait InputBlocksProcessor extends ScorexLogging { */ var _bestInputBlock: Option[InputBlockInfo] = None - // input block id -> input block index + // todo: storing linking structures + + // input block id -> input block val inputBlockRecords = mutable.Map[ModifierId, InputBlockInfo]() - // input block id -> input block transaction ids index + // input block id -> input block transaction ids val inputBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() // txid -> transaction From 377d6c96194ed93f581252df9886162a46d52af9 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 27 Jan 2025 13:40:12 +0300 Subject: [PATCH 092/109] inputBlockParents stub --- .../modifierprocessors/InputBlocksProcessor.scala | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 74fc78b21b..b754bfb5dd 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -26,11 +26,12 @@ trait InputBlocksProcessor extends ScorexLogging { */ var _bestInputBlock: Option[InputBlockInfo] = None - // todo: storing linking structures - // input block id -> input block val inputBlockRecords = mutable.Map[ModifierId, InputBlockInfo]() + // input block id -> parent input block id (or None if parent is ordering block, and height from ordering block + val inputBlockParents = mutable.Map[ModifierId, (Option[ModifierId], Int)]() + // input block id -> input block transaction ids val inputBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() @@ -69,11 +70,14 @@ trait InputBlocksProcessor extends ScorexLogging { None } } + idsToRemove.foreach { id => log.info(s"Pruning input block # $id") // todo: .debug inputBlockRecords.remove(id) inputBlockTransactions.remove(id) + inputBlockParents.remove(id) } + } // reset sub-blocks structures, should be called on receiving ordering block (or slightly later?) @@ -96,6 +100,11 @@ trait InputBlocksProcessor extends ScorexLogging { val ibParent = ib.prevInputBlockId.map(bytesToId) + // todo: consider the case when parent not available yet + val ibHeight = ibParent.map(parentId => inputBlockParents.get(parentId).map(_._2).getOrElse(0) + 1).getOrElse(1) + + inputBlockParents.put(ib.id, ibParent -> ibHeight) + // todo: currently only one chain of subblocks considered, // todo: in fact there could be multiple trees here (one subblocks tree per header) // todo: split best input header / block @@ -109,7 +118,7 @@ trait InputBlocksProcessor extends ScorexLogging { _bestInputBlock = Some(ib) true case _ => - // todo: switch from one input block chain to another + // todo: switch from one input block chain to another using height in inputBlockParents log.info(s"Applying non-best input block #: ${ib.header.id}, parent #: $ibParent") false } From c01a3d13f8cb97ca29e9fef5da19bc433849549a Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 27 Jan 2025 18:57:26 +0300 Subject: [PATCH 093/109] getBestInputBlocksChain --- .../InputBlocksProcessor.scala | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index b754bfb5dd..fe62944b90 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -7,6 +7,7 @@ import org.ergoplatform.nodeView.history.ErgoHistoryReader import org.ergoplatform.subblocks.InputBlockInfo import scorex.util.{ModifierId, ScorexLogging, bytesToId} +import scala.annotation.tailrec import scala.collection.mutable /** @@ -81,9 +82,11 @@ trait InputBlocksProcessor extends ScorexLogging { } // reset sub-blocks structures, should be called on receiving ordering block (or slightly later?) - private def resetState() = { + private def resetState(doPruning: Boolean) = { _bestInputBlock = None - prune() + if (doPruning) { + prune() + } } /** @@ -93,7 +96,7 @@ trait InputBlocksProcessor extends ScorexLogging { def applyInputBlock(ib: InputBlockInfo): Boolean = { // new ordering block arrived ( should be processed outside ? ) if (ib.header.height > _bestInputBlock.map(_.header.height).getOrElse(-1)) { - resetState() + resetState(false) } inputBlockRecords.put(ib.header.id, ib) @@ -116,6 +119,7 @@ trait InputBlocksProcessor extends ScorexLogging { case Some(maybeParent) if (ibParent.contains(maybeParent.id)) => log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") _bestInputBlock = Some(ib) + println("Best inputs-block chain: " + getBestInputBlocksChain()) true case _ => // todo: switch from one input block chain to another using height in inputBlockParents @@ -140,6 +144,31 @@ trait InputBlocksProcessor extends ScorexLogging { // Getters to serve client requests below + // todo: call on header application + def updateStateWithOrderingBlock(h: Header): Unit = { + if (h.height >= _bestInputBlock.map(_.header.height).getOrElse(0)) { + resetState(true) + } + } + + /** + * @return best known inputs-block chain for the current best-known ordering block + */ + def getBestInputBlocksChain(): Seq[ModifierId] = { + bestInputBlock() match { + case Some(tip) => + @tailrec + def stepBack(acc: Seq[ModifierId], inputId: ModifierId): Seq[ModifierId] = { + inputBlockParents.get(inputId) match { + case Some((Some(parentId), _)) => stepBack(acc :+ parentId, parentId) + case _ => acc + } + } + stepBack(Seq.empty, tip.id) + case None => Seq.empty + } + } + def getInputBlock(sbId: ModifierId): Option[InputBlockInfo] = { inputBlockRecords.get(sbId) } From 9521425267ab1e7d501d58f4c25d136d0b5e392a Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 28 Jan 2025 19:43:00 +0300 Subject: [PATCH 094/109] bestInputBlock / bestInputChain --- .../http/api/BlocksApiRoute.scala | 26 ++++++++++++++++++- .../InputBlocksProcessor.scala | 6 ++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala index 5cb5cc4cb8..dd44259f45 100644 --- a/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/BlocksApiRoute.scala @@ -41,7 +41,10 @@ case class BlocksApiRoute(viewHolderRef: ActorRef, readersHolder: ActorRef, ergo getBlockTransactionsByHeaderIdR ~ getProofForTxR ~ getFullBlockByHeaderIdR ~ - getModifierByIdR + getModifierByIdR ~ + // input block related API + getBestInputBlockR ~ + getBestInputBlocksChainR } private def getHistory: Future[ErgoHistoryReader] = @@ -62,6 +65,27 @@ case class BlocksApiRoute(viewHolderRef: ActorRef, readersHolder: ActorRef, ergo history.headerIdsAt(offset, limit).asJson } + private def getBestInputBlockR = { + (pathPrefix("bestInputBlock") & get) { + ApiResponse(getHistory.map{ h => + val bh = h.bestHeaderOpt.map(_.id) + val bi = h.bestInputBlock().map(_.id) + Json.obj("bestOrdering" -> bh.getOrElse("").asJson, "bestInputBlock" -> bi.getOrElse("").asJson) + }) + } + } + + + private def getBestInputBlocksChainR = { + (pathPrefix("bestInputChain") & get) { + ApiResponse(getHistory.map{ h => + val bh = h.bestHeaderOpt.map(_.id) + val bi = h.bestInputBlocksChain() + Json.obj("bestOrdering" -> bh.getOrElse("").asJson, "bestInputBlocks" -> bi.asJson) + }) + } + } + private def getFullBlockByHeaderId(headerId: ModifierId): Future[Option[ErgoFullBlock]] = getHistory.map { history => history.typedModifierById[Header](headerId).flatMap(history.getFullBlock) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index fe62944b90..6e07e50e7b 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -119,7 +119,7 @@ trait InputBlocksProcessor extends ScorexLogging { case Some(maybeParent) if (ibParent.contains(maybeParent.id)) => log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") _bestInputBlock = Some(ib) - println("Best inputs-block chain: " + getBestInputBlocksChain()) + println("Best inputs-block chain: " + bestInputBlocksChain()) true case _ => // todo: switch from one input block chain to another using height in inputBlockParents @@ -144,7 +144,7 @@ trait InputBlocksProcessor extends ScorexLogging { // Getters to serve client requests below - // todo: call on header application + // todo: call on best header change def updateStateWithOrderingBlock(h: Header): Unit = { if (h.height >= _bestInputBlock.map(_.header.height).getOrElse(0)) { resetState(true) @@ -154,7 +154,7 @@ trait InputBlocksProcessor extends ScorexLogging { /** * @return best known inputs-block chain for the current best-known ordering block */ - def getBestInputBlocksChain(): Seq[ModifierId] = { + def bestInputBlocksChain(): Seq[ModifierId] = { bestInputBlock() match { case Some(tip) => @tailrec From 49acb70d89159218c1e41264553feadaf6f8fc51 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Feb 2025 11:51:16 +0300 Subject: [PATCH 095/109] input blocks application tests plan --- .../InputBlockProcessorSpecification.scala | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala new file mode 100644 index 0000000000..1c1efb87ae --- /dev/null +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -0,0 +1,39 @@ +package org.ergoplatform.nodeView.history + +import org.ergoplatform.utils.ErgoCorePropertyTest + +class InputBlockProcessorSpecification extends ErgoCorePropertyTest { + + property("apply first input block after ordering block") { + + } + + property("apply child input block of best input block") { + + } + + property("apply input block with parent input block not available") { + + } + + property("apply input block with parent ordering block not available") { + + } + + property("apply input block with parent ordering block in the past") { + + } + + property("apply input block with parent ordering block in the past") { + + } + + property("apply input block with non-best parent input block") { + + } + + property("apply new best input block (input blocks chain switch)") { + + } + +} From be23a52bccc37c75aac994bd4c561ddb2771cd94 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Feb 2025 17:17:43 +0300 Subject: [PATCH 096/109] input blocks dag processing wip1 --- .../InputBlocksProcessor.scala | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 6e07e50e7b..f849acea84 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -101,29 +101,35 @@ trait InputBlocksProcessor extends ScorexLogging { inputBlockRecords.put(ib.header.id, ib) - val ibParent = ib.prevInputBlockId.map(bytesToId) + val ibParentOpt = ib.prevInputBlockId.map(bytesToId) - // todo: consider the case when parent not available yet - val ibHeight = ibParent.map(parentId => inputBlockParents.get(parentId).map(_._2).getOrElse(0) + 1).getOrElse(1) - - inputBlockParents.put(ib.id, ibParent -> ibHeight) + // todo: consider the case when parent not available yet, likely a signal to download it should be sent + // todo: and so on receiving parent child data should be updated + val ibDepth = ibParentOpt.map(parentId => inputBlockParents.get(parentId).map(_._2).getOrElse(0) + 1).getOrElse(1) + inputBlockParents.put(ib.id, ibParentOpt -> ibDepth) // todo: currently only one chain of subblocks considered, // todo: in fact there could be multiple trees here (one subblocks tree per header) // todo: split best input header / block _bestInputBlock match { case None => + // todo: check if input block is corresponding to the best header log.info(s"Applying best input block #: ${ib.header.id}, no parent") _bestInputBlock = Some(ib) true - case Some(maybeParent) if (ibParent.contains(maybeParent.id)) => + case Some(maybeParent) if (ibParentOpt.contains(maybeParent.id)) => log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") _bestInputBlock = Some(ib) - println("Best inputs-block chain: " + bestInputBlocksChain()) true case _ => + ibParentOpt match { + case Some(ibParent) => + // child of forked input block + case None => + // first input block since ordering block but another best block exists + } // todo: switch from one input block chain to another using height in inputBlockParents - log.info(s"Applying non-best input block #: ${ib.header.id}, parent #: $ibParent") + log.info(s"Applying non-best input block #: ${ib.header.id}, parent #: $ibParentOpt") false } } From d91fef60f47066d280422a0efe1fb93d4088aa96 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Feb 2025 18:31:14 +0300 Subject: [PATCH 097/109] check for best header for first subblock --- .../modifierprocessors/InputBlocksProcessor.scala | 11 +++++++---- .../history/InputBlockProcessorSpecification.scala | 6 +++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index f849acea84..0eae5b3b13 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -113,10 +113,13 @@ trait InputBlocksProcessor extends ScorexLogging { // todo: split best input header / block _bestInputBlock match { case None => - // todo: check if input block is corresponding to the best header - log.info(s"Applying best input block #: ${ib.header.id}, no parent") - _bestInputBlock = Some(ib) - true + if (ib.header.id == historyReader.bestHeaderOpt.map(_.id).getOrElse("")) { + log.info(s"Applying best input block #: ${ib.header.id}, no parent") + _bestInputBlock = Some(ib) + true + } else { + false + } case Some(maybeParent) if (ibParentOpt.contains(maybeParent.id)) => log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") _bestInputBlock = Some(ib) diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index 1c1efb87ae..3dc9a3ab42 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -32,7 +32,11 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { } - property("apply new best input block (input blocks chain switch)") { + property("apply new best input block (input blocks chain switch) - same ordering block") { + + } + + property("apply new best input block on another ordering block") { } From c19cc2e12c97eea0eb398edc05138452339f7984 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 6 Feb 2025 13:44:46 +0300 Subject: [PATCH 098/109] handling missing parent wip1 --- .../nodeView/ErgoNodeViewHolder.scala | 12 ++++- .../InputBlocksProcessor.scala | 51 +++++++++++++------ .../InputBlockProcessorSpecification.scala | 2 +- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index b7358ce6b5..81a785d34e 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -306,12 +306,16 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti // input blocks related logic // process input block got from p2p network case ProcessInputBlock(sbi) => - val bestInputBlock = history().applyInputBlock(sbi) + val (bestInputBlock, toDownloadOpt) = history().applyInputBlock(sbi) // todo: publish after checking transactions // todo: send NewBestInputBlock(None) on new full block if (bestInputBlock) { context.system.eventStream.publish(NewBestInputBlock(Some(sbi.id))) } + toDownloadOpt.foreach { inputId => + // todo: download input block + } + case ProcessInputBlockTransactions(std) => history().applyInputBlockTransactions(std.inputBlockID, std.transactions) @@ -694,12 +698,16 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti case LocallyGeneratedInputBlock(subblockInfo, subBlockTransactionsData) => log.info(s"Got locally generated input block ${subblockInfo.header.id}") - val bestInputBlock = history().applyInputBlock(subblockInfo) + val (bestInputBlock, toDownloadOpt) = history().applyInputBlock(subblockInfo) // todo: publish after checking transactions if (bestInputBlock) { context.system.eventStream.publish(NewBestInputBlock(Some(subblockInfo.id))) } history().applyInputBlockTransactions(subblockInfo.id, subBlockTransactionsData.transactions) + + toDownloadOpt.foreach { mId => + // todo: download input block + } // todo: finish processing } diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 0eae5b3b13..ca070d265b 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -44,6 +44,8 @@ trait InputBlocksProcessor extends ScorexLogging { // so transaction ids do belong to transactions in input blocks since the block (header) val orderingBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() + val waitingForInputBlocks = mutable.Set[ModifierId]() + /** * @return best ordering and input blocks */ @@ -59,7 +61,7 @@ trait InputBlocksProcessor extends ScorexLogging { private def bestInputBlockHeight: Option[Height] = _bestInputBlock.map(_.header.height) - private def prune() = { + private def prune(): Unit = { val BlocksThreshold = 2 // we remove input-blocks data after 2 ordering blocks val bestHeight = bestInputBlockHeight.getOrElse(0) @@ -73,8 +75,12 @@ trait InputBlocksProcessor extends ScorexLogging { } idsToRemove.foreach { id => - log.info(s"Pruning input block # $id") // todo: .debug - inputBlockRecords.remove(id) + log.info(s"Pruning input block # $id") // todo: switch to .debug + inputBlockRecords.remove(id).foreach { ibi => + ibi.prevInputBlockId.foreach { parentId => + waitingForInputBlocks.remove(bytesToId(parentId)) + } + } inputBlockTransactions.remove(id) inputBlockParents.remove(id) } @@ -91,9 +97,11 @@ trait InputBlocksProcessor extends ScorexLogging { /** * Update input block related structures with a new input block got from a local miner or p2p network - * @return true if provided input block is a new best input block + * @return true if provided input block is a new best input block, + * and also optionally id of another input block to download */ - def applyInputBlock(ib: InputBlockInfo): Boolean = { + def applyInputBlock(ib: InputBlockInfo): (Boolean, Option[ModifierId])= { + // new ordering block arrived ( should be processed outside ? ) if (ib.header.height > _bestInputBlock.map(_.header.height).getOrElse(-1)) { resetState(false) @@ -103,10 +111,21 @@ trait InputBlocksProcessor extends ScorexLogging { val ibParentOpt = ib.prevInputBlockId.map(bytesToId) - // todo: consider the case when parent not available yet, likely a signal to download it should be sent - // todo: and so on receiving parent child data should be updated - val ibDepth = ibParentOpt.map(parentId => inputBlockParents.get(parentId).map(_._2).getOrElse(0) + 1).getOrElse(1) - inputBlockParents.put(ib.id, ibParentOpt -> ibDepth) + ibParentOpt.flatMap(parentId => inputBlockParents.get(parentId)) match { + case Some((_, parentDepth)) => + val selfDepth = parentDepth + 1 + inputBlockParents.put(ib.id, ibParentOpt -> selfDepth) + case None if ibParentOpt.isDefined => // parent exists but not known yet, download it + waitingForInputBlocks.add(ibParentOpt.get) + return (false, ibParentOpt) + } + + if (waitingForInputBlocks.contains(ib.id)) { + // reapply children + + return (false, None) + } + // todo: currently only one chain of subblocks considered, // todo: in fact there could be multiple trees here (one subblocks tree per header) @@ -116,24 +135,26 @@ trait InputBlocksProcessor extends ScorexLogging { if (ib.header.id == historyReader.bestHeaderOpt.map(_.id).getOrElse("")) { log.info(s"Applying best input block #: ${ib.header.id}, no parent") _bestInputBlock = Some(ib) - true + (true, None) } else { - false + (false, None) } case Some(maybeParent) if (ibParentOpt.contains(maybeParent.id)) => log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") _bestInputBlock = Some(ib) - true + (true, None) case _ => ibParentOpt match { case Some(ibParent) => // child of forked input block + log.info(s"Applying forked input block #: ${ib.header.id}, with parent $ibParent") + // todo: forks switching etc + (false, None) case None => // first input block since ordering block but another best block exists + log.info(s"Applying forked input block #: ${ib.header.id}, with no parent") + (false, None) } - // todo: switch from one input block chain to another using height in inputBlockParents - log.info(s"Applying non-best input block #: ${ib.header.id}, parent #: $ibParentOpt") - false } } diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index 3dc9a3ab42..f1a7ec8912 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -12,7 +12,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { } - property("apply input block with parent input block not available") { + property("apply input block with parent input block not available (out of order application)") { } From fa22cb849e9a58a988ba1ae0e079892301505031 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 7 Feb 2025 14:08:54 +0300 Subject: [PATCH 099/109] first test in InputBlockProcessorSpecification, some fixes in ib application --- .../InputBlocksProcessor.scala | 13 +++++++------ .../InputBlockProcessorSpecification.scala | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index ca070d265b..15a296669f 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -115,15 +115,16 @@ trait InputBlocksProcessor extends ScorexLogging { case Some((_, parentDepth)) => val selfDepth = parentDepth + 1 inputBlockParents.put(ib.id, ibParentOpt -> selfDepth) + + if (waitingForInputBlocks.contains(ib.id)) { + // todo: fix children's depth, check if the chain is connected ? + return (false, None) + } case None if ibParentOpt.isDefined => // parent exists but not known yet, download it waitingForInputBlocks.add(ibParentOpt.get) return (false, ibParentOpt) - } - if (waitingForInputBlocks.contains(ib.id)) { - // reapply children - - return (false, None) + case _ => } @@ -132,7 +133,7 @@ trait InputBlocksProcessor extends ScorexLogging { // todo: split best input header / block _bestInputBlock match { case None => - if (ib.header.id == historyReader.bestHeaderOpt.map(_.id).getOrElse("")) { + if (ib.header.parentId == historyReader.bestHeaderOpt.map(_.id).getOrElse("")) { log.info(s"Applying best input block #: ${ib.header.id}, no parent") _bestInputBlock = Some(ib) (true, None) diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index f1a7ec8912..0a9aed0dde 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -1,11 +1,23 @@ package org.ergoplatform.nodeView.history +import org.ergoplatform.nodeView.state.StateType +import org.ergoplatform.subblocks.InputBlockInfo import org.ergoplatform.utils.ErgoCorePropertyTest +import org.ergoplatform.utils.HistoryTestHelpers.generateHistory +import org.ergoplatform.utils.generators.ChainGenerator.{applyChain, genChain} class InputBlockProcessorSpecification extends ErgoCorePropertyTest { property("apply first input block after ordering block") { + val h = generateHistory(verifyTransactions = true, StateType.Utxo, PoPoWBootstrap = false, blocksToKeep = -1, + epochLength = 10000, useLastEpochs = 3, initialDiffOpt = None, None) + val l = 3 + val c = genChain(l, h) + applyChain(h, c.dropRight(1)) + val ib = InputBlockInfo(1, c(2).header, None, transactionsDigest = null, merkleProof = null) + val r = h.applyInputBlock(ib) + r should be (true -> None) } property("apply child input block of best input block") { @@ -24,10 +36,6 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { } - property("apply input block with parent ordering block in the past") { - - } - property("apply input block with non-best parent input block") { } From 8e52469e9e8c5a3b878ed741dc50c078488434c6 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 7 Feb 2025 16:23:49 +0300 Subject: [PATCH 100/109] chain of two input blocks and out of order application tests --- .../InputBlocksProcessor.scala | 3 +- .../InputBlockProcessorSpecification.scala | 59 ++++++++++++++++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 15a296669f..5ef885afdd 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -124,7 +124,8 @@ trait InputBlocksProcessor extends ScorexLogging { waitingForInputBlocks.add(ibParentOpt.get) return (false, ibParentOpt) - case _ => + case None => + inputBlockParents.put(ib.id, None -> 1) } diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index 0a9aed0dde..14fd00a934 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -5,27 +5,72 @@ import org.ergoplatform.subblocks.InputBlockInfo import org.ergoplatform.utils.ErgoCorePropertyTest import org.ergoplatform.utils.HistoryTestHelpers.generateHistory import org.ergoplatform.utils.generators.ChainGenerator.{applyChain, genChain} +import scorex.util.idToBytes class InputBlockProcessorSpecification extends ErgoCorePropertyTest { property("apply first input block after ordering block") { val h = generateHistory(verifyTransactions = true, StateType.Utxo, PoPoWBootstrap = false, blocksToKeep = -1, epochLength = 10000, useLastEpochs = 3, initialDiffOpt = None, None) - val l = 3 - val c = genChain(l, h) - applyChain(h, c.dropRight(1)) - - val ib = InputBlockInfo(1, c(2).header, None, transactionsDigest = null, merkleProof = null) + val c1 = genChain(2, h) + applyChain(h, c1) + h.bestFullBlockOpt.get.id shouldBe c1.last.id + + val c2 = genChain(2, h).tail + val ib = InputBlockInfo(1, c2(0).header, None, transactionsDigest = null, merkleProof = null) val r = h.applyInputBlock(ib) r should be (true -> None) } property("apply child input block of best input block") { - + val h = generateHistory(verifyTransactions = true, StateType.Utxo, PoPoWBootstrap = false, blocksToKeep = -1, + epochLength = 10000, useLastEpochs = 3, initialDiffOpt = None, None) + val c1 = genChain(height = 2, history = h).toList + applyChain(h, c1) + + val c2 = genChain(2, h).tail + c2.head.header.parentId shouldBe h.bestHeaderOpt.get.id + h.bestFullBlockOpt.get.id shouldBe c1.last.id + + val ib1 = InputBlockInfo(1, c2(0).header, None, transactionsDigest = null, merkleProof = null) + val r1 = h.applyInputBlock(ib1) + r1 should be (true -> None) + + val c3 = genChain(height = 2, history = h).tail + c3.head.header.parentId shouldBe h.bestHeaderOpt.get.id + h.bestFullBlockOpt.get.id shouldBe c1.last.id + + val ib2 = InputBlockInfo(1, c3(0).header, Some(idToBytes(ib1.id)), transactionsDigest = null, merkleProof = null) + val r = h.applyInputBlock(ib2) + r should be (true -> None) } property("apply input block with parent input block not available (out of order application)") { - + val h = generateHistory(verifyTransactions = true, StateType.Utxo, PoPoWBootstrap = false, blocksToKeep = -1, + epochLength = 10000, useLastEpochs = 3, initialDiffOpt = None, None) + val c1 = genChain(height = 2, history = h).toList + applyChain(h, c1) + + val c2 = genChain(2, h).tail + c2.head.header.parentId shouldBe h.bestHeaderOpt.get.id + h.bestFullBlockOpt.get.id shouldBe c1.last.id + + // Generate parent and child input blocks + val parentIb = InputBlockInfo(1, c2(0).header, None, transactionsDigest = null, merkleProof = null) + val c3 = genChain(2, h).tail + val childIb = InputBlockInfo(1, c3(0).header, Some(idToBytes(parentIb.id)), transactionsDigest = null, merkleProof = null) + + // Apply child first - should fail and return parent id as needed + val r1 = h.applyInputBlock(childIb) + r1 should be (false -> Some(parentIb.id)) + + // Now apply parent - should succeed + val r2 = h.applyInputBlock(parentIb) + r2 should be (true -> None) + + // Apply child again - should now succeed as parent is available + val r3 = h.applyInputBlock(childIb) + r3 should be (true -> None) } property("apply input block with parent ordering block not available") { From aadcd2b35115176827d85bb2b43c904927cbcb22 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sun, 9 Feb 2025 00:07:10 +0300 Subject: [PATCH 101/109] deliveryWaitlist / disconnectedWaitlist --- .../InputBlocksProcessor.scala | 54 +++++++++++++------ .../InputBlockProcessorSpecification.scala | 1 + 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 5ef885afdd..fe2259b69b 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -44,7 +44,10 @@ trait InputBlocksProcessor extends ScorexLogging { // so transaction ids do belong to transactions in input blocks since the block (header) val orderingBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() - val waitingForInputBlocks = mutable.Set[ModifierId]() + // waiting list for input blocks for which we got children but the parent not delivered yet + val deliveryWaitlist = mutable.Set[ModifierId]() + + val disconnectedWaitlist = mutable.Set[InputBlockInfo]() /** * @return best ordering and input blocks @@ -78,8 +81,9 @@ trait InputBlocksProcessor extends ScorexLogging { log.info(s"Pruning input block # $id") // todo: switch to .debug inputBlockRecords.remove(id).foreach { ibi => ibi.prevInputBlockId.foreach { parentId => - waitingForInputBlocks.remove(bytesToId(parentId)) + deliveryWaitlist.remove(bytesToId(parentId)) } + disconnectedWaitlist.remove(ibi) } inputBlockTransactions.remove(id) inputBlockParents.remove(id) @@ -116,12 +120,28 @@ trait InputBlocksProcessor extends ScorexLogging { val selfDepth = parentDepth + 1 inputBlockParents.put(ib.id, ibParentOpt -> selfDepth) - if (waitingForInputBlocks.contains(ib.id)) { - // todo: fix children's depth, check if the chain is connected ? - return (false, None) + if (deliveryWaitlist.contains(ib.id)) { + // Add children from disconnectedWaitlist recursively + + def addChildren(parentId: ModifierId, parentDepth: Int): Unit = { + val children = disconnectedWaitlist.filter(childIb => + childIb.prevInputBlockId.exists(pid => bytesToId(pid) == parentId) + ) + + children.foreach { childIb => + val childDepth = parentDepth + 1 + inputBlockParents.put(childIb.id, Some(parentId) -> childDepth) + disconnectedWaitlist.remove(childIb) + addChildren(childIb.id, childDepth) + } + } + + // fix linking structure + addChildren(ib.id, selfDepth) } case None if ibParentOpt.isDefined => // parent exists but not known yet, download it - waitingForInputBlocks.add(ibParentOpt.get) + deliveryWaitlist.add(ibParentOpt.get) + disconnectedWaitlist.add(ib) return (false, ibParentOpt) case None => @@ -183,6 +203,17 @@ trait InputBlocksProcessor extends ScorexLogging { } } + def bestInputBlock(): Option[InputBlockInfo] = { + _bestInputBlock.flatMap { bib => + // todo: check header id? best input block can be child of non-best ordering header + if (bib.header.height == historyReader.headersHeight + 1) { + Some(bib) + } else { + None + } + } + } + /** * @return best known inputs-block chain for the current best-known ordering block */ @@ -225,15 +256,4 @@ trait InputBlocksProcessor extends ScorexLogging { } } - def bestInputBlock(): Option[InputBlockInfo] = { - _bestInputBlock.flatMap { bib => - // todo: check header id? best input block can be child of non-best ordering header - if (bib.header.height == historyReader.headersHeight + 1) { - Some(bib) - } else { - None - } - } - } - } diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index 14fd00a934..730903756d 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -64,6 +64,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val r1 = h.applyInputBlock(childIb) r1 should be (false -> Some(parentIb.id)) + // todo: should not be true, return sequence of blocks to apply ? // Now apply parent - should succeed val r2 = h.applyInputBlock(parentIb) r2 should be (true -> None) From 47d1ef96c5b956e00d2178ad7bcb0ca3f43bc46b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 10 Feb 2025 14:27:32 +0300 Subject: [PATCH 102/109] moving best input block update to applyInputBlockTransactions --- .../nodeView/ErgoNodeViewHolder.scala | 26 +++++++------- .../InputBlocksProcessor.scala | 36 +++++++++++-------- .../InputBlockProcessorSpecification.scala | 17 +++++---- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 81a785d34e..e210460869 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -306,19 +306,19 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti // input blocks related logic // process input block got from p2p network case ProcessInputBlock(sbi) => - val (bestInputBlock, toDownloadOpt) = history().applyInputBlock(sbi) - // todo: publish after checking transactions - // todo: send NewBestInputBlock(None) on new full block - if (bestInputBlock) { - context.system.eventStream.publish(NewBestInputBlock(Some(sbi.id))) - } + val toDownloadOpt = history().applyInputBlock(sbi) toDownloadOpt.foreach { inputId => // todo: download input block } case ProcessInputBlockTransactions(std) => - history().applyInputBlockTransactions(std.inputBlockID, std.transactions) + val newBestInputBlocks = history().applyInputBlockTransactions(std.inputBlockID, std.transactions) + // todo: publish after checking transactions + // todo: send NewBestInputBlock(None) on new full block + newBestInputBlocks.foreach { id => + context.system.eventStream.publish(NewBestInputBlock(Some(id))) + } } /** @@ -698,17 +698,15 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti case LocallyGeneratedInputBlock(subblockInfo, subBlockTransactionsData) => log.info(s"Got locally generated input block ${subblockInfo.header.id}") - val (bestInputBlock, toDownloadOpt) = history().applyInputBlock(subblockInfo) - // todo: publish after checking transactions - if (bestInputBlock) { - context.system.eventStream.publish(NewBestInputBlock(Some(subblockInfo.id))) - } - history().applyInputBlockTransactions(subblockInfo.id, subBlockTransactionsData.transactions) + val toDownloadOpt = history().applyInputBlock(subblockInfo) + val newBestInputBlocks = history().applyInputBlockTransactions(subblockInfo.id, subBlockTransactionsData.transactions) toDownloadOpt.foreach { mId => // todo: download input block } - // todo: finish processing + newBestInputBlocks.foreach { id => + context.system.eventStream.publish(NewBestInputBlock(Some(id))) + } } protected def getCurrentInfo: Receive = { diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index fe2259b69b..7228e83524 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -104,7 +104,7 @@ trait InputBlocksProcessor extends ScorexLogging { * @return true if provided input block is a new best input block, * and also optionally id of another input block to download */ - def applyInputBlock(ib: InputBlockInfo): (Boolean, Option[ModifierId])= { + def applyInputBlock(ib: InputBlockInfo): Option[ModifierId] = { // new ordering block arrived ( should be processed outside ? ) if (ib.header.height > _bestInputBlock.map(_.header.height).getOrElse(-1)) { @@ -121,8 +121,8 @@ trait InputBlocksProcessor extends ScorexLogging { inputBlockParents.put(ib.id, ibParentOpt -> selfDepth) if (deliveryWaitlist.contains(ib.id)) { - // Add children from disconnectedWaitlist recursively + // Add children from linking structures recursively def addChildren(parentId: ModifierId, parentDepth: Int): Unit = { val children = disconnectedWaitlist.filter(childIb => childIb.prevInputBlockId.exists(pid => bytesToId(pid) == parentId) @@ -139,51 +139,56 @@ trait InputBlocksProcessor extends ScorexLogging { // fix linking structure addChildren(ib.id, selfDepth) } + None case None if ibParentOpt.isDefined => // parent exists but not known yet, download it deliveryWaitlist.add(ibParentOpt.get) disconnectedWaitlist.add(ib) - return (false, ibParentOpt) + ibParentOpt case None => inputBlockParents.put(ib.id, None -> 1) + None } + } - + def applyInputBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Seq[ModifierId] = { + log.info(s"Applying input block transactions for ${sbId} , transactions: ${transactions.size}") + val transactionIds = transactions.map(_.id) + inputBlockTransactions.put(sbId, transactionIds) // todo: currently only one chain of subblocks considered, // todo: in fact there could be multiple trees here (one subblocks tree per header) // todo: split best input header / block - _bestInputBlock match { + + val ib = inputBlockRecords.get(sbId).get // todo: .get + val ibParentOpt = ib.prevInputBlockId.map(bytesToId) + + val res: Seq[ModifierId] = _bestInputBlock match { case None => if (ib.header.parentId == historyReader.bestHeaderOpt.map(_.id).getOrElse("")) { log.info(s"Applying best input block #: ${ib.header.id}, no parent") _bestInputBlock = Some(ib) - (true, None) + Seq(sbId) } else { - (false, None) + Seq.empty } case Some(maybeParent) if (ibParentOpt.contains(maybeParent.id)) => log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") _bestInputBlock = Some(ib) - (true, None) + Seq(sbId) case _ => ibParentOpt match { case Some(ibParent) => // child of forked input block log.info(s"Applying forked input block #: ${ib.header.id}, with parent $ibParent") // todo: forks switching etc - (false, None) + Seq.empty case None => // first input block since ordering block but another best block exists log.info(s"Applying forked input block #: ${ib.header.id}, with no parent") - (false, None) + Seq.empty } } - } - def applyInputBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Unit = { - log.info(s"Applying input block transactions for ${sbId} , transactions: ${transactions.size}") - val transactionIds = transactions.map(_.id) - inputBlockTransactions.put(sbId, transactionIds) if (sbId == _bestInputBlock.map(_.id).getOrElse("")) { val orderingBlockId = _bestInputBlock.get.header.id val curr = orderingBlockTransactions.getOrElse(orderingBlockId, Seq.empty) @@ -192,6 +197,7 @@ trait InputBlocksProcessor extends ScorexLogging { transactions.foreach { tx => transactionsCache.put(tx.id, tx) } + res } // Getters to serve client requests below diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index 730903756d..0ca31f91ea 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -19,7 +19,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val c2 = genChain(2, h).tail val ib = InputBlockInfo(1, c2(0).header, None, transactionsDigest = null, merkleProof = null) val r = h.applyInputBlock(ib) - r should be (true -> None) + r shouldBe None } property("apply child input block of best input block") { @@ -34,7 +34,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val ib1 = InputBlockInfo(1, c2(0).header, None, transactionsDigest = null, merkleProof = null) val r1 = h.applyInputBlock(ib1) - r1 should be (true -> None) + r1 shouldBe None val c3 = genChain(height = 2, history = h).tail c3.head.header.parentId shouldBe h.bestHeaderOpt.get.id @@ -42,7 +42,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val ib2 = InputBlockInfo(1, c3(0).header, Some(idToBytes(ib1.id)), transactionsDigest = null, merkleProof = null) val r = h.applyInputBlock(ib2) - r should be (true -> None) + r shouldBe None } property("apply input block with parent input block not available (out of order application)") { @@ -60,18 +60,17 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val c3 = genChain(2, h).tail val childIb = InputBlockInfo(1, c3(0).header, Some(idToBytes(parentIb.id)), transactionsDigest = null, merkleProof = null) - // Apply child first - should fail and return parent id as needed + // Apply child first - should return parent id as needed val r1 = h.applyInputBlock(childIb) - r1 should be (false -> Some(parentIb.id)) + r1 shouldBe Some(parentIb.id) - // todo: should not be true, return sequence of blocks to apply ? - // Now apply parent - should succeed + // Now apply parent val r2 = h.applyInputBlock(parentIb) - r2 should be (true -> None) + r2 shouldBe None // Apply child again - should now succeed as parent is available val r3 = h.applyInputBlock(childIb) - r3 should be (true -> None) + r3 shouldBe None } property("apply input block with parent ordering block not available") { From 517e8fafe721ae385b507558b23e5070b38d9923 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 12 Feb 2025 23:21:10 +0300 Subject: [PATCH 103/109] applyInputBlockTransactions scaladoc and fixes --- .../modifierprocessors/InputBlocksProcessor.scala | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 7228e83524..410997f6ee 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -151,6 +151,9 @@ trait InputBlocksProcessor extends ScorexLogging { } } + /** + * @return - sequence of new best input blocks + */ def applyInputBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Seq[ModifierId] = { log.info(s"Applying input block transactions for ${sbId} , transactions: ${transactions.size}") val transactionIds = transactions.map(_.id) @@ -159,7 +162,12 @@ trait InputBlocksProcessor extends ScorexLogging { // todo: in fact there could be multiple trees here (one subblocks tree per header) // todo: split best input header / block - val ib = inputBlockRecords.get(sbId).get // todo: .get + if (!inputBlockRecords.contains(sbId)) { + log.warn(s"Input block transactions delivered for not known input block $sbId") + return Seq.empty + } + + val ib = inputBlockRecords.apply(sbId) val ibParentOpt = ib.prevInputBlockId.map(bytesToId) val res: Seq[ModifierId] = _bestInputBlock match { @@ -200,8 +208,6 @@ trait InputBlocksProcessor extends ScorexLogging { res } - // Getters to serve client requests below - // todo: call on best header change def updateStateWithOrderingBlock(h: Header): Unit = { if (h.height >= _bestInputBlock.map(_.header.height).getOrElse(0)) { @@ -209,6 +215,8 @@ trait InputBlocksProcessor extends ScorexLogging { } } + // Getters to serve client requests below + def bestInputBlock(): Option[InputBlockInfo] = { _bestInputBlock.flatMap { bib => // todo: check header id? best input block can be child of non-best ordering header From f436de2a6df3e3b0d24ea63517cedb8dcd429a5d Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 13 Feb 2025 13:10:24 +0300 Subject: [PATCH 104/109] -Xasync to solve idea compilation issues, processBestInputBlockCandidate helper --- build.sbt | 1 + .../InputBlocksProcessor.scala | 80 ++++++++++--------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/build.sbt b/build.sbt index 38fcb0fd47..c1044082d6 100644 --- a/build.sbt +++ b/build.sbt @@ -96,6 +96,7 @@ val opts = Seq( ) javaOptions in run ++= opts +scalacOptions ++= Seq("-Xasync") scalacOptions --= Seq("-Ywarn-numeric-widen", "-Ywarn-value-discard", "-Ywarn-unused:params", "-Xcheckinit") val scalacOpts = Seq("-Ywarn-numeric-widen", "-Ywarn-value-discard", "-Ywarn-unused:params", "-Xcheckinit") diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 410997f6ee..0ca407d3b9 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -154,7 +154,8 @@ trait InputBlocksProcessor extends ScorexLogging { /** * @return - sequence of new best input blocks */ - def applyInputBlockTransactions(sbId: ModifierId, transactions: Seq[ErgoTransaction]): Seq[ModifierId] = { + def applyInputBlockTransactions(sbId: ModifierId, + transactions: Seq[ErgoTransaction]): Seq[ModifierId] = { log.info(s"Applying input block transactions for ${sbId} , transactions: ${transactions.size}") val transactionIds = transactions.map(_.id) inputBlockTransactions.put(sbId, transactionIds) @@ -167,45 +168,52 @@ trait InputBlocksProcessor extends ScorexLogging { return Seq.empty } - val ib = inputBlockRecords.apply(sbId) - val ibParentOpt = ib.prevInputBlockId.map(bytesToId) + // put transactions into cache shared among all the input blocks, + // to avoid data duplication in input block related functions + transactions.foreach { tx => + transactionsCache.put(tx.id, tx) + } - val res: Seq[ModifierId] = _bestInputBlock match { - case None => - if (ib.header.parentId == historyReader.bestHeaderOpt.map(_.id).getOrElse("")) { - log.info(s"Applying best input block #: ${ib.header.id}, no parent") - _bestInputBlock = Some(ib) - Seq(sbId) - } else { - Seq.empty - } - case Some(maybeParent) if (ibParentOpt.contains(maybeParent.id)) => - log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") - _bestInputBlock = Some(ib) - Seq(sbId) - case _ => - ibParentOpt match { - case Some(ibParent) => - // child of forked input block - log.info(s"Applying forked input block #: ${ib.header.id}, with parent $ibParent") - // todo: forks switching etc - Seq.empty - case None => - // first input block since ordering block but another best block exists - log.info(s"Applying forked input block #: ${ib.header.id}, with no parent") + def processBestInputBlockCandidate(blockId: ModifierId): Seq[ModifierId] = { + val ib = inputBlockRecords.apply(blockId) + val ibParentOpt = ib.prevInputBlockId.map(bytesToId) + + val res: Seq[ModifierId] = _bestInputBlock match { + case None => + if (ib.header.parentId == historyReader.bestHeaderOpt.map(_.id).getOrElse("")) { + log.info(s"Applying best input block #: ${ib.header.id}, no parent") + _bestInputBlock = Some(ib) + Seq(blockId) + } else { Seq.empty - } - } + } + case Some(maybeParent) if (ibParentOpt.contains(maybeParent.id)) => + log.info(s"Applying best input block #: ${ib.id} @ height ${ib.header.height}, header is ${ib.header.id}, parent is ${maybeParent.id}") + _bestInputBlock = Some(ib) + Seq(blockId) + case _ => + ibParentOpt match { + case Some(ibParent) => + // child of forked input block + log.info(s"Applying forked input block #: ${ib.header.id}, with parent $ibParent") + // todo: forks switching etc + Seq.empty + case None => + // first input block since ordering block but another best block exists + log.info(s"Applying forked input block #: ${ib.header.id}, with no parent") + Seq.empty + } + } - if (sbId == _bestInputBlock.map(_.id).getOrElse("")) { - val orderingBlockId = _bestInputBlock.get.header.id - val curr = orderingBlockTransactions.getOrElse(orderingBlockId, Seq.empty) - orderingBlockTransactions.put(orderingBlockId, curr ++ transactionIds) - } - transactions.foreach { tx => - transactionsCache.put(tx.id, tx) + if (res.headOption.getOrElse("0") == _bestInputBlock.map(_.id).getOrElse("1")) { + val orderingBlockId = _bestInputBlock.get.header.id + val curr = orderingBlockTransactions.getOrElse(orderingBlockId, Seq.empty) + orderingBlockTransactions.put(orderingBlockId, curr ++ transactionIds) + } + res } - res + + processBestInputBlockCandidate(sbId) } // todo: call on best header change From 9e2a6ddc536d6dc414d4cf3d77b19d9fe72b01c8 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 13 Feb 2025 17:28:39 +0300 Subject: [PATCH 105/109] bestHeight, bestTips to find best input block chains tips efficiently --- .../InputBlocksProcessor.scala | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 0ca407d3b9..589625058a 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -37,8 +37,15 @@ trait InputBlocksProcessor extends ScorexLogging { val inputBlockTransactions = mutable.Map[ModifierId, Seq[ModifierId]]() // txid -> transaction + // todo: improve removing, some txs included in forked input blocks may stuck in the cache val transactionsCache = mutable.Map[ModifierId, ErgoTransaction]() + // ordering block id -> best known input block chain tips + val bestTips = mutable.Map[ModifierId, mutable.Set[ModifierId]]() + + // ordering block id -> best known input block chain height + val bestHeights = mutable.Map[ModifierId, Int]() + // transactions generated AFTER an ordering block // block header (ordering block) -> transaction ids // so transaction ids do belong to transactions in input blocks since the block (header) @@ -68,7 +75,22 @@ trait InputBlocksProcessor extends ScorexLogging { val BlocksThreshold = 2 // we remove input-blocks data after 2 ordering blocks val bestHeight = bestInputBlockHeight.getOrElse(0) - val idsToRemove = inputBlockRecords.flatMap { case (id, ibi) => + + val orderingBlockIdsToRemove = bestHeights.keys.filter { orderingId => + bestHeight > historyReader.heightOf(orderingId).getOrElse(0) + }.toSeq + + orderingBlockIdsToRemove.foreach { id => + bestHeights.remove(id) + bestTips.remove(id) + orderingBlockTransactions.remove(id).map { ids => + ids.foreach { txId => + transactionsCache.remove(txId) + } + } + } + + val inputBlockIdsToRemove = inputBlockRecords.flatMap { case (id, ibi) => val res = (bestHeight - ibi.header.height) > BlocksThreshold if (res) { Some(id) @@ -77,7 +99,7 @@ trait InputBlocksProcessor extends ScorexLogging { } } - idsToRemove.foreach { id => + inputBlockIdsToRemove.foreach { id => log.info(s"Pruning input block # $id") // todo: switch to .debug inputBlockRecords.remove(id).foreach { ibi => ibi.prevInputBlockId.foreach { parentId => @@ -120,6 +142,20 @@ trait InputBlocksProcessor extends ScorexLogging { val selfDepth = parentDepth + 1 inputBlockParents.put(ib.id, ibParentOpt -> selfDepth) + val orderingId = ib.header.id + val tipHeight = bestHeights.getOrElse(orderingId, 0) + + if (selfDepth > tipHeight) { + bestHeights.put(orderingId, selfDepth) + } + + val currentBestTips = bestTips.getOrElse(orderingId, mutable.Set.empty) + + if (selfDepth >= tipHeight || (currentBestTips.size < 3 && tipHeight >= 4 && selfDepth >= tipHeight - 2)) { + val newBestTips = currentBestTips += ib.id + bestTips.put(orderingId, newBestTips) + } + if (deliveryWaitlist.contains(ib.id)) { // Add children from linking structures recursively From e39618a4db6090a690f1d8d953f8ca854da4eeea Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 14 Feb 2025 14:09:43 +0300 Subject: [PATCH 106/109] updateBestTipsAndHeight, check best tips and best tip height in tests --- .../InputBlocksProcessor.scala | 80 +++++++++++-------- .../InputBlockProcessorSpecification.scala | 5 ++ 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 589625058a..d690f49596 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -126,63 +126,59 @@ trait InputBlocksProcessor extends ScorexLogging { * @return true if provided input block is a new best input block, * and also optionally id of another input block to download */ + // todo: use PoEM to store only 2-3 best chains and select best one quickly def applyInputBlock(ib: InputBlockInfo): Option[ModifierId] = { - - // new ordering block arrived ( should be processed outside ? ) if (ib.header.height > _bestInputBlock.map(_.header.height).getOrElse(-1)) { resetState(false) } inputBlockRecords.put(ib.header.id, ib) + val orderingId = ib.header.parentId + def currentBestTips = bestTips.getOrElse(orderingId, mutable.Set.empty) + def tipHeight = bestHeights.getOrElse(orderingId, 0) val ibParentOpt = ib.prevInputBlockId.map(bytesToId) + def updateBestTipsAndHeight(depth: Int): Unit = { + if (depth > tipHeight) { + bestHeights.put(orderingId, depth) + } + if (depth >= tipHeight || (currentBestTips.size < 3 && tipHeight >= 4 && depth >= tipHeight - 2)) { + bestTips.put(orderingId, currentBestTips += ib.id) + } + } + + def addChildren(parentId: ModifierId, parentDepth: Int): Unit = { + val children = disconnectedWaitlist.filter(childIb => + childIb.prevInputBlockId.exists(pid => bytesToId(pid) == parentId) + ) + val childDepth = parentDepth + 1 + updateBestTipsAndHeight(childDepth) + children.foreach { childIb => + inputBlockParents.put(childIb.id, Some(parentId) -> childDepth) + disconnectedWaitlist.remove(childIb) + addChildren(childIb.id, childDepth) + } + } + ibParentOpt.flatMap(parentId => inputBlockParents.get(parentId)) match { case Some((_, parentDepth)) => val selfDepth = parentDepth + 1 inputBlockParents.put(ib.id, ibParentOpt -> selfDepth) - - val orderingId = ib.header.id - val tipHeight = bestHeights.getOrElse(orderingId, 0) - - if (selfDepth > tipHeight) { - bestHeights.put(orderingId, selfDepth) - } - - val currentBestTips = bestTips.getOrElse(orderingId, mutable.Set.empty) - - if (selfDepth >= tipHeight || (currentBestTips.size < 3 && tipHeight >= 4 && selfDepth >= tipHeight - 2)) { - val newBestTips = currentBestTips += ib.id - bestTips.put(orderingId, newBestTips) - } - + updateBestTipsAndHeight(selfDepth) if (deliveryWaitlist.contains(ib.id)) { - - // Add children from linking structures recursively - def addChildren(parentId: ModifierId, parentDepth: Int): Unit = { - val children = disconnectedWaitlist.filter(childIb => - childIb.prevInputBlockId.exists(pid => bytesToId(pid) == parentId) - ) - - children.foreach { childIb => - val childDepth = parentDepth + 1 - inputBlockParents.put(childIb.id, Some(parentId) -> childDepth) - disconnectedWaitlist.remove(childIb) - addChildren(childIb.id, childDepth) - } - } - - // fix linking structure addChildren(ib.id, selfDepth) } None - case None if ibParentOpt.isDefined => // parent exists but not known yet, download it + + case None if ibParentOpt.isDefined => deliveryWaitlist.add(ibParentOpt.get) disconnectedWaitlist.add(ib) ibParentOpt case None => inputBlockParents.put(ib.id, None -> 1) + updateBestTipsAndHeight(1) None } } @@ -302,6 +298,22 @@ trait InputBlocksProcessor extends ScorexLogging { } } + /** + * @param id ordering block (header) id + * @return tips (leaf input blocks) for the ordering block with identifier `id` + */ + def getOrderingBlockTips(id: ModifierId): Option[Set[ModifierId]] = { + bestTips.get(id).map(_.toSet) + } + + /** + * @param id ordering block (header) id + * @return height of the best input block tip for the ordering block with identifier `id` + */ + def getOrderingBlockTipHeight(id: ModifierId): Option[Int] = { + bestHeights.get(id) + } + /** * @param id ordering block (header) id * @return transactions included in best input blocks chain since ordering block with identifier `id` diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index 0ca31f91ea..2865ef439c 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -35,6 +35,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val ib1 = InputBlockInfo(1, c2(0).header, None, transactionsDigest = null, merkleProof = null) val r1 = h.applyInputBlock(ib1) r1 shouldBe None + h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(ib1.id) val c3 = genChain(height = 2, history = h).tail c3.head.header.parentId shouldBe h.bestHeaderOpt.get.id @@ -43,6 +44,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val ib2 = InputBlockInfo(1, c3(0).header, Some(idToBytes(ib1.id)), transactionsDigest = null, merkleProof = null) val r = h.applyInputBlock(ib2) r shouldBe None + h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(ib2.id) } property("apply input block with parent input block not available (out of order application)") { @@ -63,14 +65,17 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { // Apply child first - should return parent id as needed val r1 = h.applyInputBlock(childIb) r1 shouldBe Some(parentIb.id) + h.getOrderingBlockTips(h.bestHeaderOpt.get.id) shouldBe None // Now apply parent val r2 = h.applyInputBlock(parentIb) r2 shouldBe None + h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(parentIb.id) // Apply child again - should now succeed as parent is available val r3 = h.applyInputBlock(childIb) r3 shouldBe None + h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(childIb.id) } property("apply input block with parent ordering block not available") { From 34cad4c70d78cfb926a54da6aac92a31af2aa4a7 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 14 Feb 2025 14:11:26 +0300 Subject: [PATCH 107/109] check tip height in tests --- .../nodeView/history/InputBlockProcessorSpecification.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index 2865ef439c..b8987cb450 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -36,6 +36,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val r1 = h.applyInputBlock(ib1) r1 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(ib1.id) + h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 1 val c3 = genChain(height = 2, history = h).tail c3.head.header.parentId shouldBe h.bestHeaderOpt.get.id @@ -45,6 +46,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val r = h.applyInputBlock(ib2) r shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(ib2.id) + h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 2 } property("apply input block with parent input block not available (out of order application)") { @@ -66,16 +68,19 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { val r1 = h.applyInputBlock(childIb) r1 shouldBe Some(parentIb.id) h.getOrderingBlockTips(h.bestHeaderOpt.get.id) shouldBe None + h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id) shouldBe None // Now apply parent val r2 = h.applyInputBlock(parentIb) r2 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(parentIb.id) + h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 1 // Apply child again - should now succeed as parent is available val r3 = h.applyInputBlock(childIb) r3 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(childIb.id) + h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 2 } property("apply input block with parent ordering block not available") { From c78e2ac519dd0a89e027584d84948afa2d7777c3 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 14 Feb 2025 21:20:21 +0300 Subject: [PATCH 108/109] isAncestor --- .../InputBlocksProcessor.scala | 23 +++++++++++++++++++ .../InputBlockProcessorSpecification.scala | 9 ++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index d690f49596..79c08a3917 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -286,6 +286,29 @@ trait InputBlocksProcessor extends ScorexLogging { } } + /** + * Checks if input block with id `child` has ancestor with id `parent` in its chain + * + * @param child id of potential descendant input block + * @param parent id of potential ancestor input block + * @return true if `parent` is ancestor of `child`, false otherwise + */ + def isAncestor(child: ModifierId, parent: ModifierId): Boolean = { + @tailrec + def loop(current: ModifierId): Boolean = { + if (current == parent) { + true + } else { + inputBlockParents.get(current) match { + case Some((Some(parentId), _)) => loop(parentId) + case _ => false + } + } + } + + if (child == parent) false else loop(child) + } + def getInputBlock(sbId: ModifierId): Option[InputBlockInfo] = { inputBlockRecords.get(sbId) } diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index b8987cb450..e450f853e1 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -37,6 +37,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { r1 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(ib1.id) h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 1 + h.isAncestor(ib1.id, ib1.id) shouldBe false val c3 = genChain(height = 2, history = h).tail c3.head.header.parentId shouldBe h.bestHeaderOpt.get.id @@ -47,6 +48,9 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { r shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(ib2.id) h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 2 + h.isAncestor(ib2.id, ib1.id) shouldBe true + h.isAncestor(ib2.id, ib2.id) shouldBe false + h.isAncestor(ib1.id, ib2.id) shouldBe false } property("apply input block with parent input block not available (out of order application)") { @@ -69,18 +73,23 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { r1 shouldBe Some(parentIb.id) h.getOrderingBlockTips(h.bestHeaderOpt.get.id) shouldBe None h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id) shouldBe None + h.isAncestor(childIb.id, parentIb.id) shouldBe false // Now apply parent val r2 = h.applyInputBlock(parentIb) r2 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(parentIb.id) h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 1 + h.isAncestor(parentIb.id, parentIb.id) shouldBe false // Apply child again - should now succeed as parent is available val r3 = h.applyInputBlock(childIb) r3 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(childIb.id) h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 2 + h.isAncestor(childIb.id, parentIb.id) shouldBe true + h.isAncestor(childIb.id, childIb.id) shouldBe false + h.isAncestor(parentIb.id, childIb.id) shouldBe false } property("apply input block with parent ordering block not available") { From 13663b87e62f136aeb842b6253678ceef4d9aa61 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 14 Feb 2025 22:50:39 +0300 Subject: [PATCH 109/109] isAncestor returns parent's child --- .../InputBlocksProcessor.scala | 25 ++++++++----------- .../InputBlockProcessorSpecification.scala | 18 ++++++------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala index 79c08a3917..ec91e65995 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/InputBlocksProcessor.scala @@ -287,26 +287,23 @@ trait InputBlocksProcessor extends ScorexLogging { } /** - * Checks if input block with id `child` has ancestor with id `parent` in its chain + * Returns parent's immediate child that is an ancestor of the given child block * - * @param child id of potential descendant input block - * @param parent id of potential ancestor input block - * @return true if `parent` is ancestor of `child`, false otherwise + * @param child id of descendant input block + * @param parent id of ancestor input block + * @return Some(parentChild) if found in child's ancestry chain, None otherwise */ - def isAncestor(child: ModifierId, parent: ModifierId): Boolean = { + def isAncestor(child: ModifierId, parent: ModifierId): Option[ModifierId] = { @tailrec - def loop(current: ModifierId): Boolean = { - if (current == parent) { - true - } else { - inputBlockParents.get(current) match { - case Some((Some(parentId), _)) => loop(parentId) - case _ => false - } + def loop(current: ModifierId, lastSeen: ModifierId): Option[ModifierId] = { + inputBlockParents.get(current) match { + case Some((Some(parentId), _)) if parentId == parent => Some(lastSeen) + case Some((Some(parentId), _)) => loop(parentId, current) + case _ => None } } - if (child == parent) false else loop(child) + if (child == parent) None else loop(child, child) } def getInputBlock(sbId: ModifierId): Option[InputBlockInfo] = { diff --git a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala index e450f853e1..5119450fc2 100644 --- a/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/history/InputBlockProcessorSpecification.scala @@ -37,7 +37,7 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { r1 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(ib1.id) h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 1 - h.isAncestor(ib1.id, ib1.id) shouldBe false + h.isAncestor(ib1.id, ib1.id).isEmpty shouldBe true val c3 = genChain(height = 2, history = h).tail c3.head.header.parentId shouldBe h.bestHeaderOpt.get.id @@ -48,9 +48,9 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { r shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(ib2.id) h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 2 - h.isAncestor(ib2.id, ib1.id) shouldBe true - h.isAncestor(ib2.id, ib2.id) shouldBe false - h.isAncestor(ib1.id, ib2.id) shouldBe false + h.isAncestor(ib2.id, ib1.id).contains(ib2.id) shouldBe true + h.isAncestor(ib2.id, ib2.id).isEmpty shouldBe true + h.isAncestor(ib1.id, ib2.id).isEmpty shouldBe true } property("apply input block with parent input block not available (out of order application)") { @@ -73,23 +73,23 @@ class InputBlockProcessorSpecification extends ErgoCorePropertyTest { r1 shouldBe Some(parentIb.id) h.getOrderingBlockTips(h.bestHeaderOpt.get.id) shouldBe None h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id) shouldBe None - h.isAncestor(childIb.id, parentIb.id) shouldBe false + h.isAncestor(childIb.id, parentIb.id).isEmpty shouldBe true // Now apply parent val r2 = h.applyInputBlock(parentIb) r2 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(parentIb.id) h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 1 - h.isAncestor(parentIb.id, parentIb.id) shouldBe false + h.isAncestor(parentIb.id, parentIb.id).isEmpty shouldBe true // Apply child again - should now succeed as parent is available val r3 = h.applyInputBlock(childIb) r3 shouldBe None h.getOrderingBlockTips(h.bestHeaderOpt.get.id).get should contain(childIb.id) h.getOrderingBlockTipHeight(h.bestHeaderOpt.get.id).get shouldBe 2 - h.isAncestor(childIb.id, parentIb.id) shouldBe true - h.isAncestor(childIb.id, childIb.id) shouldBe false - h.isAncestor(parentIb.id, childIb.id) shouldBe false + h.isAncestor(childIb.id, parentIb.id).contains(childIb.id) shouldBe true + h.isAncestor(childIb.id, childIb.id).isEmpty shouldBe true + h.isAncestor(parentIb.id, childIb.id).isEmpty shouldBe true } property("apply input block with parent ordering block not available") {