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

NODE-2613 RIDE calculateDelay() #3879

Merged
merged 8 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ object EnvironmentFunctionsBenchmark {

override def accountScript(addressOrAlias: Recipient): Option[Script] = ???

override def calculateDelay(hitSource: ByteStr, baseTarget: Long, generator: ByteStr, balance: Long): Long = ???

def callScript(
dApp: Address,
func: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.wavesplatform.state

import com.wavesplatform.common.state.ByteStr
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole

import java.util.concurrent.TimeUnit

/*
[info] Benchmark (configFile) Mode Cnt Score Error Units
[info] CalculateDelayBenchmark.calculateDelay1 waves.conf avgt 10 1,616 ± 0,244 us/op
[info] CalculateDelayBenchmark.calculateDelay2 waves.conf avgt 10 1,671 ± 0,073 us/op
[info] CalculateDelayBenchmark.calculateDelay3 waves.conf avgt 10 1,688 ± 0,228 us/op
[info] CalculateDelayBenchmark.calculateDelay4 waves.conf avgt 10 1,656 ± 0,020 us/op
*/

@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Array(Mode.AverageTime))
@Threads(1)
@Fork(1)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 10, time = 1)
class CalculateDelayBenchmark {
import CalculateDelayBenchmark.*

@Benchmark
def calculateDelay1(bh: Blackhole, st: St): Unit =
bh.consume(st.environment.calculateDelay(ByteStr.empty, 0, ByteStr.empty, 0))

@Benchmark
def calculateDelay2(bh: Blackhole, st: St): Unit =
bh.consume(
st.environment.calculateDelay(ByteStr.fill(96)(127), Long.MaxValue, ByteStr.fill(26)(127), Long.MaxValue)
)

@Benchmark
def calculateDelay3(bh: Blackhole, st: St): Unit =
bh.consume(
st.environment.calculateDelay(ByteStr.fill(96)(-128), Long.MinValue, ByteStr.fill(26)(-128), Long.MinValue)
)

@Benchmark
def calculateDelay4(bh: Blackhole, st: St): Unit =
bh.consume(
st.environment.calculateDelay(ByteStr.fill(32)(32), 123456, ByteStr.fill(26)(32), 100_000_000)
)
}

object CalculateDelayBenchmark {
class St extends DBState {}
}
7 changes: 7 additions & 0 deletions lang/doc/v8/funcs/blockchain-functions.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,12 @@
paramsDoc: [ "dApp", "function name", "arguments", "Attached payments" ]
complexity: 75
}
{
name: "calculateDelay"
params: [ "ByteVector", "Int", "Address", "Int" ]
doc: "Calculates mining delay using Fair PoS calculator."
paramsDoc: [ "hit source", "base target", "generator address", "generator balance" ]
complexity: 1
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ package object utils {
override def addressFromString(address: String): Either[String, Recipient.Address] = ???
override def addressFromPublicKey(publicKey: ByteStr): Either[String, Address] = ???
override def accountScript(addressOrAlias: Recipient): Option[Script] = ???
override def calculateDelay(hs: ByteStr, bt: Long, gt: ByteStr, b: Long): Long = ???
override def callScript(
dApp: Address,
func: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ object FunctionIds {
val BLS12_GROTH16_VERIFY: Short = 800
val BN256_GROTH16_VERIFY: Short = 801

val ECRECOVER: Short = 900
val ECRECOVER: Short = 900
val CALCULATE_DELAY: Short = 901

val BLS12_GROTH16_VERIFY_LIM: Short = 2400 // Reserved n id for generated limited functions
val BN256_GROTH16_VERIFY_LIM: Short = 2450 // Reserved n id for generated limited functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import com.wavesplatform.lang.v1.FunctionHeader.{Native, User}
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.compiler.Types.*
import com.wavesplatform.lang.v1.evaluator.FunctionIds.*
import com.wavesplatform.lang.v1.evaluator.ctx.impl.EnvironmentFunctions.AddressLength
import com.wavesplatform.lang.v1.evaluator.ctx.impl.converters.*
import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Bindings.{scriptTransfer as _, *}
import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Types.*
import com.wavesplatform.lang.v1.evaluator.ctx.impl.{EnvironmentFunctions, GlobalValNames, PureContext, notImplemented, unit}
import com.wavesplatform.lang.v1.evaluator.ctx.impl.*
import com.wavesplatform.lang.v1.evaluator.ctx.{BaseFunction, NativeFunction, UserFunction}
import com.wavesplatform.lang.v1.evaluator.{ContextfulNativeFunction, ContextfulUserFunction, FunctionIds, Log}
import com.wavesplatform.lang.v1.traits.domain.{Issue, Lease, Recipient}
Expand Down Expand Up @@ -304,7 +305,7 @@ object Functions {
PureContext.eq,
List(
FUNCTION_CALL(PureContext.sizeBytes, List(REF("@afs_addrBytes"))),
CONST_LONG(EnvironmentFunctions.AddressLength)
CONST_LONG(AddressLength)
)
),
IF(
Expand Down Expand Up @@ -880,7 +881,6 @@ object Functions {
BYTESTR,
("lease", leaseActionType)
) {
val AddressLength = 26
val MaxAliasLength = 30
new ContextfulNativeFunction.Simple[Environment]("calculateLeaseId", BYTESTR, Seq(("lease", leaseActionType))) {
override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] =
Expand Down Expand Up @@ -943,4 +943,41 @@ object Functions {
}
}

val calculateDelay: NativeFunction[Environment] = {
val args =
Seq(
("hit source", BYTESTR),
("base target", LONG),
("generator", addressType),
("balance", LONG)
)
NativeFunction.withEnvironment[Environment](
"calculateDelay",
1,
CALCULATE_DELAY,
LONG,
args*
) {
val MaxHitSourceLength = 96
new ContextfulNativeFunction.Simple[Environment]("calculateDelay", LONG, args) {
override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] =
args match {
case CONST_BYTESTR(hitSource) :: CONST_LONG(baseTarget) :: CaseObj(`addressType`, fields) :: CONST_LONG(balance) :: Nil =>
val addressBytes = fields("bytes").asInstanceOf[CONST_BYTESTR].bs
if (addressBytes.size > AddressLength) {
val error = CommonError(s"Address bytes length = ${addressBytes.size} exceeds limit = $AddressLength")
(error: ExecutionError).asLeft[EVALUATED].pure[F]
} else if (hitSource.size > MaxHitSourceLength) {
val error = CommonError(s"Hit source bytes length = ${hitSource.size} exceeds limit = $MaxHitSourceLength")
(error: ExecutionError).asLeft[EVALUATED].pure[F]
} else {
val delay = env.calculateDelay(hitSource, baseTarget, addressBytes, balance)
(CONST_LONG(delay): EVALUATED).asRight[ExecutionError].pure[F]
}
case xs =>
notImplemented[Id, EVALUATED]("calculateDelay(hitSource: ByteVector, baseTarget: ByteVector, generator: Address, balance: Long)", xs)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ object WavesContext {
fromV4Funcs(proofsEnabled, ds.stdLibVersion, typeDefs) ++ v5Funcs ++ dAppFuncs ++ accountFuncs
}

private def fromV8Funcs(proofsEnabled: Boolean, ds: DirectiveSet, typeDefs: Map[String, FINAL]) =
fromV5Funcs(proofsEnabled, ds, typeDefs) :+ calculateDelay

private def selfCallFunctions(v: StdLibVersion) =
Array(
getIntegerFromStateSelfF,
Expand All @@ -119,11 +122,11 @@ object WavesContext {

val versionSpecificFuncs =
version match {
case V1 | V2 => Array(txByIdF(proofsEnabled, version)) ++ balanceV123Functions
case V3 => fromV3Funcs(proofsEnabled, version, typeDefs) ++ balanceV123Functions
case V4 => fromV4Funcs(proofsEnabled, version, typeDefs)
case V5 => fromV5Funcs(proofsEnabled, ds, typeDefs)
case _ => fromV5Funcs(proofsEnabled, ds, typeDefs)
case V1 | V2 => Array(txByIdF(proofsEnabled, version)) ++ balanceV123Functions
case V3 => fromV3Funcs(proofsEnabled, version, typeDefs) ++ balanceV123Functions
case V4 => fromV4Funcs(proofsEnabled, version, typeDefs)
case V5 | V6 | V7 => fromV5Funcs(proofsEnabled, ds, typeDefs)
case _ => fromV8Funcs(proofsEnabled, ds, typeDefs)
}
commonFuncs ++ versionSpecificFuncs
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ trait Environment[F[_]] {
availableComplexity: Int,
reentrant: Boolean
): Coeval[F[(Either[ValidationError, (EVALUATED, Log[F])], Int)]]
def calculateDelay(hitSource: ByteStr, baseTarget: Long, generator: ByteStr, balance: Long): Long
}
47 changes: 24 additions & 23 deletions lang/testkit/src/main/scala/com/wavesplatform/lang/Common.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,30 @@ object Common {

def emptyBlockchainEnvironment(h: Int = 1, in: Coeval[Environment.InputEntity] = Coeval(???), nByte: Byte = 'T'): Environment[Id] =
new Environment[Id] {
override def height: Long = h
override def chainId: Byte = nByte
override def inputEntity = in()

override def transactionById(id: Array[Byte]): Option[Tx] = ???
override def transferTransactionById(id: Array[Byte]): Option[Tx.Transfer] = ???
override def transactionHeightById(id: Array[Byte]): Option[Long] = ???
override def assetInfoById(id: Array[Byte]): Option[ScriptAssetInfo] = ???
override def lastBlockOpt(): Option[BlockInfo] = ???
override def blockInfoByHeight(height: Int): Option[BlockInfo] = ???
override def data(recipient: Recipient, key: String, dataType: DataType): Option[Any] = None
override def hasData(recipient: Recipient): Boolean = false
override def resolveAlias(name: String): Either[String, Recipient.Address] = ???
override def accountBalanceOf(addressOrAlias: Recipient, assetId: Option[Array[Byte]]): Either[String, Long] = ???
override def accountWavesBalanceOf(addressOrAlias: Recipient): Either[String, Environment.BalanceDetails] = ???
override def tthis: Environment.Tthis = Coproduct(Address(ByteStr.empty))
override def multiPaymentAllowed: Boolean = true
override def txId: ByteStr = ???
override def transferTransactionFromProto(b: Array[Byte]): Option[Tx.Transfer] = ???
override def addressFromString(address: String): Either[String, Recipient.Address] = ???
override def addressFromPublicKey(publicKey: ByteStr): Either[String, Address] = ???
def accountScript(addressOrAlias: Recipient): Option[Script] = ???
override def callScript(
def height: Long = h
def chainId: Byte = nByte
def inputEntity = in()

def transactionById(id: Array[Byte]): Option[Tx] = ???
def transferTransactionById(id: Array[Byte]): Option[Tx.Transfer] = ???
def transactionHeightById(id: Array[Byte]): Option[Long] = ???
def assetInfoById(id: Array[Byte]): Option[ScriptAssetInfo] = ???
def lastBlockOpt(): Option[BlockInfo] = ???
def blockInfoByHeight(height: Int): Option[BlockInfo] = ???
def data(recipient: Recipient, key: String, dataType: DataType): Option[Any] = None
def hasData(recipient: Recipient): Boolean = false
def resolveAlias(name: String): Either[String, Recipient.Address] = ???
def accountBalanceOf(a: Recipient, b: Option[Array[Byte]]): Either[String, Long] = ???
def accountWavesBalanceOf(a: Recipient): Either[String, Environment.BalanceDetails] = ???
def tthis: Environment.Tthis = Coproduct(Address(ByteStr.empty))
def multiPaymentAllowed: Boolean = true
def txId: ByteStr = ???
def transferTransactionFromProto(b: Array[Byte]): Option[Tx.Transfer] = ???
def addressFromString(address: String): Either[String, Recipient.Address] = ???
def addressFromPublicKey(publicKey: ByteStr): Either[String, Address] = ???
def accountScript(addressOrAlias: Recipient): Option[Script] = ???
def calculateDelay(hs: ByteStr, bt: Long, gt: ByteStr, b: Long): Long = ???
def callScript(
dApp: Address,
func: String,
args: List[EVALUATED],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ sealed trait PoSCalculator {
}

object PoSCalculator {
private[consensus] val HitSize: Int = 8
val MinBaseTarget: Long = 9
val HitSize: Int = 8
val MinBaseTarget: Long = 9

def generationSignature(signature: ByteStr, publicKey: PublicKey): Array[Byte] = {
val s = new Array[Byte](crypto.DigestLength * 2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import com.wavesplatform.account.{AddressOrAlias, PublicKey}
import com.wavesplatform.block.BlockHeader
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.consensus.{FairPoSCalculator, PoSCalculator}
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.features.MultiPaymentPolicyProvider.*
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.{Global, ValidationError}
import com.wavesplatform.lang.directives.DirectiveSet
import com.wavesplatform.lang.directives.values.StdLibVersion
import com.wavesplatform.lang.script.Script
Expand Down Expand Up @@ -250,6 +251,11 @@ class WavesEnvironment(
reentrant: Boolean
): Coeval[(Either[ValidationError, (EVALUATED, Log[Id])], Int)] = ???

override def calculateDelay(hitSource: ByteStr, baseTarget: Long, generator: ByteStr, balance: Long): Long = {
val hit = Global.blake2b256(hitSource.arr ++ generator.arr).take(PoSCalculator.HitSize)
FairPoSCalculator.V2.calculateDelay(BigInt(1, hit), baseTarget, balance)
}

private def getRewards(generator: PublicKey, height: Int): Seq[(Address, Long)] = {
if (blockchain.isFeatureActivated(BlockchainFeatures.CappedReward)) {
val rewardShares = BlockRewardCalculator.getSortedBlockRewardShares(height, generator.toAddress, blockchain)
Expand Down
5 changes: 3 additions & 2 deletions node/src/test/scala/com/wavesplatform/history/Domain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import com.wavesplatform.lang.script.Script
import com.wavesplatform.mining.{BlockChallenger, BlockChallengerImpl}
import com.wavesplatform.settings.WavesSettings
import com.wavesplatform.state.*
import com.wavesplatform.state.appender.BlockAppender
import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult
import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult.{Applied, Ignored}
import com.wavesplatform.state.TxStateSnapshotHashBuilder.InitStateHash
import com.wavesplatform.state.appender.BlockAppender
import com.wavesplatform.state.diffs.{BlockDiffer, TransactionDiffer}
import com.wavesplatform.state.reader.SnapshotBlockchain
import com.wavesplatform.test.TestTime
Expand Down Expand Up @@ -444,7 +444,8 @@ case class Domain(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl, rocksDBWri
)
resultStateHash <- stateHash.map(Right(_)).getOrElse {
if (blockchain.isFeatureActivated(TransactionStateSnapshot, blockchain.height + 1)) {
val blockchain = SnapshotBlockchain(this.blockchain, StateSnapshot.empty, blockWithoutStateHash, ByteStr.empty, 0, None)
val hitSource = posSelector.validateGenerationSignature(blockWithoutStateHash).getOrElse(blockWithoutStateHash.header.generationSignature)
val blockchain = SnapshotBlockchain(this.blockchain, StateSnapshot.empty, blockWithoutStateHash, hitSource, 0, None)
val prevStateHash = this.blockchain.lastBlockHeader.flatMap(_.header.stateHash).getOrElse(InitStateHash)

BlockDiffer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.wavesplatform.state.diffs.smart.predef

import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.db.WithDomain
import com.wavesplatform.db.WithState.AddrWithBalance
import com.wavesplatform.lang.directives.values.V8
import com.wavesplatform.lang.v1.compiler.TestCompiler
import com.wavesplatform.test.*
import com.wavesplatform.transaction.TxHelpers.*

class CalculateDelayTest extends PropSpec with WithDomain {
import DomainPresets.*

private val contract =
TestCompiler(V8).compileContract(
s"""
| @Callable(i)
| func default() = {
| let hitSource = if (height % 2 == 0) then lastBlock.generationSignature else lastBlock.vrf.value()
| let address1 = i.caller
| let address2 = Address(base58'${signer(2).toAddress}')
| let address3 = Address(base58'${signer(3).toAddress}')
| let lowest = calculateDelay(hitSource, lastBlock.baseTarget, address1, 10 * 1000 * 1000)
| let medium = calculateDelay(hitSource, lastBlock.baseTarget, address2, 30 * 1000 * 1000)
| let largest = calculateDelay(hitSource, lastBlock.baseTarget, address3, 90 * 1000 * 1000)
| [
| IntegerEntry("lowest", lowest),
| IntegerEntry("medium", medium),
| IntegerEntry("largest", largest)
| ]
| }
|
| @Callable(i)
| func error1() = {
| strict r = calculateDelay(base58'${ByteStr.fill(97)(1)}', 0, i.caller, 0)
| []
| }
|
| @Callable(i)
| func error2() = {
| strict r = calculateDelay(lastBlock.generationSignature, 0, Address(base58'${ByteStr.fill(27)(1)}'), 0)
| []
| }
""".stripMargin
)

property("distribution of calculateDelay()") {
withDomain(TransactionStateSnapshot, AddrWithBalance.enoughBalances(secondSigner)) { d =>
d.appendBlock(setScript(secondSigner, contract))
val minDelays =
(1 to 200).map { _ =>
d.appendBlock(invoke())
d.liquidDiff.accountData(secondAddress).values.minBy(_.value.asInstanceOf[Long]).key
}
val lowestIsMiner = minDelays.count(_ == "lowest")
val mediumIsMiner = minDelays.count(_ == "medium")
val largestIsMiner = minDelays.count(_ == "largest")
lowestIsMiner should be > 0
mediumIsMiner should be > lowestIsMiner
largestIsMiner should be > mediumIsMiner
}
}

property("errors of calculateDelay()") {
withDomain(TransactionStateSnapshot, AddrWithBalance.enoughBalances(secondSigner)) { d =>
d.appendBlock(setScript(secondSigner, contract))
d.appendBlockE(invoke(func = Some("error1"))) should produce("Hit source bytes length = 97 exceeds limit = 96")
d.appendBlockE(invoke(func = Some("error2"))) should produce("Address bytes length = 27 exceeds limit = 26")
}
}
}
Loading