Skip to content

Commit

Permalink
Merge pull request #1902 from ergoplatform/v5.0.4
Browse files Browse the repository at this point in the history
Candidate for 5.0.4
  • Loading branch information
kushti authored Dec 6, 2022
2 parents d60dff6 + 9b03e8d commit 37a2e07
Show file tree
Hide file tree
Showing 23 changed files with 435 additions and 342 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Ergo Platform website: [https://ergoplatform.org/](https://ergoplatform.org/)
* Memory-hard Proof-of-Work function [Autolykos2](https://docs.ergoplatform.com/ErgoPow.pdf)
* Support for stateless clients (asymmetric, based on [https://eprint.iacr.org/2016/994](https://eprint.iacr.org/2016/994)),
[NiPoPoWs](https://eprint.iacr.org/2017/963.pdf), hybrid modes
* [Alternative transactional language](https://github.com/ScorexFoundation/sigmastate-interpreter), which is more powerful that Bitcoin Script but also safe against
* [Alternative transactional language](https://github.com/ScorexFoundation/sigmastate-interpreter), which is more powerful than Bitcoin Script but also safe against
heavy validation attacks
* Alternative fee model with [mandatory storage-rent component](https://fc18.ifca.ai/bitcoin/papers/bitcoin18-final18.pdf )

Expand All @@ -23,7 +23,7 @@ and currently the reference implementation code should be considered as the spec

## Building and Running Node and UI

See [documentation](https://docs.ergoplatform.com/node/install/install/)
See [documentation](https://docs.ergoplatform.com/node/install/)

## Testing

Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.ergoplatform.wallet.boxes

import org.ergoplatform.ErgoBoxAssets
import org.ergoplatform.{ErgoBoxAssets, ErgoBoxAssetsHolder}
import org.ergoplatform.SigmaConstants.MaxBoxSize
import org.ergoplatform.wallet.TokensMap
import org.ergoplatform.wallet.boxes.BoxSelector.{BoxSelectionError, BoxSelectionResult}
Expand Down Expand Up @@ -52,6 +52,17 @@ trait BoxSelector extends ScorexLogging {
}.getOrElse(0L)
}

def selectionResultWithEip27Output[T <: ErgoBoxAssets](inputBoxes: Seq[T],
changeBoxes: Seq[ErgoBoxAssets]): BoxSelectionResult[T] = {
val reemissionAmt = reemissionAmount(inputBoxes)
val payToReemissionBox = if(reemissionAmt > 0) {
Some(ErgoBoxAssetsHolder(reemissionAmt))
} else {
None
}
new BoxSelectionResult(inputBoxes, changeBoxes, payToReemissionBox)
}

}

object BoxSelector {
Expand All @@ -66,7 +77,16 @@ object BoxSelector {
*/
val ScanDepthFactor = 300

final case class BoxSelectionResult[T <: ErgoBoxAssets](boxes: Seq[T], changeBoxes: Seq[ErgoBoxAssets])
/**
* Containter for box selector output
*
* @param inputBoxes - transaction inputs chosen by a selector
* @param changeBoxes - change outputs
* @param payToReemissionBox - pay-to-reemission output mde according to EIP-27, if needed
*/
class BoxSelectionResult[T <: ErgoBoxAssets](val inputBoxes: Seq[T],
val changeBoxes: Seq[ErgoBoxAssets],
val payToReemissionBox: Option[ErgoBoxAssets])

/**
* Returns how much ERG can be taken from a box when it is spent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ class DefaultBoxSelector(override val reemissionDataOpt: Option[ReemissionData])
},
assetsMet
)) {
val ra = reemissionAmount(res)
formChangeBoxes(currentBalance, targetBalance, currentAssets, targetAssets, ra).mapRight { changeBoxes =>
BoxSelectionResult(res, changeBoxes)
formChangeBoxes(currentBalance, targetBalance, currentAssets, targetAssets).mapRight { changeBoxes =>
selectionResultWithEip27Output(res, changeBoxes)
}
} else {
Left(NotEnoughTokensError(
Expand All @@ -127,14 +126,12 @@ class DefaultBoxSelector(override val reemissionDataOpt: Option[ReemissionData])
* @param targetBalance - ERG amount to be transferred to recipients
* @param foundBoxAssets - assets balances of boxes
* @param targetBoxAssets - assets amounts to be transferred to recipients
* @param reemissionAmt - amount of re-emission tokens in collected boxes
* @return
*/
def formChangeBoxes(foundBalance: Long,
targetBalance: Long,
foundBoxAssets: mutable.Map[ModifierId, Long],
targetBoxAssets: TokensMap,
reemissionAmt: Long): Either[BoxSelectionError, Seq[ErgoBoxAssets]] = {
targetBoxAssets: TokensMap): Either[BoxSelectionError, Seq[ErgoBoxAssets]] = {
AssetUtils.subtractAssetsMut(foundBoxAssets, targetBoxAssets)
val changeBoxesAssets: Seq[mutable.Map[ModifierId, Long]] = foundBoxAssets.grouped(MaxAssetsPerBox).toIndexedSeq
val changeBalance = foundBalance - targetBalance
Expand Down Expand Up @@ -164,26 +161,7 @@ class DefaultBoxSelector(override val reemissionDataOpt: Option[ReemissionData])
} else {
Seq.empty
}

if (reemissionAmt > 0) {
reemissionDataOpt match {
case Some(reemissionData) =>
// we construct this instance to get pay-to-reemission contract from it
// we set re-emission contract NFT id, re-emission start height is not used so we set it to 0
val rc: ReemissionContracts = new ReemissionContracts {
override val reemissionNftIdBytes: Array[Byte] = idToBytes(reemissionData.reemissionNftId)
override val reemissionStartHeight: Int = 0
}
val p2r = rc.payToReemission
val payToReemissionBox = new ErgoBoxCandidate(reemissionAmt, p2r, creationHeight = 0)
Right(payToReemissionBox +: changeBoxes)
case None =>
log.error("reemissionData when reemissionAmt > 0, should not happen at all")
Right(changeBoxes)
}
} else {
Right(changeBoxes)
}
Right(changeBoxes)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,25 @@ class ReplaceCompactCollectBoxSelector(maxInputs: Int,
val tail = inputBoxes.take(maxInputs * BoxSelector.ScanDepthFactor).filter(filterFn).toSeq
// if number of inputs exceeds the limit, the selector is sorting remaining boxes(actually, only 10*maximum
// boxes) by value in descending order and replaces small-value boxes in the inputs by big-value from the tail (1,2,3,4 => 10)
(if (initialSelection.boxes.length > maxInputs) {
(if (initialSelection.inputBoxes.length > maxInputs) {
replace(initialSelection, tail, targetBalance, targetAssets)
} else {
Right(initialSelection)
}).flatMapRight { afterReplacement =>
// if the number of inputs still exceeds the limit, the selector is trying to throw away the dust if possible.
// E.g. if inputs are (100, 200, 1, 2, 1000), target value is 1300 and maximum number of inputs is 3,
// the selector kicks out (1, 2)
if (afterReplacement.boxes.length > maxInputs) {
if (afterReplacement.inputBoxes.length > maxInputs) {
compress(afterReplacement, targetBalance, targetAssets)
} else {
Right(afterReplacement)
}
}.flatMapRight { afterCompaction =>
// if number of inputs after the previous steps is below optimal, the selector is trying to append the dust,
// by sorting remaining boxes in ascending order and appending them till optimal number of inputs.
if (afterCompaction.boxes.length > maxInputs) {
Left(MaxInputsExceededError(s"${afterCompaction.boxes.length} boxes exceed max inputs in transaction ($maxInputs)"))
} else if (afterCompaction.boxes.length < optimalInputs) {
if (afterCompaction.inputBoxes.length > maxInputs) {
Left(MaxInputsExceededError(s"${afterCompaction.inputBoxes.length} boxes exceed max inputs in transaction ($maxInputs)"))
} else if (afterCompaction.inputBoxes.length < optimalInputs) {
collectDust(afterCompaction, tail, targetBalance, targetAssets)
} else {
Right(afterCompaction)
Expand All @@ -88,27 +88,26 @@ class ReplaceCompactCollectBoxSelector(maxInputs: Int,
val compactedBalance = boxes.foldLeft(0L) { case (sum, b) => sum + BoxSelector.valueOf(b, reemissionDataOpt) }
val compactedAssets = mutable.Map[ModifierId, Long]()
AssetUtils.mergeAssetsMut(compactedAssets, boxes.map(_.tokens): _*)
val ra = reemissionAmount(boxes)
super.formChangeBoxes(compactedBalance, targetBalance, compactedAssets, targetAssets, ra)
super.formChangeBoxes(compactedBalance, targetBalance, compactedAssets, targetAssets)
}

protected[boxes] def collectDust[T <: ErgoBoxAssets](bsr: BoxSelectionResult[T],
tail: Seq[T],
targetBalance: Long,
targetAssets: TokensMap): Either[BoxSelectionError, BoxSelectionResult[T]] = {
val diff = optimalInputs - bsr.boxes.length
val diff = optimalInputs - bsr.inputBoxes.length

// it is okay to not to consider reemission tokens here probably, so sorting is done by _.value just, not valueOf()
val dust = tail.sortBy(_.value).take(diff).filter(b => !bsr.boxes.contains(b))
val dust = tail.sortBy(_.value).take(diff).filter(b => !bsr.inputBoxes.contains(b))

val boxes = bsr.boxes ++ dust
calcChange(boxes, targetBalance, targetAssets).mapRight(changeBoxes => BoxSelectionResult(boxes, changeBoxes))
val boxes = bsr.inputBoxes ++ dust
calcChange(boxes, targetBalance, targetAssets).mapRight(changeBoxes => selectionResultWithEip27Output(boxes, changeBoxes))
}

protected[boxes] def compress[T <: ErgoBoxAssets](bsr: BoxSelectionResult[T],
targetBalance: Long,
targetAssets: TokensMap): Either[BoxSelectionError, BoxSelectionResult[T]] = {
val boxes = bsr.boxes
val boxes = bsr.inputBoxes
val diff = boxes.foldLeft(0L) { case (sum, b) => sum + BoxSelector.valueOf(b, reemissionDataOpt) } - targetBalance

val targetAssetsKeys = targetAssets.keySet
Expand All @@ -124,7 +123,7 @@ class ReplaceCompactCollectBoxSelector(maxInputs: Int,
}
val compactedBoxes = boxes.filter(b => !thrownBoxes.contains(b))
calcChange(compactedBoxes, targetBalance, targetAssets)
.mapRight(changeBoxes => BoxSelectionResult(compactedBoxes, changeBoxes))
.mapRight(changeBoxes => selectionResultWithEip27Output(compactedBoxes, changeBoxes))
} else {
Right(bsr)
}
Expand All @@ -135,7 +134,7 @@ class ReplaceCompactCollectBoxSelector(maxInputs: Int,
targetBalance: Long,
targetAssets: TokensMap): Either[BoxSelectionError, BoxSelectionResult[T]] = {
val bigBoxes = tail.sortBy(b => -BoxSelector.valueOf(b, reemissionDataOpt))
val boxesToThrowAway = bsr.boxes.filter(!_.tokens.keySet.exists(tid => targetAssets.keySet.contains(tid)))
val boxesToThrowAway = bsr.inputBoxes.filter(!_.tokens.keySet.exists(tid => targetAssets.keySet.contains(tid)))
val sorted = boxesToThrowAway.sortBy(b => BoxSelector.valueOf(b, reemissionDataOpt))

val boxesToAdd = ListBuffer.empty[T]
Expand Down Expand Up @@ -164,9 +163,9 @@ class ReplaceCompactCollectBoxSelector(maxInputs: Int,

replaceStep(bigBoxes, sorted)
if (boxesToAdd.nonEmpty) {
val compactedBoxes = bsr.boxes.filter(b => !boxesToDrop.contains(b)) ++ boxesToAdd
val compactedBoxes = bsr.inputBoxes.filter(b => !boxesToDrop.contains(b)) ++ boxesToAdd
calcChange(compactedBoxes, targetBalance, targetAssets)
.mapRight(changeBoxes => BoxSelectionResult(compactedBoxes, changeBoxes))
.mapRight(changeBoxes => selectionResultWithEip27Output(compactedBoxes, changeBoxes))
} else {
Right(bsr)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ object TransactionBuilder {
case Right(v) => v
}
// although we're only interested in change boxes, make sure selection contains exact inputs
assert(selection.boxes == inputs, s"unexpected selected boxes, expected: $inputs, got ${selection.boxes}")
assert(selection.inputBoxes == inputs, s"unexpected selected boxes, expected: $inputs, got ${selection.inputBoxes}")
val changeBoxes = selection.changeBoxes
val changeBoxesHaveTokens = changeBoxes.exists(_.tokens.nonEmpty)

Expand Down
Loading

0 comments on commit 37a2e07

Please sign in to comment.