Skip to content

Commit

Permalink
Merge pull request #1807 from ergoplatform/v4.0.40
Browse files Browse the repository at this point in the history
Candidate for 4.0.40
  • Loading branch information
kushti authored Aug 18, 2022
2 parents 15bd802 + 0ee60e0 commit ba74e13
Show file tree
Hide file tree
Showing 23 changed files with 328 additions and 249 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ To run specific Ergo version `<VERSION>` as a service with custom config `/path/
-e MAX_HEAP=3G \
ergoplatform/ergo:<VERSION> --<networkId> -c /etc/myergo.conf

Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v4.0.39`.
Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v4.0.40`.

This will connect to the Ergo mainnet or testnet following your configuration passed in `myergo.conf` and network flag `--<networkId>`. Every default config value would be overwritten with corresponding value in `myergo.conf`. `MAX_HEAP` variable can be used to control how much memory can the node consume.

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/api/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: "3.0.2"

info:
version: "4.0.39"
version: "4.0.40"
title: Ergo Node API
description: API docs for Ergo Node. Models are shared between all Ergo products
contact:
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ scorex {
nodeName = "ergo-node"

# Network protocol version to be sent in handshakes
appVersion = 4.0.39
appVersion = 4.0.40

# Network agent name. May contain information about client code
# stack, starting from core code-base up to the end graphical interface.
Expand Down
6 changes: 3 additions & 3 deletions src/main/resources/mainnet.conf
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ ergo {
# The node still applying transactions to UTXO set and so checks UTXO set digests for each block.
# Block at checkpoint height is to be checked against expected one.
checkpoint = {
height = 804900
blockId = "9d377e888dc7753ca5de3a7bc879afdc01993fd24f27bfe9c270d4828273c1a4"
height = 818258
blockId = "77f500f93890f7df6cc9d5214c0b1db6e9251c96915c716fb1aab6360d768e63"
}

# List with hex-encoded identifiers of transactions banned from getting into memory pool
Expand All @@ -65,7 +65,7 @@ scorex {
network {
magicBytes = [1, 0, 2, 4]
bindAddress = "0.0.0.0:9030"
nodeName = "ergo-mainnet-4.0.39"
nodeName = "ergo-mainnet-4.0.40"
nodeName = ${?NODENAME}
knownPeers = [
"213.239.193.208:9030",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import scorex.core.serialization.ScorexSerializer
import scorex.core.transaction.Transaction
import scorex.core.utils.ScorexEncoding
import scorex.core.validation.ValidationResult.fromValidationState
import scorex.core.validation.{ModifierValidator, ValidationResult, ValidationState}
import scorex.core.validation.{InvalidModifier, ModifierValidator, ValidationResult, ValidationState}
import scorex.db.ByteArrayUtils
import scorex.util.serialization.{Reader, Writer}
import scorex.util.{ModifierId, ScorexLogging, bytesToId}
Expand Down Expand Up @@ -85,14 +85,14 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input],
*/
def validateStateless(): ValidationState[Unit] = {
ModifierValidator(ErgoValidationSettings.initial)
.validate(txNoInputs, inputs.nonEmpty, s"$id")
.validate(txNoOutputs, outputCandidates.nonEmpty, s"$id")
.validate(txManyInputs, inputs.size <= Short.MaxValue, s"$id: ${inputs.size}")
.validate(txManyDataInputs, dataInputs.size <= Short.MaxValue, s"$id: ${dataInputs.size}")
.validate(txManyOutputs, outputCandidates.size <= Short.MaxValue, s"$id: ${outputCandidates.size}")
.validate(txNegativeOutput, outputCandidates.forall(_.value >= 0), s"$id: ${outputCandidates.map(_.value)}")
.validateNoFailure(txOutputSum, outputsSumTry)
.validate(txInputsUnique, inputs.distinct.size == inputs.size, s"$id: ${inputs.distinct.size} == ${inputs.size}")
.validate(txNoInputs, inputs.nonEmpty, InvalidModifier(s"Tx $id has no inputs", id, modifierTypeId))
.validate(txNoOutputs, outputCandidates.nonEmpty, InvalidModifier(s"Tx $id has no outputs", id, modifierTypeId))
.validate(txManyInputs, inputs.size <= Short.MaxValue, InvalidModifier(s"$id: ${inputs.size}", id, modifierTypeId))
.validate(txManyDataInputs, dataInputs.size <= Short.MaxValue, InvalidModifier(s"$id: ${dataInputs.size}", id, modifierTypeId))
.validate(txManyOutputs, outputCandidates.size <= Short.MaxValue, InvalidModifier(s"$id: ${outputCandidates.size}", id, modifierTypeId))
.validate(txNegativeOutput, outputCandidates.forall(_.value >= 0), InvalidModifier(s"$id: ${outputCandidates.map(_.value)}", id, modifierTypeId))
.validateNoFailure(txOutputSum, outputsSumTry, id, modifierTypeId)
.validate(txInputsUnique, inputs.distinct.size == inputs.size, InvalidModifier(s"$id: ${inputs.distinct.size} == ${inputs.size}", id, modifierTypeId))
}

/**
Expand Down Expand Up @@ -149,9 +149,9 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input],

validationBefore
// Check whether input box script interpreter raised exception
.validate(txScriptValidation, costTry.isSuccess && isCostValid, s"$id: #$inputIndex => $costTry")
.validate(txScriptValidation, costTry.isSuccess && isCostValid, InvalidModifier(s"$id: #$inputIndex => $costTry", id, modifierTypeId))
// Check that cost of the transaction after checking the input becomes too big
.validate(bsBlockTransactionsCost, currCost <= maxCost, s"$id: cost exceeds limit after input #$inputIndex")
.validate(bsBlockTransactionsCost, currCost <= maxCost, InvalidModifier(s"$id: cost exceeds limit after input #$inputIndex", id, modifierTypeId))
.map(c => addExact(c, scriptCost))
}

Expand All @@ -162,11 +162,11 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input],
val blockVersion = stateContext.blockVersion

validationBefore
.validate(txDust, out.value >= BoxUtils.minimalErgoAmount(out, stateContext.currentParameters), s"$id, output ${Algos.encode(out.id)}, ${out.value} >= ${BoxUtils.minimalErgoAmount(out, stateContext.currentParameters)}")
.validate(txFuture, out.creationHeight <= stateContext.currentHeight, s" ${out.creationHeight} <= ${stateContext.currentHeight} is not true, output id: $id: output $out")
.validate(txNegHeight, (blockVersion == 1) || out.creationHeight >= 0, s" ${out.creationHeight} >= 0 is not true, output id: $id: output $out")
.validate(txBoxSize, out.bytes.length <= MaxBoxSize.value, s"$id: output $out")
.validate(txBoxPropositionSize, out.propositionBytes.length <= MaxPropositionBytes.value, s"$id: output $out")
.validate(txDust, out.value >= BoxUtils.minimalErgoAmount(out, stateContext.currentParameters), InvalidModifier(s"$id, output ${Algos.encode(out.id)}, ${out.value} >= ${BoxUtils.minimalErgoAmount(out, stateContext.currentParameters)}", id, modifierTypeId))
.validate(txFuture, out.creationHeight <= stateContext.currentHeight, InvalidModifier(s" ${out.creationHeight} <= ${stateContext.currentHeight} is not true, output id: $id: output $out", id, modifierTypeId))
.validate(txNegHeight, (blockVersion == 1) || out.creationHeight >= 0, InvalidModifier(s" ${out.creationHeight} >= 0 is not true, output id: $id: output $out", id, modifierTypeId))
.validate(txBoxSize, out.bytes.length <= MaxBoxSize.value, InvalidModifier(s"$id: output $out", id, modifierTypeId))
.validate(txBoxPropositionSize, out.propositionBytes.length <= MaxPropositionBytes.value, InvalidModifier(s"$id: output $out", id, modifierTypeId))
}

private def verifyAssets(validationBefore: ValidationState[Long],
Expand All @@ -189,7 +189,7 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input],

validationBefore
// Check that transaction is not too costly considering all the assets
.validate(bsBlockTransactionsCost, maxCost >= newCost, s"$id: assets cost")
.validate(bsBlockTransactionsCost, maxCost >= newCost, InvalidModifier(s"$id: assets cost", id, modifierTypeId))
.validateSeq(outAssets) {
case (validationState, (outAssetId, outAmount)) =>
val inAmount: Long = inAssets.getOrElse(outAssetId, -1L)
Expand All @@ -198,12 +198,12 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input],
// with a possible exception for a new asset created by the transaction
validationState.validate(txAssetsPreservation,
inAmount >= outAmount || (outAssetId == newAssetId && outAmount > 0),
s"$id: Amount in = $inAmount, out = $outAmount. Allowed new asset = $newAssetId, out = $outAssetId")
InvalidModifier(s"$id: Amount in = $inAmount, out = $outAmount. Allowed new asset = $newAssetId, out = $outAssetId", id, modifierTypeId))
}
.payload(newCost)
case Failure(e) =>
// should never be here as far as we've already checked this when we've created the box
ModifierValidator.fatal(e.getMessage)
ModifierValidator.fatal(e.getMessage, id, modifierTypeId)
}
}

Expand Down Expand Up @@ -375,26 +375,26 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input],
val startCost = addExact(initialCost, accumulatedCost)
ModifierValidator(stateContext.validationSettings)
// Check that the initial transaction cost is not too exceeding block limit
.validate(bsBlockTransactionsCost, maxCost >= startCost, s"$id: initial cost")
.validate(bsBlockTransactionsCost, maxCost >= startCost, InvalidModifier(s"$id: initial cost", id, modifierTypeId))
// Starting validation
.payload(startCost)
// Perform cheap checks first
.validateNoFailure(txAssetsInOneBox, outAssetsTry)
.validateNoFailure(txAssetsInOneBox, outAssetsTry, id, modifierTypeId)
.validate(txPositiveAssets,
outputCandidates.forall(_.additionalTokens.forall(_._2 > 0)),
s"$id: ${outputCandidates.map(_.additionalTokens)}")
InvalidModifier(s"$id: ${outputCandidates.map(_.additionalTokens)}", id, modifierTypeId))
// Check that outputs are not dust, and not created in future
.validateSeq(outputs) { case (validationState, out) => verifyOutput(validationState, out, stateContext) }
// Just to be sure, check that all the input boxes to spend (and to read) are presented.
// Normally, this check should always pass, if the client is implemented properly
// so it is not part of the protocol really.
.validate(txBoxesToSpend, boxesToSpend.size == inputs.size, s"$id: ${boxesToSpend.size} == ${inputs.size}")
.validate(txDataBoxes, dataBoxes.size == dataInputs.size, s"$id: ${dataBoxes.size} == ${dataInputs.size}")
.validate(txBoxesToSpend, boxesToSpend.size == inputs.size, InvalidModifier(s"$id: ${boxesToSpend.size} == ${inputs.size}", id, modifierTypeId))
.validate(txDataBoxes, dataBoxes.size == dataInputs.size, InvalidModifier(s"$id: ${dataBoxes.size} == ${dataInputs.size}", id, modifierTypeId))
// Check that there are no overflow in input and output values
.validate(txInputsSum, inputSumTry.isSuccess, s"$id")
.validate(txInputsSum, inputSumTry.isSuccess, InvalidModifier(s"$id as invalid Inputs Sum", id, modifierTypeId))
// Check that transaction is not creating money out of thin air.
.validate(txErgPreservation, inputSumTry == outputsSumTry, s"$id: $inputSumTry == $outputsSumTry")
.validateTry(outAssetsTry, e => ModifierValidator.fatal("Incorrect assets", e)) { case (validation, (outAssets, outAssetsNum)) =>
.validate(txErgPreservation, inputSumTry == outputsSumTry, InvalidModifier(s"$id: $inputSumTry == $outputsSumTry", id, modifierTypeId))
.validateTry(outAssetsTry, e => ModifierValidator.fatal("Incorrect assets", id, modifierTypeId, e)) { case (validation, (outAssets, outAssetsNum)) =>
verifyAssets(validation, outAssets, outAssetsNum, boxesToSpend, stateContext)
}
// Check inputs, the most expensive check usually, so done last.
Expand All @@ -403,7 +403,7 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input],
verifyInput(validation, boxesToSpend, dataBoxes, box, idx.toShort, stateContext, currentTxCost)
}
.validate(txReemission, !stateContext.ergoSettings.chainSettings.reemission.checkReemissionRules ||
verifyReemissionSpending(boxesToSpend, outputCandidates, stateContext).isSuccess)
verifyReemissionSpending(boxesToSpend, outputCandidates, stateContext).isSuccess, InvalidModifier(id, id, modifierTypeId))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.ergoplatform.nodeView.ErgoNodeViewHolder._
import scorex.core.consensus.{Equal, Fork, Nonsense, Older, Unknown, Younger}
import scorex.core.network.ModifiersStatus.Requested
import scorex.core.{ModifierTypeId, NodeViewModifier, PersistentNodeViewModifier, idsToString}
import scorex.core.network.NetworkController.ReceivableMessages.{DisconnectFrom, PenalizePeer, RegisterMessageSpecs, SendToNetwork}
import scorex.core.network.NetworkController.ReceivableMessages.{PenalizePeer, RegisterMessageSpecs, SendToNetwork}
import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages._
import org.ergoplatform.nodeView.state.ErgoStateReader
import org.ergoplatform.nodeView.wallet.ErgoWalletReader
Expand Down Expand Up @@ -84,10 +84,13 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef,
private val maxHeadersPerBucket = 400 // maximum of headers to download by single peer

// It could be the case that adversarial peers are sending sync messages to the node to cause
// resource exhaustion. To prevent it, we do not answer to a peer on sync message, if previous one was sent
// resource exhaustion. To prevent it, we do not provide an answer for sync message, if previous one was sent
// no more than `PerPeerSyncLockTime` milliseconds ago.
private val PerPeerSyncLockTime = 100

// when we got last modifier, both unconfirmed transactions and block sections count
private var lastModifierGotTime: Long = 0

/**
* Register periodic events
*/
Expand Down Expand Up @@ -416,7 +419,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef,
}

/**
* A helper method to ask for block sectiona from given peer
* A helper method to ask for block section from given peer
*
* @param modifierTypeId - block section type id
* @param modifierIds - ids of block section to download
Expand Down Expand Up @@ -503,16 +506,17 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef,
* Filter out non-requested block parts (with a penalty to spamming peer),
* parse block parts and send valid modifiers to NodeViewHolder
*/
protected def modifiersFromRemote(
hr: ErgoHistory,
data: ModifiersData,
remote: ConnectedPeer,
blockAppliedTxsCache: FixedSizeApproximateCacheQueue): Unit = {
protected def modifiersFromRemote(hr: ErgoHistory,
data: ModifiersData,
remote: ConnectedPeer,
blockAppliedTxsCache: FixedSizeApproximateCacheQueue): Unit = {
val typeId = data.typeId
val modifiers = data.modifiers
log.info(s"Got ${modifiers.size} modifiers of type $typeId from remote connected peer: ${remote.connectionId}")
log.debug("Modifier ids: " + modifiers.keys)

lastModifierGotTime = System.currentTimeMillis()

// filter out non-requested modifiers
val requestedModifiers = processSpam(remote, typeId, modifiers, blockAppliedTxsCache)

Expand Down Expand Up @@ -741,20 +745,35 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef,
log.info(s"Peer ${peer.toString} has not delivered modifier " +
s"$modifierTypeId : ${encoder.encodeId(modifierId)} on time, status tracker: $syncTracker")

penalizeNonDeliveringPeer(peer)
// For now, we drop connection to the peer, as we do not ban it, connection will be likely established
// again after some time (but not soon if connections limit reached)
networkControllerRef ! DisconnectFrom(peer)
// Number of block section delivery checks increased or initialized,
// except the case where we can have issues with connectivity,
// which is currently defined by comparing request time with time the
// node got last modifier (in future we may consider more precise method)
val checksDone = deliveryTracker.getRequestedInfo(modifierTypeId, modifierId) match {
case Some(ri) if ri.requestTime < lastModifierGotTime =>
ri.checks
case Some(ri) =>
penalizeNonDeliveringPeer(peer)
ri.checks + 1
case None => 0
}

val checksDone = deliveryTracker.requestsMade(modifierTypeId, modifierId) + 1
val maxDeliveryChecks = networkSettings.maxDeliveryChecks
if(checksDone < maxDeliveryChecks) {
if (checksDone < maxDeliveryChecks) {
log.info(s"Rescheduling request for $modifierId")
deliveryTracker.setUnknown(modifierId, modifierTypeId)
requestBlockSection(modifierTypeId, modifierId, checksDone, Some(peer))
} else {
log.error(s"Exceeded max delivery attempts($maxDeliveryChecks) limit for $modifierId")
deliveryTracker.setUnknown(modifierId, modifierTypeId)
if (modifierTypeId == Header.modifierTypeId) {
// if we can not get header after max number of attempts, invalidate it
log.info(s"Marking header as invalid: $modifierId")
deliveryTracker.setInvalid(modifierId, modifierTypeId)
} else {
// we will stop to ask for non-header block section automatically after some time,
// see how `nextModifiersToDownload` done in `ToDownloadProcessor`
deliveryTracker.setUnknown(modifierId, modifierTypeId)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import org.ergoplatform.modifiers.history.extension.Extension
import org.ergoplatform.modifiers.history.header.{Header, PreGenesisHeader}
import org.ergoplatform.modifiers.history.popow.{NipopowAlgos, NipopowProof, PoPowHeader, PoPowParams}
import org.ergoplatform.modifiers.state.UTXOSnapshotChunk
import org.ergoplatform.modifiers.{NonHeaderBlockSection, ErgoFullBlock, BlockSection}
import org.ergoplatform.modifiers.{BlockSection, ErgoFullBlock, NonHeaderBlockSection}
import org.ergoplatform.nodeView.history.ErgoHistory.Height
import org.ergoplatform.nodeView.history.storage._
import org.ergoplatform.nodeView.history.storage.modifierprocessors._
import org.ergoplatform.nodeView.history.storage.modifierprocessors.popow.PoPoWProofsProcessor
import org.ergoplatform.settings.ErgoSettings
import scorex.core.{ModifierTypeId, NodeViewComponent}
import scorex.core.consensus.{ContainsModifiers, Equal, Fork, PeerChainStatus, ModifierSemanticValidity, Older, Unknown, Younger}
import scorex.core.consensus.{ContainsModifiers, Equal, Fork, ModifierSemanticValidity, Older, PeerChainStatus, Unknown, Younger}
import scorex.core.utils.ScorexEncoding
import scorex.core.validation.MalformedModifierError
import scorex.util.{ModifierId, ScorexLogging}
Expand Down Expand Up @@ -419,7 +419,7 @@ trait ErgoHistoryReader
case chunk: UTXOSnapshotChunk =>
validate(chunk)
case m: Any =>
Failure(new MalformedModifierError(s"Modifier $m has incorrect type"))
Failure(new MalformedModifierError(s"Modifier $m has incorrect type", modifier.id, modifier.modifierTypeId))
}
}

Expand Down
Loading

0 comments on commit ba74e13

Please sign in to comment.