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-2609 Light node implementation #3874

Merged
merged 48 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0040114
NODE-2609 WIP
Aug 18, 2023
300ec90
NODE-2609 Support snapshots in network protocol
Aug 24, 2023
afe28b4
NODE-2609 Add TODO
Aug 24, 2023
227c42b
Merge branch 'version-1.5.x' into NODE-2609_merge
Aug 28, 2023
1168ad9
NODE-2609 Compute block state hash before feature activation
Aug 25, 2023
03559a1
NODE-2609 State snapshot application
Aug 28, 2023
f1a888e
NODE-2609 Update dependencies
Aug 28, 2023
4a73d8f
NODE-2609 State snapshot application
Aug 30, 2023
09a20da
NODE-2609 Load snapshots in History
Aug 31, 2023
61821e5
Merge branch 'version-1.5.x' into NODE-2609
Sep 1, 2023
33c893b
NODE-2609 Fix tests
Sep 1, 2023
feab7a0
NODE-2609 Light node init optimization
Sep 1, 2023
347e39d
NODE-2609 Tests
Sep 6, 2023
4246f50
NODE-2609 Fix test
Sep 6, 2023
689a3e1
NODE-2609 Fix test
Sep 7, 2023
1ce9608
NODE-2609 Fix test
Sep 7, 2023
fbd495b
NODE-2609 Fixes
Sep 7, 2023
813a5b4
NODE-2609 Review fixes
Sep 13, 2023
7cfa442
NODE-2609 Review fixes
Sep 13, 2023
4c58ac9
NODE-2609 Review fixes
Sep 13, 2023
d5bf5c0
NODE-2609 Review fixes
Sep 13, 2023
cf1e250
NODE-2609 Correctly apply mined key block that references to non-last…
Sep 19, 2023
98ef738
NODE-2609 Support snapshots for state export/import
Sep 12, 2023
26f343e
NODE-2609 Call rollback trigger only after successful block validation
Sep 20, 2023
1fcb33e
NODE-2609 Review fixes
Sep 21, 2023
6a69eb3
NODE-2609 Review fixes
Sep 22, 2023
a3faefa
NODE-2609 Fix blinking test
Sep 25, 2023
e8e5c5d
Merge branch 'version-1.5.x' into NODE-2609
ivan-mashonskiy Sep 25, 2023
fffd8c2
NODE-2609 Added log
Sep 27, 2023
2bcce95
NODE-2609 More precise prevBlockHeader
Sep 27, 2023
c924598
NODE-2609 Fix elided tx snapshot storing
Sep 28, 2023
a4c5eaa
NODE-2609 Fix elided tx process for light node
Sep 28, 2023
ba99271
Merge branch 'version-1.5.x' into NODE-2609
ivan-mashonskiy Sep 28, 2023
1c7dfad
NODE-2609 Fix import
Sep 28, 2023
cd8cc7f
NODE-2609 Remove challenging block txs from UTX
Sep 29, 2023
0012f34
NODE-2609 Add test on generating balance
Sep 29, 2023
6788996
NODE-2609 Add test
Sep 29, 2023
7bed2d4
NODE-2609 Fix generating balance restoration
Sep 30, 2023
24fe4fc
NODE-2609 Schedule mining attempt after liquid block replacement
Oct 2, 2023
32068f6
NODE-2609 Forge only better challenging blocks
Oct 2, 2023
5643f69
NODE-2609 Use tx status for state hash computation while mining
Oct 2, 2023
49bd78f
NODE-2609 Forge challenging block on appender thread
Oct 2, 2023
b0fb812
NODE-2609 Fix tests
Oct 3, 2023
8eee33a
NODE-2609 Add test
Oct 3, 2023
638db93
NODE-2609 Add test
Oct 4, 2023
71cccb9
NODE-2609 Return UTX cleanup after key block application
Oct 5, 2023
9dc89e8
NODE-2609 Return UTX cleanup after key block application
Oct 5, 2023
68d5e91
Merge branch 'version-1.5.x' into NODE-2609
phearnot Oct 6, 2023
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 @@ -26,7 +26,8 @@ abstract class DBState extends ScorexLogging {
new RocksDBWriter(
rdb,
settings.blockchainSettings,
settings.dbSettings.copy(maxCacheSize = 1)
settings.dbSettings.copy(maxCacheSize = 1),
settings.enableLightMode
)

AddressScheme.current = new AddressScheme { override val chainId: Byte = 'W' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object RollbackBenchmark extends ScorexLogging {
val settings = Application.loadApplicationConfig(Some(new File(args(0))))
val rdb = RDB.open(settings.dbSettings)
val time = new NTP(settings.ntpServer)
val rocksDBWriter = new RocksDBWriter(rdb, settings.blockchainSettings, settings.dbSettings)
val rocksDBWriter = new RocksDBWriter(rdb, settings.blockchainSettings, settings.dbSettings, settings.enableLightMode)

val issuer = KeyPair(new Array[Byte](32))

Expand Down Expand Up @@ -80,6 +80,7 @@ object RollbackBenchmark extends ScorexLogging {
0,
None,
genesisBlock.header.generationSignature,
ByteStr.empty,
genesisBlock
)

Expand All @@ -103,7 +104,7 @@ object RollbackBenchmark extends ScorexLogging {
val nextSnapshot = StateSnapshot.build(rocksDBWriter, portfolios2.toMap).explicitGet()

log.info("Appending next block")
rocksDBWriter.append(nextSnapshot, 0, 0, None, ByteStr.empty, nextBlock)
rocksDBWriter.append(nextSnapshot, 0, 0, None, ByteStr.empty, ByteStr.empty, nextBlock)

log.info("Rolling back")
val start = System.nanoTime()
Expand Down
10 changes: 5 additions & 5 deletions benchmark/src/test/scala/com/wavesplatform/state/BaseState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.wavesplatform.state

import java.io.File
import java.nio.file.Files

import com.typesafe.config.ConfigFactory
import com.wavesplatform.account.KeyPair
import com.wavesplatform.block.Block
Expand Down Expand Up @@ -76,11 +75,12 @@ trait BaseState {
.block

private def append(prev: Option[Block], next: Block): Unit = {
val preconditionSnapshot =
BlockDiffer.fromBlock(state, prev, next, MiningConstraint.Unlimited, next.header.generationSignature)
val differResult =
BlockDiffer
.fromBlock(state, prev, next, None, MiningConstraint.Unlimited, next.header.generationSignature)
.explicitGet()
.snapshot
state.append(preconditionSnapshot, 0, 0, None, next.header.generationSignature, next)

state.append(differResult.snapshot, 0, 0, None, next.header.generationSignature, differResult.computedStateHash, next)
}

def applyBlock(b: Block): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ object RocksDBWriterBenchmark {
RDB.open(wavesSettings.dbSettings)
}

val db = new RocksDBWriter(rawDB, wavesSettings.blockchainSettings, wavesSettings.dbSettings)
val db = new RocksDBWriter(rawDB, wavesSettings.blockchainSettings, wavesSettings.dbSettings, wavesSettings.enableLightMode)

def loadBlockInfoAt(height: Int): Option[(BlockMeta, Seq[(TxMeta, Transaction)])] =
loadBlockMetaAt(height).map { meta =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ object WavesEnvironmentBenchmark {
}

val environment: Environment[Id] = {
val state = new RocksDBWriter(rdb, wavesSettings.blockchainSettings, wavesSettings.dbSettings)
val state = new RocksDBWriter(rdb, wavesSettings.blockchainSettings, wavesSettings.dbSettings, wavesSettings.enableLightMode)
new WavesEnvironment(
AddressScheme.current.chainId,
Coeval.raiseError(new NotImplementedError("`tx` is not implemented")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,7 @@ class BlockchainUpdatesSpec extends FreeSpec with WithBUDomain with ScalaFutures
xtnBuybackAddress,
d.blockchain
)

append.stateUpdate.get.balances shouldBe Seq(
PBBalanceUpdate(
challengingMiner.toAddress.toByteString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object BaseTargetChecker {
blockchainUpdater.isFeatureActivated(BlockchainFeatures.TransactionStateSnapshot)
)
.explicitGet()
blockchainUpdater.processBlock(genesisBlock, genesisBlock.header.generationSignature)
blockchainUpdater.processBlock(genesisBlock, genesisBlock.header.generationSignature, None)

NodeConfigs.Default.map(_.withFallback(sharedConfig)).collect {
case cfg if cfg.as[Boolean]("waves.miner.enable") =>
Expand Down
2 changes: 2 additions & 0 deletions node-it/src/test/scala/com/wavesplatform/it/NodeConfigs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ object NodeConfigs {
s"waves.blockchain.custom.functionality.min-asset-info-update-interval = $blocks"

val nonMiner: String = "waves.miner.enable = no"

val lightNode: String = "waves.enable-light-mode = true"
}

}
24 changes: 21 additions & 3 deletions node-it/src/test/scala/com/wavesplatform/it/api/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.state.DataEntry
import com.wavesplatform.transaction.assets.exchange.AssetPair
import com.wavesplatform.transaction.transfer.MassTransferTransaction.Transfer
import io.grpc.{Metadata, Status => GrpcStatus}
import play.api.libs.json._
import io.grpc.{Metadata, Status as GrpcStatus}
import play.api.libs.json.*

import scala.util.{Failure, Success}

Expand Down Expand Up @@ -802,10 +802,11 @@ case class Block(
reward: Option[Long],
desiredReward: Option[Long],
vrf: Option[String],
challengedHeader: Option[ChallengedBlockHeader],
version: Option[Byte] = None
)
object Block {
import PublicKey._
import PublicKey.*

implicit val blockFormat: Format[Block] = Format(
Reads(jsv =>
Expand All @@ -830,6 +831,7 @@ object Block {
baseTarget <- (jsv \ "nxt-consensus" \ "base-target").validateOpt[Int]
transactionsRoot <- (jsv \ "transactionsRoot").validateOpt[String]
vrf <- (jsv \ "VRF").validateOpt[String]
challengedHeader <- (jsv \ "challengedHeader").validateOpt[ChallengedBlockHeader]
} yield Block(
id,
signature,
Expand All @@ -850,6 +852,7 @@ object Block {
reward,
desiredReward,
vrf,
challengedHeader,
version
)
),
Expand All @@ -873,6 +876,7 @@ case class BlockHeader(
desiredReward: Option[Long],
totalFee: Long,
vrf: Option[String],
challengedHeader: Option[ChallengedBlockHeader],
version: Option[Byte] = None
)
object BlockHeader {
Expand All @@ -895,6 +899,7 @@ object BlockHeader {
baseTarget <- (jsv \ "nxt-consensus" \ "base-target").validateOpt[Int]
transactionsRoot <- (jsv \ "transactionsRoot").validateOpt[String]
vrf <- (jsv \ "VRF").validateOpt[String]
challengedHeader <- (jsv \ "challengedHeader").validateOpt[ChallengedBlockHeader]
} yield BlockHeader(
id,
signature,
Expand All @@ -911,13 +916,26 @@ object BlockHeader {
desiredReward,
totalFee,
vrf,
challengedHeader,
version
)
),
Json.writes[BlockHeader]
)
}

case class ChallengedBlockHeader(
headerSignature: String,
features: Set[Short],
generator: String,
generatorPublicKey: String,
desiredReward: Long,
stateHash: Option[String]
)
object ChallengedBlockHeader {
implicit val challengedBlockHeaderFormat: Format[ChallengedBlockHeader] = Json.format
}

case class GenerationSignatureResponse(generationSignature: String)
object GenerationSignatureResponse {
implicit val generationSignatureResponseFormat: Format[GenerationSignatureResponse] = Json.format
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package com.wavesplatform.it.sync

import com.typesafe.config.Config
import com.wavesplatform.account.KeyPair
import com.wavesplatform.block.Block
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.consensus.FairPoSCalculator
import com.wavesplatform.{TestValues, crypto}
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.it.api.Block as ApiBlock
import com.wavesplatform.it.{BaseFunSuite, Node, NodeConfigs, TransferSending}
import com.wavesplatform.it.api.AsyncNetworkApi.NodeAsyncNetworkApi
import com.wavesplatform.it.api.SyncHttpApi.*
import com.wavesplatform.lang.directives.values.V8
import com.wavesplatform.lang.v1.compiler.TestCompiler
import com.wavesplatform.network.RawBytes
import com.wavesplatform.transaction.Asset.Waves
import com.wavesplatform.transaction.assets.exchange.OrderType
import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer
import com.wavesplatform.transaction.{Transaction, TxHelpers, TxNonNegativeAmount}

import scala.concurrent.Await
import scala.concurrent.duration.*

class BlockChallengeSuite extends BaseFunSuite with TransferSending {
override def nodeConfigs: Seq[Config] =
NodeConfigs.newBuilder
.overrideBase(_.quorum(4))
.overrideBase(_.minAssetInfoUpdateInterval(0))
.overrideBase(
_.preactivatedFeatures(
BlockchainFeatures.SynchronousCalls.id.toInt -> 0,
BlockchainFeatures.RideV6.id.toInt -> 0,
BlockchainFeatures.ConsensusImprovements.id.toInt -> 0,
BlockchainFeatures.TransactionStateSnapshot.id.toInt -> 0
)
)
.withDefault(1)
.withSpecial(1, _.lightNode)
.withSpecial(2, _.nonMiner)
.buildNonConflicting()

test("NODE-1167, NODE-1174, NODE-1175. All nodes should receive and apply block with challenge") {
val challenger = nodes.head
val sender = nodes(1)
val malicious = nodes.last

val height = challenger.height
val lastBlock = challenger.blockAt(height)

val elidedTransfer = TxHelpers.transfer(malicious.keyPair, amount = malicious.balance(malicious.address).balance + 200000000)
val txs = createTxs(challenger, sender, nodes(2)) :+ elidedTransfer
val invalidBlock = createBlockWithInvalidStateHash(lastBlock, height, malicious.keyPair, txs)
waitForBlockTime(invalidBlock)
Await.ready(challenger.sendByNetwork(RawBytes.fromBlock(invalidBlock)), 2.minutes)

txs.foreach { tx =>
val txInfo = nodes.waitForTransaction(tx.id().toString)
val expectedStatus = if (tx.id() == elidedTransfer.id()) "elided" else "succeeded"
txInfo.applicationStatus shouldBe Some(expectedStatus)
}

val challengingIds = nodes.map { node =>
val challengingBlock = node.blockAt(height + 1)
checkChallengingBlock(challengingBlock, invalidBlock, challenger.address, txs)
challengingBlock.id
}

challengingIds.toSet.size shouldBe 1
}

private def checkChallengingBlock(challengingBlock: ApiBlock, challengedBlock: Block, challengerAddress: String, txs: Seq[Transaction]) = {
challengingBlock.challengedHeader shouldBe defined
val challengedHeader = challengingBlock.challengedHeader.get
challengedHeader.headerSignature shouldBe challengedBlock.signature.toString
challengedHeader.features shouldBe challengedBlock.header.featureVotes.toSet
challengedHeader.desiredReward shouldBe challengedBlock.header.rewardVote
challengedHeader.stateHash shouldBe challengedBlock.header.stateHash.map(_.toString)
challengedHeader.generator shouldBe challengedBlock.header.generator.toAddress.toString
challengedHeader.generatorPublicKey shouldBe challengedBlock.header.generator.toString
challengingBlock.generator shouldBe challengerAddress
challengingBlock.transactions.map(_.id).toSet shouldBe txs.map(_.id().toString).toSet
}

private def createBlockWithInvalidStateHash(lastBlock: ApiBlock, height: Int, signer: KeyPair, txs: Seq[Transaction]): Block = {
val lastBlockVrfOrGenSig = lastBlock.vrf.orElse(lastBlock.generationSignature).map(str => ByteStr.decodeBase58(str).get).get.arr
val genSig: ByteStr = crypto.signVRF(signer.privateKey, lastBlockVrfOrGenSig)

val hitSource =
crypto.verifyVRF(genSig, lastBlockVrfOrGenSig, signer.publicKey).explicitGet()

val posCalculator = FairPoSCalculator.V1
val version = 5.toByte

val validBlockDelay: Long = posCalculator
.calculateDelay(
hit(hitSource.arr),
lastBlock.baseTarget.get,
nodes.head.accountBalances(signer.toAddress.toString)._2
)

val baseTarget: Long = posCalculator
.calculateBaseTarget(
10,
height,
lastBlock.baseTarget.get,
lastBlock.timestamp,
None,
lastBlock.timestamp + validBlockDelay
)

Block
.buildAndSign(
version = version,
timestamp = lastBlock.timestamp + validBlockDelay,
reference = ByteStr.decodeBase58(lastBlock.id).get,
baseTarget = baseTarget,
generationSignature = genSig,
txs = txs,
signer = signer,
featureVotes = Seq(22),
rewardVote = 1000000000L,
stateHash = Some(ByteStr.fill(32)(1)),
challengedHeader = None
)
.explicitGet()
}

private def hit(generatorSignature: Array[Byte]): BigInt = BigInt(1, generatorSignature.take(8).reverse)

private def waitForBlockTime(block: Block): Unit = {
val timeout = block.header.timestamp - System.currentTimeMillis()

if (timeout > 0) Thread.sleep(timeout)
}

private def createTxs(challenger: Node, sender: Node, dApp: Node): Seq[Transaction] = {
val dAppScript = TestCompiler(V8).compileContract(
s"""
|@Callable(i)
|func foo() = []
|""".stripMargin
)
val assetScript = TestCompiler(V8).compileAsset("true")

val issue = TxHelpers.issue(sender.keyPair)
val issueSmart = TxHelpers.issue(sender.keyPair, name = "smart", script = Some(assetScript))
val lease = TxHelpers.lease(sender.keyPair)

Seq(
issue,
issueSmart,
TxHelpers.setScript(dApp.keyPair, dAppScript),
TxHelpers.burn(issue.asset, sender = sender.keyPair),
TxHelpers.createAlias("alias", sender.keyPair),
TxHelpers.dataSingle(sender.keyPair),
TxHelpers.exchange(
TxHelpers.order(OrderType.BUY, issue.asset, Waves, sender = sender.keyPair, matcher = sender.keyPair),
TxHelpers.order(OrderType.SELL, issue.asset, Waves, sender = sender.keyPair, matcher = sender.keyPair),
sender.keyPair
),
TxHelpers.invoke(dApp.keyPair.toAddress, Some("foo"), invoker = sender.keyPair),
lease,
TxHelpers.leaseCancel(lease.id(), sender.keyPair),
TxHelpers
.massTransfer(
sender.keyPair,
Seq(ParsedTransfer(challenger.keyPair.toAddress, TxNonNegativeAmount.unsafeFrom(1))),
fee = TestValues.fee
),
TxHelpers.reissue(issue.asset, sender.keyPair),
TxHelpers.setAssetScript(sender.keyPair, issueSmart.asset, assetScript, fee = 200000000),
TxHelpers.transfer(sender.keyPair, challenger.keyPair.toAddress, 1),
TxHelpers.sponsor(issue.asset, sender = sender.keyPair),
TxHelpers.updateAssetInfo(issue.assetId, sender = sender.keyPair)
)
}
}
Loading