Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7b394cc
Validate NiPoPoW proof parameters
tum231990 Jun 12, 2026
21c327f
Address NiPoPoW params review feedback
Jun 16, 2026
a2ffb6e
Stabilize fee estimation heuristics
Jun 19, 2026
4777818
6.0.5 version set
kushti Jul 8, 2026
17f578a
Merge pull request #2379 from a-shannon/fix/nipopow-proof-param-bounds
kushti Jul 10, 2026
366a046
Validate NiPoPoW proof header PoW
tum231990 Jul 10, 2026
594eeb9
Remove unused InfoApiRoute import
tum231990 Jul 10, 2026
cdd89ce
Merge pull request #2396 from a-shannon/fix/nipopow-validated-pow-levels
kushti Jul 13, 2026
ce1dfac
Merge pull request #2410 from a-shannon/fix/fee-estimation-stability
kushti Jul 13, 2026
706a916
Reject invalid inbound NiPoPoW parameters
tum231990 Jul 13, 2026
3cc7f3c
refactor: reuse NiPoPoW parameter validation
Jul 13, 2026
28d644a
Merge pull request #2417 from a-shannon/codex/fix-nipopow-inbound-params
kushti Jul 14, 2026
14e7e71
Bound per-peer outbound buffering
tum231990 Jul 14, 2026
eaa7f1b
Close all live connections for blacklisted IPs
tum231990 Jul 14, 2026
b7baa6c
Preserve asset issuance with token burn requests
tum231990 Jul 15, 2026
83b15cc
Merge pull request #2430 from a-shannon/fix/burn-issue-output-matching
kushti Jul 15, 2026
514800b
Test outbound retry accounting through ACK flow
tum231990 Jul 15, 2026
130518a
Merge pull request #2426 from a-shannon/fix/p2p-outbound-buffer-limit
kushti Jul 15, 2026
84b1638
Merge pull request #2427 from a-shannon/fix/blacklisted-ip-connection…
kushti Jul 15, 2026
88fdaf7
Merge branch 'v6.0.4' of github.com:ergoplatform/ergo into v6.0.5
kushti Jul 30, 2026
7589493
Merge branch 'v6.0.5' of github.com:ergoplatform/ergo into v6.0.5
kushti Jul 30, 2026
7cd8579
fix(nipopow): reject non-canonical interlink runs
a-shannon Aug 5, 2026
ca51652
fix(nipopow): build interlink proofs from full extension tree
a-shannon Aug 5, 2026
f910c9e
fix(nipopow): bind interlink proofs to extension root
a-shannon Aug 5, 2026
80cd70c
test(nipopow): add cross-runtime full-root fixture
a-shannon Aug 5, 2026
53afdbb
fix(nipopow): validate proof parameters before comparison
a-shannon Aug 5, 2026
e6b2d21
fix(nipopow): bound serialized proof resources
a-shannon Aug 5, 2026
5fb8d95
fix(nipopow): version cached proof bytes after root cutover
a-shannon Aug 5, 2026
2bd086a
test(nipopow): add complete cross-runtime proof fixture
a-shannon Aug 5, 2026
ab5d3c9
fix(nipopow): reject invalid continuous mode bytes
a-shannon Aug 6, 2026
3050143
test(nipopow): align CI compatibility
a-shannon Aug 6, 2026
0e83f1b
chore: remove test-only development restriction
a-shannon Aug 6, 2026
a2d3090
fix(nipopow): bound merkle validation and serve snapshots only
a-shannon Aug 6, 2026
57d7f04
refactor(nipopow): clarify length-prefixed parsing terms
a-shannon Aug 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,3 @@
- Follow existing test patterns in similar files
- Type annotations for public methods
- Prefer immutable data structures and functional patterns

## Development Restrictions
- **Code Changes**: Only modify code in `src/test/` folders
- **Production Code**: Do not touch production code in `src/main/` directories
- **Test Focus**: All development work should be test-related only
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import scorex.crypto.authds.LeafData
import scorex.crypto.authds.merkle.{BatchMerkleProof, Leaf, MerkleProof, MerkleTree}
import scorex.crypto.hash.Digest32
import scorex.util.ModifierId
import scala.annotation.nowarn
import scala.collection.mutable
/**
* Extension block section with header id not provided
Expand Down Expand Up @@ -38,20 +37,18 @@ class ExtensionCandidate(val fields: Seq[(Array[Byte], Array[Byte])]) {
.flatMap(kv => merkleTree.proofByElement(Leaf[Digest32](LeafData @@ kv)(Algos.hash)))

/**
* Constructs BatchMerkleProof for a list of interlinks
* Note - only accounts for interlink vector fields in the extension
* Constructs a BatchMerkleProof for the requested extension fields
*
* @param keys - array of 2-byte keys
* @return BatchMerkleProof or None if keys not found
*/
@nowarn
def batchProofFor(keys: Array[Byte]*): Option[BatchMerkleProof[Digest32]] = {
val indices = keys.flatMap(key => fields.find(_._1 sameElements key)
.map(Extension.kvToLeaf)
.map(kv => Leaf[Digest32](LeafData @@ kv)(Algos.hash).hash)
.flatMap(leafData => interlinksMerkleTree.elementsHashIndex.get(
.flatMap(leafData => merkleTree.elementsHashIndex.get(
new mutable.WrappedArray.ofByte(leafData))))
if (indices.isEmpty) None else interlinksMerkleTree.proofByIndices(indices)(Algos.hash)
if (indices.isEmpty) None else merkleTree.proofByIndices(indices)(Algos.hash)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,18 @@ class NipopowAlgos(val chainSettings: ChainSettings) {
*/
def maxLevelOf(header: Header): Int =
if (!header.isGenesis) {
val requiredTarget = org.ergoplatform.mining.q / DifficultySerializer.decodeCompactBits(header.nBits)
val decodedDifficulty = DifficultySerializer.decodeCompactBits(header.nBits)
require(decodedDifficulty > 0, "Decoded difficulty target must be positive")
val requiredTarget = org.ergoplatform.mining.q / decodedDifficulty
val realTarget = powScheme.powHit(header).doubleValue
val level = log2(requiredTarget.doubleValue) - log2(realTarget.doubleValue)
level.toInt
} else {
Int.MaxValue
}

def hasValidPow(header: Header): Boolean = powScheme.validate(header).isSuccess

/**
* Computes best score of a given chain.
* The score value depends on number of µ-superblocks in the given chain.
Expand All @@ -96,6 +100,8 @@ class NipopowAlgos(val chainSettings: ChainSettings) {
* end function
*/
def bestArg(chain: Seq[Header])(m: Int): Int = {
PoPowParams.requireValidM(m)

@scala.annotation.tailrec
def loop(level: Int, acc: Seq[(Int, Int)] = Seq.empty): Seq[(Int, Int)] =
if (level == 0) {
Expand Down Expand Up @@ -130,7 +136,7 @@ class NipopowAlgos(val chainSettings: ChainSettings) {
val k = params.k
val m = params.m

require(params.k >= 1, s"$k < 1")
PoPowParams.requireValid(m, k)
require(chain.lengthCompare(k + m) >= 0, s"Can not prove chain of size < ${k + m}")
require(chain.head.header.isGenesis, "Can not prove non-anchored chain")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ case class NipopowProof(popowAlgos: NipopowAlgos,
*/
def isBetterThan(that: NipopowProof): Boolean = {
try {
if (this.isValid && that.isValid) {
if (this.m != that.m || this.k != that.k) {
false
} else if (this.isValid && that.isValid) {
popowAlgos.lowestCommonAncestor(headersChain, that.headersChain)
.map(h => headersChain.filter(_.height > h.height) -> that.headersChain.filter(_.height > h.height))
.exists({ case (thisDivergingChain, thatDivergingChain) =>
Expand All @@ -72,7 +74,20 @@ case class NipopowProof(popowAlgos: NipopowAlgos,
* @return true if the proof is valid
*/
lazy val isValid: Boolean = {
this.hasValidConnections && this.hasValidHeights && this.hasValidProofs && this.hasValidDifficultyHeaders
this.hasValidParams &&
this.hasValidConnections &&
this.hasValidHeights &&
this.hasValidProofs &&
this.hasValidDifficultyHeaders &&
this.hasValidPow
}

/**
* Checks proof parameters and the exact suffix cardinality before any
* parameter-dependent scoring or validation work.
*/
lazy val hasValidParams: Boolean = {
PoPowParams.areValid(m, k) && suffixTail.lengthCompare(k - 1) == 0
}

/**
Expand Down Expand Up @@ -155,6 +170,8 @@ case class NipopowProof(popowAlgos: NipopowAlgos,
suffixHead.checkInterlinksProof()
}

lazy val hasValidPow: Boolean = headersChain.forall(popowAlgos.hasValidPow)

}

object NipopowProof {
Expand Down Expand Up @@ -185,9 +202,27 @@ object NipopowProof {

}

object NipopowProofSerializer {
private val MaxProofElements = PoPowParams.MaxProofElements

private def requireWithinLimit(value: Int, limit: Int, what: String): Unit =
require(value <= limit, s"$what $value exceeds sanity limit $limit")

private def readLengthPrefixed[T](r: Reader,
limit: Int,
what: String)
(parse: Array[Byte] => T): T = {
val declaredLength = r.getUInt().toIntExact
requireWithinLimit(declaredLength, limit, s"$what length")
parse(r.getBytes(declaredLength))
}
}

class NipopowProofSerializer(poPowAlgos: NipopowAlgos) extends ErgoSerializer[NipopowProof] {
import NipopowProofSerializer._

override def serialize(obj: NipopowProof, w: Writer): Unit = {
require(obj.hasValidParams, "Invalid NiPoPoW proof parameters or suffix length")
w.putUInt(obj.m.toLong)
w.putUInt(obj.k.toLong)
w.putUInt(obj.prefix.size.toLong)
Expand All @@ -211,19 +246,27 @@ class NipopowProofSerializer(poPowAlgos: NipopowAlgos) extends ErgoSerializer[Ni
override def parse(r: Reader): NipopowProof = {
val m = r.getUInt().toIntExact
val k = r.getUInt().toIntExact
PoPowParams.requireValid(m, k)
val prefixSize = r.getUInt().toIntExact
requireWithinLimit(prefixSize, MaxProofElements, "prefix count")
val prefix = (0 until prefixSize).map { _ =>
val size = r.getUInt().toIntExact
PoPowHeaderSerializer.parseBytes(r.getBytes(size))
readLengthPrefixed(r, PoPowHeaderSerializer.MaxSerializedBytes, "prefix element")(
PoPowHeaderSerializer.parseBytes)
}
val suffixHeadSize = r.getUInt().toIntExact
val suffixHead = PoPowHeaderSerializer.parseBytes(r.getBytes(suffixHeadSize))
val suffixHead = readLengthPrefixed(r, PoPowHeaderSerializer.MaxSerializedBytes, "suffix head")(
PoPowHeaderSerializer.parseBytes)
val suffixSize = r.getUInt().toIntExact
requireWithinLimit(suffixSize, MaxProofElements, "suffix count")
require(suffixSize == k - 1,
s"NiPoPoW suffix length ${suffixSize + 1} does not match k parameter $k")
val suffixTail = (0 until suffixSize).map { _ =>
val size = r.getUInt().toIntExact
HeaderSerializer.parseBytes(r.getBytes(size))
readLengthPrefixed(r, PoPowHeaderSerializer.MaxHeaderBytes, "suffix tail")(
HeaderSerializer.parseBytes)
}
val continuous = if (r.getByte() == 1) true else false
val continuousByte = r.getByte()
require(continuousByte == 0 || continuousByte == 1,
s"invalid NiPoPoW continuous mode byte ${continuousByte & 0xff}")
val continuous = continuousByte == 1
NipopowProof(poPowAlgos, m, k, prefix, suffixHead, suffixTail, continuous)
}

Expand Down
Loading
Loading