diff --git a/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala new file mode 100644 index 0000000000..e5109fea80 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/SubBlockAlgos.scala @@ -0,0 +1,231 @@ +package org.ergoplatform + +import org.ergoplatform.mining.AutolykosPowScheme +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 scorex.util.Extensions._ +import scorex.util.serialization.{Reader, Writer} +import scorex.util.{ModifierId, bytesToId, idToBytes} + +import scala.collection.mutable + +/** + * Implementation steps: + * * 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 + * * input blocks support in /mining API + * * sub confirmations API + */ +object SubBlockAlgos { + + // 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. + // * script execution context different for input and ordering blocks for the following fields : + // * timestamp - next input or ordering block has non-decreasing timestamp to ours + // * height - the same for input blocks and next ordering block + // * 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 + + // sub blocks per block, adjustable via miners voting + // 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 + + 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 in header + + // todo: consider 2-for-1 pow technique + if (hit < subTarget) { + InputBlock + } else if (hit >= subTarget && hit < fullTarget) { + FinalizingBlock + } else { + InvalidPoWBlock + } + } + + 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: + // version + // sub block (~200 bytes) - contains a link to parent block + // previous sub block id + // transactions since last sub blocks + + + + /** + * 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 MessageSpecInitial[ModifierId] { + + import scorex.util.{bytesToId, idToBytes} + + 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): ModifierId = { + bytesToId(r.getBytes(Constants.ModifierIdSize)) + } + } + + case class TransactionsSince(transactionsWithBlockIds: Array[(ModifierId, Array[Array[Byte]])]) + + class DataSpec extends MessageSpecInitial[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) + } + } + +} + +object structures { + var lastBlock: Header = null // we ignore forks for now + + // 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 + + + /** + * 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 + */ + def processSubBlock(sbi: SubBlockInfo): (Seq[ModifierId], Seq[ModifierId]) = { + val sbHeader = sbi.subBlock + val prevSbIdOpt = sbi.prevSubBlockId.map(bytesToId) + val sbHeight = sbHeader.height + + def emptyResult: (Seq[ModifierId], Seq[ModifierId]) = Seq.empty -> Seq.empty + + 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 + 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 { + emptyResult // no new transactions + } + } 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, 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): Unit = { + if (header.height > lastBlock.height) { + lastBlock = header + subBlocks.clear() + subBlockLinks.clear() + subBlockTxs = Map.empty + } else { + ??? // todo: process + } + } + +} + 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/http/api/ApiCodecs.scala b/ergo-core/src/main/scala/org/ergoplatform/http/api/ApiCodecs.scala index e27d66153b..e82a8032fd 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/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosPowScheme.scala index 70d90454ec..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,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.{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._ @@ -241,7 +242,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) } @@ -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 NoSolutionFound => NothingFound + case InputSolutionFound(as) => InputBlockHeaderFound(h.toHeader(as)) + case OrderingSolutionFound(as) => OrderingBlockHeaderFound(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 OrderingBlockHeaderFound(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): BlockSolutionSearchResult = { + + 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): BlockSolutionSearchResult = if (i == endNonce) { + NoSolutionFound } 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/AutolykosSolution.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala index 20f3237a10..f7ea1fc5f9 100644 --- a/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala +++ b/ergo-core/src/main/scala/org/ergoplatform/mining/AutolykosSolution.scala @@ -6,6 +6,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 org.ergoplatform.serialization.ErgoSerializer @@ -58,6 +59,31 @@ 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/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala b/ergo-core/src/main/scala/org/ergoplatform/mining/DefaultFakePowScheme.scala index c3375a45c6..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,5 +1,6 @@ package org.ergoplatform.mining +import org.ergoplatform.{OrderingBlockHeaderFound, 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, + 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 cf4ce0637f..2d57c825ab 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,38 @@ 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 OrderingBlockHeaderFound(h: Header) extends ProveBlockResult + +case class InputBlockFound(fb: ErgoFullBlock) extends ProveBlockResult + +case class InputBlockHeaderFound(h: Header) extends ProveBlockResult + +sealed trait BlockSolutionSearchResult + +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 { type PrivateKey = BigInt 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 43c3869ef8..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 @@ -71,6 +71,22 @@ object Extension extends ApiCodecs { */ val ValidationRulesPrefix: Byte = 0x02 + /** + * Prefix for keys related to sub-blocks related data. + */ + 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/ergo-core/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala b/ergo-core/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala index b2b9f1f082..4467036cdc 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,7 +1,7 @@ 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 sigma.data.SigmaConstants.{MaxBoxSize, MaxPropositionBytes} import org.ergoplatform.http.api.ApiCodecs @@ -67,6 +67,8 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input], override lazy val id: ModifierId = bytesToId(serializedId) + lazy val weakId = id.take(SubBlockAlgos.weakTransactionIdLength) + /** * 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, 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/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..c611bc6085 --- /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.MessageSpecSubblocks +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 MessageSpecSubblocks[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/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/SubBlockTransactionsMessageSpec.scala b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsMessageSpec.scala new file mode 100644 index 0000000000..0abb2be62d --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsMessageSpec.scala @@ -0,0 +1,37 @@ +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 SubBlockTransactionsMessageSpec 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) + } + +} 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/SubBlockTransactionsRequestMessageSpec.scala new file mode 100644 index 0000000000..ff9c057fa5 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/network/message/subblocks/SubBlockTransactionsRequestMessageSpec.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 SubBlockTransactionsRequestMessageSpec 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/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 712a185d35..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(pmod: 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..8a554e3f99 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/nodeView/LocallyGeneratedInputBlock.scala @@ -0,0 +1,6 @@ +package org.ergoplatform.nodeView + +import org.ergoplatform.network.message.subblocks.SubBlockTransactionsData +import org.ergoplatform.subblocks.SubBlockInfo + +case class LocallyGeneratedInputBlock(sbi: SubBlockInfo, sbt: SubBlockTransactionsData) 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/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/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( 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..c8e058dc77 --- /dev/null +++ b/ergo-core/src/main/scala/org/ergoplatform/subblocks/SubBlockInfo.scala @@ -0,0 +1,71 @@ +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.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} + +/** + * 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 + * @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, + prevSubBlockId: Option[Array[Byte]], + subblockTransactionsDigest: Digest32, + merkleProof: BatchMerkleProof[Digest32] // Merkle proof for both prevSubBlockId & subblockTransactionsDigest + ) { + + def valid(): Boolean = { + // todo: implement data validity checks + false + } + + def transactionsConfirmedDigest: Digest32 = subBlock.transactionsRoot +} + +object SubBlockInfo { + + 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 = { + w.put(sbi.version) + HeaderSerializer.serialize(sbi.subBlock, w) + w.putOption(sbi.prevSubBlockId){case (w, id) => w.putBytes(id)} + w.putBytes(sbi.subblockTransactionsDigest) + val proof = bmp.serialize(sbi.merkleProof) + w.putUShort(proof.length.toShort) + w.putBytes(proof) + } + + 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 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/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/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/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 } } } diff --git a/papers/subblocks/subblocks-forum.md b/papers/subblocks/subblocks-forum.md new file mode 100644 index 0000000000..69cda824c0 --- /dev/null +++ b/papers/subblocks/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). +* 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/papers/subblocks/subblocks.md b/papers/subblocks/subblocks.md new file mode 100644 index 0000000000..7f12ce65cf --- /dev/null +++ b/papers/subblocks/subblocks.md @@ -0,0 +1,123 @@ +Sub-Blocks and Improved Confirmed Transactions Propagation +========================== + +* Author: kushti +* Status: Proposed +* Created: 31-Oct-2023 +* License: CC0 +* 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 (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 which still could be useful for +a variety of applications. + +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, 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 +a block which is more difficult to find than an ordinary, for example, for a (level-1) superblock *S* we may require +*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. + +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 +--------------------------------------- + +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) 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 +------------------------------------------------- + +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. + + +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. + +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 +------------------ + +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 +--------------------------------------- + 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/http/api/ErgoUtilsApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ErgoUtilsApiRoute.scala index f2d65ca128..25e2d13a05 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 sigma.data.ProveDlog @@ -18,10 +18,7 @@ import java.security.SecureRandom import scala.util.Failure import sigma.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/http/api/MiningApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala index dd645a8dc6..ae40a26a6f 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, RESTApiSettings} @@ -62,6 +62,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/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index b90c98ee9e..5929023ce3 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -13,17 +13,20 @@ 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.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 import org.ergoplatform.wallet.interpreter.ErgoInterpreter -import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, Input} +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} @@ -56,32 +59,27 @@ class CandidateGenerator( readersHolderRef ! GetReaders } - /** Send solved block to local blockchain controller */ - private def sendToNodeView(newBlock: ErgoFullBlock): Unit = { + /** Send solved ordering block to processing */ + 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) { - newBlock.blockSections - } else { - newBlock.mandatoryBlockSections - } - sectionsToApply.foreach(viewHolderRef ! LocallyGeneratedModifier(_)) + viewHolderRef ! LocallyGeneratedOrderingBlock(newBlock) + } + + /** Send solved input block to processing */ + private def sendInputToNodeView(sbi: SubBlockInfo, sbt: SubBlockTransactionsData): Unit = { + log.info( + s"New input block ${sbi.subBlock.id} w. nonce ${Longs.fromByteArray(sbi.subBlock.powSolution.n)}" + ) + viewHolderRef ! LocallyGeneratedInputBlock(sbi, sbt) } override def receive: Receive = { // 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( @@ -178,9 +176,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) @@ -188,20 +187,31 @@ 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) => - 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))) + sf match { + case _: OrderingSolutionFound => + val newBlock = completeOrderingBlock(state.cache.get.candidateBlock, solution) + log.info(s"New block mined, header: ${newBlock.header}") + ergoSettings.chainSettings.powScheme + .validate(newBlock.header) // check header PoW only + .map(_ => newBlock) match { + case Success(newBlock) => + sendOrderingToNodeView(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!") + val (sbi, sbt) = completeInputBlock(state.cache.get.candidateBlock, solution) + sendInputToNodeView(sbi, sbt) + StatusReply.error( - new Exception(s"Invalid block mined: ${exception.getMessage}", exception) + new Exception(s"Input block found! PoW valid: ${SubBlockAlgos.checkInputBlockPoW(sbi.subBlock)}") ) } } @@ -295,22 +305,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) @@ -408,10 +402,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 && @@ -425,7 +415,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 @@ -444,23 +433,36 @@ 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)) + 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) + // 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 { + 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) @@ -827,6 +829,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 => @@ -910,7 +913,7 @@ 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) @@ -918,4 +921,23 @@ object CandidateGenerator extends ScorexLogging { new ErgoFullBlock(header, blockTransactions, extension, Some(adProofs)) } + 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 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/mining/ErgoMiningThread.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala index 306ddd1f6c..350b1fff6f 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, InputSolutionFound, NothingFound, OrderingBlockFound, OrderingSolutionFound} import org.ergoplatform.mining.CandidateGenerator.{Candidate, GenerateCandidate} import org.ergoplatform.settings.ErgoSettings import scorex.util.ScorexLogging @@ -61,19 +62,26 @@ 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)) case MineCmd => val lastNonceToCheck = nonce + NonceStep powScheme.proveCandidate(candidateBlock, sk, nonce, lastNonceToCheck) match { - case Some(newBlock) => - log.info(s"Found solution, sending it for validation") - candidateGenerator ! newBlock.header.powSolution - case None => + case OrderingBlockFound(newBlock) => + 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)) self ! MineCmd + case _ => + //todo : rework ProveBlockResult hierarchy to avoid this branch } case GetSolvedBlocksCount => sender() ! SolvedBlocksCount(solvedBlocksCount) diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index e2a550282a..eb0da81dd8 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._ @@ -22,9 +21,9 @@ 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.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, SubBlockTransactionsData, SubBlockTransactionsMessageSpec, SubBlockTransactionsRequestMessageSpec} import org.ergoplatform.serialization.{ErgoSerializer, ManifestSerializer, SubtreeSerializer} +import org.ergoplatform.subblocks.SubBlockInfo import scorex.crypto.authds.avltree.batch.VersionedLDBAVLStorage.splitDigest import scala.annotation.tailrec @@ -53,7 +54,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 +778,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 +802,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 } } @@ -1075,6 +1076,45 @@ 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()) { // 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) + // 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") + penalizeMisbehavingPeer(remote) + } + } else { + log.info(s"Got sub-block for height ${subBlockHeader.height}, while height of our best full-block is ${hr.fullBlockHeight}") + // just ignore the subblock + } + } + + 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. @@ -1230,7 +1270,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, @@ -1494,6 +1534,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 +1559,18 @@ 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) + 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 e1cc4de78d..79ba571569 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizerMessages.scala @@ -11,6 +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 /** * Repository of messages processed ErgoNodeViewSynchronizer actor @@ -142,4 +144,8 @@ object ErgoNodeViewSynchronizerMessages { * @param nipopowProof - proof to initialize history from */ case class ProcessNipopow(nipopowProof: NipopowProof) + + case class ProcessSubblock(subblock: SubBlockInfo) + + case class ProcessSubblockTransactions(std: SubBlockTransactionsData) } 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 diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 19af8bba61..8ffca3bcae 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -19,13 +19,13 @@ 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 import java.io.File import org.ergoplatform.modifiers.history.extension.Extension +import org.ergoplatform.subblocks.SubBlockInfo import scala.annotation.tailrec import scala.collection.mutable @@ -40,7 +40,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 @@ -230,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) { @@ -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) } @@ -302,6 +302,13 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti updateNodeView(updatedHistory = Some(history())) } } + + // subblocks related logic + case ProcessSubblock(sbi) => + history().applySubBlockHeader(sbi) + + case ProcessSubblockTransactions(std) => + history().applySubBlockTransactions(std.subblockID, std.transactions) } /** @@ -573,7 +580,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.") @@ -663,9 +670,25 @@ 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) + 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(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 } protected def getCurrentInfo: Receive = { @@ -718,6 +741,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 diff --git a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistory.scala index c001dd8e64..38c8113bf7 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) => @@ -136,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 @@ -148,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 @@ -184,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 9f7a45f0fe..dd2d39f7f9 100644 --- a/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/history/ErgoHistoryReader.scala @@ -9,9 +9,8 @@ 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.utils.ScorexEncoding import org.ergoplatform.validation.MalformedModifierError import scorex.util.{ModifierId, ScorexLogging} @@ -27,8 +26,8 @@ trait ErgoHistoryReader with ContainsModifiers[BlockSection] with HeadersProcessor with BlockSectionProcessor - with ScorexLogging - with ScorexEncoding { + with SubBlocksProcessor + 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/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..9baec15136 --- /dev/null +++ b/src/main/scala/org/ergoplatform/nodeView/history/storage/modifierprocessors/SubBlocksProcessor.scala @@ -0,0 +1,61 @@ +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 + } + +} diff --git a/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala b/src/main/scala/org/ergoplatform/nodeView/state/DigestState.scala index 94a51540b7..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 @@ -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")) @@ -82,17 +81,17 @@ 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")) 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/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 a16f29265b..c23c90535a 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoState.scala @@ -12,9 +12,8 @@ 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.nodeView.LocallyGeneratedBlockSection import org.ergoplatform.validation.ModifierValidator import scorex.crypto.authds.avltree.batch._ import scorex.crypto.authds.avltree.batch.serialization.{BatchAVLProverManifest, BatchAVLProverSubtree} @@ -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)}")) } } @@ -109,111 +107,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 + private def applyFullBlock(fb: ErgoFullBlock, estimatedTip: Option[Height]) + (generate: LocallyGeneratedBlockSection => 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) - } + // 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(LocallyGeneratedBlockSection(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) } - } - 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) + + 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) + } + } + } + + override def applyModifier(mod: BlockSection, estimatedTip: Option[Height]) + (generate: LocallyGeneratedBlockSection => 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") diff --git a/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala b/src/test/scala/org/ergoplatform/mining/CandidateGeneratorPropSpec.scala index e1a9595d9f..378bb9f4b9 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 sigma.data.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/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/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 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/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/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/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/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index 1414789dae..d4b76fa072 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 = { 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 =>