Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0.0] Header.checkPow method #968

Merged
merged 21 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

yarn.lock
*.log
yarn.lock
docs/spec/out/
test-out/
flamegraphs/
Expand Down
5 changes: 5 additions & 0 deletions core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,11 @@ trait Header {
*/
def serializeWithoutPoW: Coll[Byte]

/**
* @return result of header's proof-of-work validation
*/
def checkPow: Boolean

}

/** Runtime representation of Context ErgoTree type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ object ReflectionData {
},
mkMethod(clazz, "powDistance", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[Header].powDistance
},
mkMethod(clazz, "checkPow", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[Header].checkPow
}
)
)
Expand Down
84 changes: 84 additions & 0 deletions core/shared/src/main/scala/sigma/util/NBitsUtils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package sigma.util

import java.math.BigInteger

object NBitsUtils {

/**
* <p>The "compact" format is a representation of a whole number N using an unsigned 32 bit number similar to a
* floating point format. The most significant 8 bits are the unsigned exponent of base 256. This exponent can
* be thought of as "number of bytes of N". The lower 23 bits are the mantissa. Bit number 24 (0x800000) represents
* the sign of N. Therefore, N = (-1^sign) * mantissa * 256^(exponent-3).</p>
*
* <p>Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). MPI uses the most significant bit of the
* first byte as sign. Thus 0x1234560000 is compact 0x05123456 and 0xc0de000000 is compact 0x0600c0de. Compact
* 0x05c0de00 would be -0x40de000000.</p>
*
* <p>Bitcoin only uses this "compact" format for encoding difficulty targets, which are unsigned 256bit quantities.
* Thus, all the complexities of the sign bit and using base 256 are probably an implementation accident.</p>
*/
def decodeCompactBits(compact: Long): BigInt = {
val size: Int = (compact >> 24).toInt & 0xFF
val bytes: Array[Byte] = new Array[Byte](4 + size)
bytes(3) = size.toByte
if (size >= 1) bytes(4) = ((compact >> 16) & 0xFF).toByte
if (size >= 2) bytes(5) = ((compact >> 8) & 0xFF).toByte
if (size >= 3) bytes(6) = (compact & 0xFF).toByte
decodeMPI(bytes)
}

/**
* @see Utils#decodeCompactBits(long)
*/
def encodeCompactBits(requiredDifficulty: BigInt): Long = {
val value = requiredDifficulty.bigInteger
var result: Long = 0L
var size: Int = value.toByteArray.length
if (size <= 3) {
result = value.longValue << 8 * (3 - size)
} else {
result = value.shiftRight(8 * (size - 3)).longValue
}
// The 0x00800000 bit denotes the sign.
// Thus, if it is already set, divide the mantissa by 256 and increase the exponent.
if ((result & 0x00800000L) != 0) {
result >>= 8
size += 1
}
result |= size << 24
val a: Int = if (value.signum == -1) 0x00800000 else 0
result |= a
result
}


/** Parse 4 bytes from the byte array (starting at the offset) as unsigned 32-bit integer in big endian format. */
def readUint32BE(bytes: Array[Byte]): Long = ((bytes(0) & 0xffL) << 24) | ((bytes(1) & 0xffL) << 16) | ((bytes(2) & 0xffL) << 8) | (bytes(3) & 0xffL)

/**
* MPI encoded numbers are produced by the OpenSSL BN_bn2mpi function. They consist of
* a 4 byte big endian length field, followed by the stated number of bytes representing
* the number in big endian format (with a sign bit).
*
*/
private def decodeMPI(mpi: Array[Byte]): BigInteger = {

val length: Int = readUint32BE(mpi).toInt
val buf = new Array[Byte](length)
System.arraycopy(mpi, 4, buf, 0, length)

if (buf.length == 0) {
BigInteger.ZERO
} else {
val isNegative: Boolean = (buf(0) & 0x80) == 0x80
if (isNegative) buf(0) = (buf(0) & 0x7f).toByte
val result: BigInteger = new BigInteger(buf)
if (isNegative) {
result.negate
} else {
result
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ class HeaderWithoutPow(val version: Byte, // 1 byte
def toHeader(powSolution: AutolykosSolution, bytes: Array[Byte]): ErgoHeader =
ErgoHeader(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp,
nBits, height, extensionRoot, powSolution, votes, unparsedBytes, bytes)

override def toString: String = {
s"HeaderWithoutPow($version, $parentId, ${bytesToId(ADProofsRoot)}, ${bytesToId(stateRoot)}, " +
s"${bytesToId(transactionsRoot)}, $timestamp, $nBits, $height, ${bytesToId(extensionRoot)}, ${bytesToId(votes)}, " +
s"${bytesToId(unparsedBytes)} )"
}
}

object HeaderWithoutPow {
Expand Down
15 changes: 13 additions & 2 deletions data/shared/src/main/scala/sigma/ast/SMethod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ case class SMethod(
irInfo: MethodIRInfo,
docInfo: Option[OperationInfo],
costFunc: Option[MethodCostFunc],
userDefinedInvoke: Option[SMethod.InvokeHandler]
userDefinedInvoke: Option[SMethod.InvokeHandler],
sinceVersion: Byte
) {

/** Operation descriptor of this method. */
Expand Down Expand Up @@ -314,7 +315,17 @@ object SMethod {
): SMethod = {
SMethod(
objType, name, stype, methodId, costKind, explicitTypeArgs,
MethodIRInfo(None, None, None), None, None, None)
MethodIRInfo(None, None, None), None, None, None, 0)
}

/** Convenience factory method. */
def apply(objType: MethodsContainer, name: String, stype: SFunc,
methodId: Byte,
costKind: CostKind,
sinceVersion: Byte): SMethod = {
SMethod(
objType, name, stype, methodId, costKind, Nil,
MethodIRInfo(None, None, None), None, None, None, sinceVersion)
}


Expand Down
23 changes: 20 additions & 3 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1456,11 +1456,28 @@ case object SHeaderMethods extends MonoTypeMethods {
lazy val powDistanceMethod = propertyCall("powDistance", SBigInt, 14, FixedCost(JitCost(10)))
lazy val votesMethod = propertyCall("votes", SByteArray, 15, FixedCost(JitCost(10)))

protected override def getMethods() = super.getMethods() ++ Seq(
// cost of checkPoW is 700 as about 2*32 hashes required, and 1 hash (id) over short data costs 10
lazy val checkPowMethod = SMethod(
this, "checkPow", SFunc(Array(SHeader), SBoolean), 16, FixedCost(JitCost(700)),
sinceVersion = VersionContext.V6SoftForkVersion)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "Validate header's proof-of-work")

private lazy val v5Methods = super.getMethods() ++ Seq(
idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod,
timestampMethod, nBitsMethod, heightMethod, extensionRootMethod, minerPkMethod, powOnetimePkMethod,
powNonceMethod, powDistanceMethod, votesMethod
)
powNonceMethod, powDistanceMethod, votesMethod)

// 6.0 : checkPow method added
private lazy val v6Methods = v5Methods ++ Seq(checkPowMethod)

protected override def getMethods() = {
if (VersionContext.current.isV6SoftForkActivated) {
v6Methods
} else {
v5Methods
}
}
}

/** Type descriptor of `PreHeader` type of ErgoTree. */
Expand Down
3 changes: 3 additions & 0 deletions data/shared/src/main/scala/sigma/ast/values.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,9 @@ case class MethodCall(
addCost(MethodCall.costKind) // MethodCall overhead
method.costKind match {
case fixed: FixedCost =>
if (method.sinceVersion > 0 && E.context.currentErgoTreeVersion < method.sinceVersion) {
syntax.error(s"Method ${method.name} is not supported in tree version ${E.context.currentErgoTreeVersion}")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be the first code fragment versioned by tree version (rather than activatedVersion). Maybe use activatedVersion instead? dApps usually don't care about versions, and hence requiring a minimal tree version may require dApp upgrades.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible, however, there are some questions to resolve then :

  • NewFeature implementation by you was expecting this:
    override def isSupportedIn(vc: VersionContext): Boolean =
      vc.activatedVersion >= sinceVersion && vc.ergoTreeVersion >= sinceVersion

why ?

  • what does tree version mean at all ? How tree of version v+1 is different from tree version v?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rollback versioning by tree and changed NewFeature definition. The question about versioning semantics still remains.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "vc.activatedVersion >= sinceVersion && vc.ergoTreeVersion >= sinceVersion" was an intermediate version, which was fixed in the in my original more recent PRs.
The tree version is more about bytecode format, see for example switch from v0, to v1, there size has become required. However, there is also a relation between tree version and block version, s.t. treeVersion <= activatedScriptVersion.

val extra = method.extraDescriptors
val extraLen = extra.length
val len = args.length
Expand Down
10 changes: 6 additions & 4 deletions data/shared/src/main/scala/sigma/data/CHeader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.ergoplatform.{AutolykosSolution, ErgoHeader, HeaderWithoutPow, Header
import scorex.crypto.authds.ADDigest
import scorex.crypto.hash.Digest32
import scorex.util.{bytesToId, idToBytes}
import sigma.pow.Autolykos2PowValidation
import sigma.{AvlTree, BigInt, Coll, Colls, GroupElement, Header}

/** A default implementation of [[Header]] interface.
Expand Down Expand Up @@ -66,10 +67,11 @@ class CHeader(val ergoHeader: ErgoHeader) extends Header with WrapperOf[ErgoHead
override def wrappedValue: ErgoHeader = ergoHeader

override def serializeWithoutPoW: Coll[Byte] = {
val headerWithoutPow = HeaderWithoutPow(version, bytesToId(parentId.toArray), Digest32 @@ ADProofsRoot.toArray,
ADDigest @@ stateRoot.digest.toArray, Digest32 @@ transactionsRoot.toArray, timestamp,
nBits, height, Digest32 @@ extensionRoot.toArray, votes.toArray, unparsedBytes.toArray)
Colls.fromArray(HeaderWithoutPowSerializer.toBytes(headerWithoutPow))
Colls.fromArray(HeaderWithoutPowSerializer.toBytes(ergoHeader))
}

override def checkPow: Boolean = {
Autolykos2PowValidation.checkPoWForVersion2(this)
}

override def toString: String =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package sigma.eval

import sigma.{AvlTree, Coll, Context}
import sigma.{AvlTree, Coll, Context, Header}
import sigma.ast.{Constant, FixedCost, MethodCall, OperationCostInfo, OperationDesc, PerItemCost, SType, TypeBasedCost}
import sigma.data.KeyValueColl

Expand Down
Loading
Loading