Skip to content

Commit

Permalink
NODE-2632 Delayed Light Node block structure (#3917)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrtm000 authored Dec 7, 2023
1 parent 9f95648 commit b9c794d
Show file tree
Hide file tree
Showing 28 changed files with 352 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.wavesplatform.transaction.TxHelpers
import com.wavesplatform.utils.DiffMatchers
import monix.execution.Scheduler.Implicits.global
import org.scalatest.{Assertion, BeforeAndAfterAll}
import com.wavesplatform.test.DomainPresets.*

import scala.concurrent.Await
import scala.concurrent.duration.{DurationInt, FiniteDuration}
Expand Down Expand Up @@ -186,7 +187,7 @@ class AccountsApiGrpcSpec extends FreeSpec with BeforeAndAfterAll with DiffMatch

val sender = TxHelpers.signer(1)
val challengedMiner = TxHelpers.signer(2)
withDomain(DomainPresets.TransactionStateSnapshot, balances = AddrWithBalance.enoughBalances(sender)) { d =>
withDomain(TransactionStateSnapshot.configure(_.copy(lightNodeBlockFieldsAbsenceInterval = 0)), balances = AddrWithBalance.enoughBalances(sender)) { d =>
val grpcApi = getGrpcApi(d)

val challengingMiner = d.wallet.generateNewAccount().get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class BlocksApiGrpcSpec extends FreeSpec with BeforeAndAfterAll with DiffMatcher

"NODE-922. GetBlock should return correct data for challenging block" in {
val sender = TxHelpers.signer(1)
withDomain(DomainPresets.TransactionStateSnapshot, balances = AddrWithBalance.enoughBalances(sender, defaultSigner)) { d =>
withDomain(TransactionStateSnapshot.configure(_.copy(lightNodeBlockFieldsAbsenceInterval = 0)), balances = AddrWithBalance.enoughBalances(sender, defaultSigner)) { d =>
val grpcApi = getGrpcApi(d)
val challengingMiner = d.wallet.generateNewAccount().get

Expand Down Expand Up @@ -304,7 +304,7 @@ class BlocksApiGrpcSpec extends FreeSpec with BeforeAndAfterAll with DiffMatcher

"NODE-922. GetBlockRange should return correct data for challenging block" in {
val sender = TxHelpers.signer(1)
withDomain(DomainPresets.TransactionStateSnapshot, balances = AddrWithBalance.enoughBalances(sender, defaultSigner)) { d =>
withDomain(TransactionStateSnapshot.configure(_.copy(lightNodeBlockFieldsAbsenceInterval = 0)), balances = AddrWithBalance.enoughBalances(sender, defaultSigner)) { d =>
val grpcApi = getGrpcApi(d)
val challengingMiner = d.wallet.generateNewAccount().get

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.wavesplatform.history.Domain
import com.wavesplatform.protobuf.transaction.{PBTransactions, Recipient}
import com.wavesplatform.state.TxMeta
import com.wavesplatform.test.*
import com.wavesplatform.test.DomainPresets.*
import com.wavesplatform.transaction.Asset.Waves
import com.wavesplatform.transaction.{TxHelpers, TxVersion}
import com.wavesplatform.transaction.assets.exchange.{ExchangeTransaction, Order, OrderType}
Expand Down Expand Up @@ -142,7 +143,7 @@ class TransactionsApiGrpcSpec extends FreeSpec with BeforeAndAfterAll with DiffM
val challengedMiner = TxHelpers.signer(2)
val resender = TxHelpers.signer(3)
val recipient = TxHelpers.signer(4)
withDomain(DomainPresets.TransactionStateSnapshot, balances = AddrWithBalance.enoughBalances(sender)) { d =>
withDomain(TransactionStateSnapshot.configure(_.copy(lightNodeBlockFieldsAbsenceInterval = 0)), balances = AddrWithBalance.enoughBalances(sender)) { d =>
val grpcApi = getGrpcApi(d)
val challengingMiner = d.wallet.generateNewAccount().get

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ class BlockchainUpdatesSpec extends FreeSpec with WithBUDomain with ScalaFutures
val sender = TxHelpers.signer(3)
val recipient = TxHelpers.signer(4)

withDomainAndRepo(settings = DomainPresets.TransactionStateSnapshot) { case (d, repo) =>
withDomainAndRepo(settings = TransactionStateSnapshot.configure(_.copy(lightNodeBlockFieldsAbsenceInterval = 0))) { case (d, repo) =>
val challengingMiner = d.wallet.generateNewAccount().get

val initSenderBalance = 100000.waves
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wavesplatform.it.sync
package com.wavesplatform.it.sync.lightnode

import com.typesafe.config.Config
import com.wavesplatform.account.KeyPair
Expand Down Expand Up @@ -35,6 +35,7 @@ class BlockChallengeSuite extends BaseFunSuite with TransferSending {
BlockchainFeatures.LightNode.id.toInt -> 0
)
)
.overrideBase(_.raw("waves.blockchain.custom.functionality.light-node-block-fields-absence-interval = 0"))
.withDefault(1)
.withSpecial(1, _.lightNode)
.withSpecial(2, _.nonMiner)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.wavesplatform.it.sync
package com.wavesplatform.it.sync.lightnode

import com.google.common.primitives.Ints
import com.typesafe.config.Config
import com.wavesplatform.account.{Address, PublicKey}
import com.wavesplatform.it.{BaseFunSuite, NodeConfigs, TransferSending}
import com.wavesplatform.it.api.SyncHttpApi.*
import com.wavesplatform.it.{BaseFunSuite, NodeConfigs, TransferSending}

class LightNodeBroadcastSuite extends BaseFunSuite with TransferSending {
override def nodeConfigs: Seq[Config] =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package com.wavesplatform.it.sync.lightnode

import com.typesafe.config.Config
import com.wavesplatform.features.BlockchainFeatures.LightNode
import com.wavesplatform.it.api.SyncHttpApi.*
import com.wavesplatform.it.{BaseFunSuite, NodeConfigs, TransferSending}
import com.wavesplatform.test.NumericExt

class LightNodeMiningSuite extends BaseFunSuite with TransferSending {
override def nodeConfigs: Seq[Config] =
NodeConfigs.newBuilder
.overrideBase(_.lightNode)
.withDefault(2)
.overrideBase(_.preactivatedFeatures(LightNode.id.toInt -> 2))
.overrideBase(_.raw("waves.blockchain.custom.functionality.light-node-block-fields-absence-interval = 2"))
.withDefault(1)
.withSpecial(1, _.lightNode)
.buildNonConflicting()

test("nodes can mine in light mode") {
val first = nodes.head
val second = nodes.last
test("node can mine in light mode after light-node-block-fields-absence-interval") {
val lightNode = nodes.find(_.settings.enableLightMode).get
val fullNode = nodes.find(!_.settings.enableLightMode).get
val lightNodeAddress = lightNode.keyPair.toAddress.toString
val fullNodeAddress = fullNode.keyPair.toAddress.toString

val tx1 = first.transfer(first.keyPair, second.address, 1, waitForTx = true)
nodes.waitForHeightArise()
second.transactionStatus(tx1.id).applicationStatus.get shouldBe "succeeded"
nodes.waitForHeight(5)
fullNode.transfer(fullNode.keyPair, lightNodeAddress, fullNode.balance(fullNodeAddress).balance - 1.waves)
lightNode.blockSeq(2, 5).foreach(_.generator shouldBe fullNodeAddress)

val tx2 = second.transfer(second.keyPair, first.address, 1, waitForTx = true)
nodes.waitForHeightArise()
first.transactionStatus(tx2.id).applicationStatus.get shouldBe "succeeded"
lightNode.waitForHeight(6)
lightNode.blockAt(6).generator shouldBe lightNodeAddress
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.wavesplatform.it.sync
package com.wavesplatform.it.sync.lightnode

import com.google.common.primitives.Ints
import com.typesafe.config.Config
import com.wavesplatform.account.{Address, PublicKey}
import com.wavesplatform.it.{BaseFunSuite, NodeConfigs, TransferSending}
import com.wavesplatform.it.api.SyncHttpApi.*
import com.wavesplatform.it.{BaseFunSuite, NodeConfigs, TransferSending}

class LightNodeRollbackSuite extends BaseFunSuite with TransferSending {
override def nodeConfigs: Seq[Config] =
Expand Down
4 changes: 4 additions & 0 deletions node/src/main/resources/custom-defaults.conf
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ functionality {
lease-expiration = 1000000
min-block-time = 15s
delay-delta = 8

# Fill new block header fields (state hash and challenged header)
# only specified number of blocks after the Light Node blockchain feature activation
light-node-block-fields-absence-interval = 1000
}

# Block rewards settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ case class BlocksApiRoute(settings: RestAPISettings, commonApi: CommonBlocksApi,
val predictedHeight = (lowerBound + offset).max(lowerBound).min(upperBound)

val timestamp = timestampOf(predictedHeight)
val rightTimestmap = timestampOf(predictedHeight + 1, Long.MaxValue)
val rightTimestamp = timestampOf(predictedHeight + 1, Long.MaxValue)
val leftHit = timestamp <= target
val rightHit = rightTimestmap <= target
val rightHit = rightTimestamp <= target

val (newLower, newUpper) = {
if (!leftHit) (lowerBound, (predictedHeight - 1).max(lowerBound))
Expand Down
3 changes: 1 addition & 2 deletions node/src/main/scala/com/wavesplatform/crypto/package.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.wavesplatform

import java.lang.reflect.Constructor

import com.wavesplatform.account.{PrivateKey, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.transaction.TxValidationError.GenericError
import com.wavesplatform.utils.*
import org.whispersystems.curve25519.OpportunisticCurve25519Provider

import java.lang.reflect.Constructor
import scala.util.Try

package object crypto {
Expand Down
38 changes: 20 additions & 18 deletions node/src/main/scala/com/wavesplatform/mining/BlockChallenger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class BlockChallengerImpl(
settings: WavesSettings,
timeService: Time,
pos: PoSSelector,
appendBlock: Block => Task[Either[ValidationError, BlockApplyResult]]
appendBlock: Block => Task[Either[ValidationError, BlockApplyResult]],
timeDrift: Long = MaxTimeDrift
) extends BlockChallenger
with ScorexLogging {

Expand Down Expand Up @@ -179,8 +180,6 @@ class BlockChallengerImpl(
blockchainUpdater.parentHeader(prevBlockHeader, 2).map(_.timestamp),
blockTime
)

initialBlockSnapshot <- BlockDiffer.createInitialBlockSnapshot(blockchainUpdater, challengedBlock.header.reference, acc.toAddress)
blockWithoutChallengeAndStateHash <- Block.buildAndSign(
challengedBlock.header.version,
blockTime,
Expand All @@ -204,6 +203,7 @@ class BlockChallengerImpl(
blockchainUpdater.computeNextReward,
None
)
initialBlockSnapshot <- BlockDiffer.createInitialBlockSnapshot(blockchainUpdater, challengedBlock.header.reference, acc.toAddress)
stateHash <- TxStateSnapshotHashBuilder
.computeStateHash(
txs,
Expand All @@ -227,26 +227,28 @@ class BlockChallengerImpl(
acc,
blockFeatures(blockchainUpdater, settings),
blockRewardVote(settings),
Some(stateHash),
Some(
ChallengedHeader(
challengedBlock.header.timestamp,
challengedBlock.header.baseTarget,
challengedBlock.header.generationSignature,
challengedBlock.header.featureVotes,
challengedBlock.header.generator,
challengedBlock.header.rewardVote,
challengedStateHash,
challengedSignature
if (blockchainWithNewBlock.supportsLightNodeBlockFields()) Some(stateHash) else None,
if (blockchainWithNewBlock.supportsLightNodeBlockFields())
Some(
ChallengedHeader(
challengedBlock.header.timestamp,
challengedBlock.header.baseTarget,
challengedBlock.header.generationSignature,
challengedBlock.header.featureVotes,
challengedBlock.header.generator,
challengedBlock.header.rewardVote,
challengedStateHash,
challengedSignature
)
)
)
else None
)
} yield {
log.debug(s"Forged challenging block $challengingBlock")
challengingBlock
}
}.flatMap {
case res @ Right(block) => waitForTimeAlign(block.header.timestamp).map(_ => res)
case res @ Right(block) => waitForTimeAlign(block.header.timestamp, timeDrift).map(_ => res)
case err @ Left(_) => Task(err)
}

Expand All @@ -262,10 +264,10 @@ class BlockChallengerImpl(
private def blockRewardVote(settings: WavesSettings): Long =
settings.rewardsSettings.desired.getOrElse(-1L)

private def waitForTimeAlign(blockTime: Long): Task[Unit] =
private def waitForTimeAlign(blockTime: Long, timeDrift: Long): Task[Unit] =
Task {
val currentTime = timeService.correctedTime()
blockTime - currentTime - MaxTimeDrift
blockTime - currentTime - timeDrift
}.flatMap { timeDiff =>
if (timeDiff > 0) {
Task.sleep(timeDiff.millis)
Expand Down
54 changes: 28 additions & 26 deletions node/src/main/scala/com/wavesplatform/mining/Miner.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.wavesplatform.mining

import java.time.LocalTime
import cats.syntax.either.*
import com.wavesplatform.account.{Address, KeyPair, PKKeyPair}
import com.wavesplatform.block.Block.*
Expand All @@ -18,11 +17,11 @@ import com.wavesplatform.state.*
import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult.{Applied, Ignored}
import com.wavesplatform.state.appender.BlockAppender
import com.wavesplatform.state.diffs.BlockDiffer
import com.wavesplatform.transaction.TxValidationError.BlockFromFuture
import com.wavesplatform.transaction.*
import com.wavesplatform.transaction.TxValidationError.BlockFromFuture
import com.wavesplatform.utils.{ScorexLogging, Time}
import com.wavesplatform.utx.UtxPool.PackStrategy
import com.wavesplatform.utx.UtxPool
import com.wavesplatform.utx.UtxPool.PackStrategy
import com.wavesplatform.wallet.Wallet
import io.netty.channel.group.ChannelGroup
import kamon.Kamon
Expand All @@ -31,6 +30,7 @@ import monix.execution.cancelables.{CompositeCancelable, SerialCancelable}
import monix.execution.schedulers.SchedulerService
import monix.reactive.Observable

import java.time.LocalTime
import scala.concurrent.duration.*

trait Miner {
Expand Down Expand Up @@ -60,7 +60,8 @@ class MinerImpl(
pos: PoSSelector,
val minerScheduler: SchedulerService,
val appenderScheduler: SchedulerService,
transactionAdded: Observable[Unit]
transactionAdded: Observable[Unit],
maxTimeDrift: Long = appender.MaxTimeDrift
) extends Miner
with MinerDebugInfo
with ScorexLogging {
Expand Down Expand Up @@ -90,27 +91,28 @@ class MinerImpl(
def getNextBlockGenerationOffset(account: KeyPair): Either[String, FiniteDuration] =
this.nextBlockGenOffsetWithConditions(account, blockchainUpdater)

def scheduleMining(tempBlockchain: Option[Blockchain]): Unit = {
Miner.blockMiningStarted.increment()

val accounts = if (settings.minerSettings.privateKeys.nonEmpty) {
settings.minerSettings.privateKeys.map(PKKeyPair(_))
} else {
wallet.privateKeyAccounts
def scheduleMining(tempBlockchain: Option[Blockchain]): Unit =
if (!settings.enableLightMode || blockchainUpdater.supportsLightNodeBlockFields()) {
Miner.blockMiningStarted.increment()

val accounts = if (settings.minerSettings.privateKeys.nonEmpty) {
settings.minerSettings.privateKeys.map(PKKeyPair(_))
} else {
wallet.privateKeyAccounts
}

val hasAllowedForMiningScriptsAccounts =
accounts.filter(kp => hasAllowedForMiningScript(kp.toAddress, tempBlockchain.getOrElse(blockchainUpdater)))
scheduledAttempts := CompositeCancelable.fromSet(hasAllowedForMiningScriptsAccounts.map { account =>
generateBlockTask(account, tempBlockchain)
.onErrorHandle(err => log.warn(s"Error mining Block", err))
.runAsyncLogErr(appenderScheduler)
}.toSet)
microBlockAttempt := SerialCancelable()

debugStateRef = MinerDebugInfo.MiningBlocks
}

val hasAllowedForMiningScriptsAccounts =
accounts.filter(kp => hasAllowedForMiningScript(kp.toAddress, tempBlockchain.getOrElse(blockchainUpdater)))
scheduledAttempts := CompositeCancelable.fromSet(hasAllowedForMiningScriptsAccounts.map { account =>
generateBlockTask(account, tempBlockchain)
.onErrorHandle(err => log.warn(s"Error mining Block", err))
.runAsyncLogErr(appenderScheduler)
}.toSet)
microBlockAttempt := SerialCancelable()

debugStateRef = MinerDebugInfo.MiningBlocks
}

override def state: MinerDebugInfo.State = debugStateRef

private def checkAge(parentHeight: Int, parentTimestamp: Long): Either[String, Unit] =
Expand Down Expand Up @@ -184,11 +186,11 @@ class MinerImpl(
currentTime - 1.minute.toMillis
)
_ <- Either.cond(
blockTime <= currentTime + appender.MaxTimeDrift,
blockTime <= currentTime + maxTimeDrift,
log.debug(
s"Forging with ${account.toAddress}, balance $balance, prev block $reference at $height with target ${lastBlockHeader.baseTarget}"
),
s"Block time $blockTime is from the future: current time is $currentTime, MaxTimeDrift = ${appender.MaxTimeDrift}"
s"Block time $blockTime is from the future: current time is $currentTime, MaxTimeDrift = $maxTimeDrift"
)
consensusData <- consensusData(height, account, lastBlockHeader, blockTime)
prevStateHash =
Expand All @@ -207,7 +209,7 @@ class MinerImpl(
account,
blockFeatures(version),
blockRewardVote(version),
stateHash,
if (blockchainUpdater.supportsLightNodeBlockFields(height + 1)) stateHash else None,
None
)
.leftMap(_.err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class MicroBlockMinerImpl(
signer = account,
featureVotes = accumulatedBlock.header.featureVotes,
rewardVote = accumulatedBlock.header.rewardVote,
stateHash = stateHash,
stateHash = if (blockchainUpdater.supportsLightNodeBlockFields()) stateHash else None,
challengedHeader = None
)
.leftMap(BlockBuildError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ case class FunctionalitySettings(
ethInvokePaymentsCheckHeight: Int = 0,
daoAddress: Option[String] = None,
xtnBuybackAddress: Option[String] = None,
xtnBuybackRewardPeriod: Int = Int.MaxValue
xtnBuybackRewardPeriod: Int = Int.MaxValue,
lightNodeBlockFieldsAbsenceInterval: Int = 1000
) {
val allowLeasedBalanceTransferUntilHeight: Int = blockVersion3AfterHeight
val allowTemporaryNegativeUntil: Long = lastTimeBasedForkParameter
Expand Down
Loading

0 comments on commit b9c794d

Please sign in to comment.